summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-03-25 16:14:53 -0400
committerHazelnoot <acomputerdog@gmail.com>2025-03-25 16:14:53 -0400
commitd8908ef2d8fa84d8e0fc1d30ab90a600a3d88054 (patch)
tree0c8d3e0385ce7021c7187ef8b608f1abd87496e5
parentmerge: enhance: Update de-DE.yml (!949) (diff)
parentenhance(frontend): 設定の移行を手動でトリガーできるように (diff)
downloadsharkey-d8908ef2d8fa84d8e0fc1d30ab90a600a3d88054.tar.gz
sharkey-d8908ef2d8fa84d8e0fc1d30ab90a600a3d88054.tar.bz2
sharkey-d8908ef2d8fa84d8e0fc1d30ab90a600a3d88054.zip
merge upstream
-rw-r--r--.config/cypress-devcontainer.yml5
-rw-r--r--.config/docker_example.yml23
-rw-r--r--.config/example.yml33
-rw-r--r--.devcontainer/devcontainer.json4
-rwxr-xr-x.devcontainer/init.sh2
-rw-r--r--.npmrc2
-rw-r--r--CHANGELOG.md133
-rw-r--r--CONTRIBUTING.md1
-rw-r--r--assets/about/drive.pngbin96460 -> 0 bytes
-rw-r--r--assets/about/post.pngbin324167 -> 0 bytes
-rw-r--r--assets/about/reaction.pngbin24621 -> 0 bytes
-rw-r--r--assets/about/ui.pngbin97127 -> 0 bytes
-rw-r--r--assets/ss/explore.jpgbin243786 -> 0 bytes
-rw-r--r--assets/ss/user.jpgbin151706 -> 0 bytes
-rw-r--r--cypress/e2e/basic.cy.ts2
-rw-r--r--locales/ar-SA.yml6
-rw-r--r--locales/bn-BD.yml5
-rw-r--r--locales/ca-ES.yml119
-rw-r--r--locales/cs-CZ.yml14
-rw-r--r--locales/de-DE.yml57
-rw-r--r--locales/el-GR.yml4
-rw-r--r--locales/en-US.yml109
-rw-r--r--locales/es-ES.yml36
-rw-r--r--locales/fr-FR.yml8
-rw-r--r--locales/id-ID.yml10
-rw-r--r--locales/index.d.ts575
-rw-r--r--locales/it-IT.yml149
-rw-r--r--locales/ja-JP.yml157
-rw-r--r--locales/ja-KS.yml109
-rw-r--r--locales/ko-GS.yml4
-rw-r--r--locales/ko-KR.yml72
-rw-r--r--locales/lo-LA.yml3
-rw-r--r--locales/nl-NL.yml3
-rw-r--r--locales/no-NO.yml3
-rw-r--r--locales/pl-PL.yml5
-rw-r--r--locales/pt-PT.yml12
-rw-r--r--locales/ro-RO.yml3
-rw-r--r--locales/ru-RU.yml8
-rw-r--r--locales/sk-SK.yml5
-rw-r--r--locales/sv-SE.yml3
-rw-r--r--locales/th-TH.yml10
-rw-r--r--locales/tr-TR.yml2
-rw-r--r--locales/uk-UA.yml5
-rw-r--r--locales/uz-UZ.yml4
-rw-r--r--locales/vi-VN.yml6
-rw-r--r--locales/zh-CN.yml101
-rw-r--r--locales/zh-TW.yml167
-rw-r--r--package.json42
-rw-r--r--packages/backend/.swcrc2
-rw-r--r--packages/backend/migration/1739006797620-GoogleAnalytics.js16
-rw-r--r--packages/backend/migration/1740121393164-system-accounts.js37
-rw-r--r--packages/backend/migration/1740129169650-system-accounts-2.js22
-rw-r--r--packages/backend/migration/1740133121105-system-accounts-3.js23
-rw-r--r--packages/backend/migration/1740993126937-system-accounts-4.js17
-rw-r--r--packages/backend/migration/1741279404074-system-accounts-fixup.js26
-rw-r--r--packages/backend/migration/1741424411879-user-featured-fixup.js26
-rw-r--r--packages/backend/migration/1742203321812-chat.js46
-rw-r--r--packages/backend/migration/1742608337548-chat-2.js18
-rw-r--r--packages/backend/migration/1742617546147-chat-3.js26
-rw-r--r--packages/backend/migration/1742707840715-chat-4.js26
-rw-r--r--packages/backend/migration/1742721896936-chat-5.js16
-rw-r--r--packages/backend/migration/1742795111958-chat-6.js20
-rw-r--r--packages/backend/package.json143
-rw-r--r--packages/backend/src/GlobalModule.ts2
-rw-r--r--packages/backend/src/config.ts11
-rw-r--r--packages/backend/src/core/AbuseReportService.ts6
-rw-r--r--packages/backend/src/core/AccountMoveService.ts14
-rw-r--r--packages/backend/src/core/CaptchaService.ts6
-rw-r--r--packages/backend/src/core/ChatService.ts780
-rw-r--r--packages/backend/src/core/CoreModule.ts42
-rw-r--r--packages/backend/src/core/CreateSystemUserService.ts95
-rw-r--r--packages/backend/src/core/DeleteAccountService.ts15
-rw-r--r--packages/backend/src/core/DownloadService.ts4
-rw-r--r--packages/backend/src/core/DriveService.ts7
-rw-r--r--packages/backend/src/core/EmailService.ts7
-rw-r--r--packages/backend/src/core/FanoutTimelineService.ts3
-rw-r--r--packages/backend/src/core/GlobalEventService.ts40
-rw-r--r--packages/backend/src/core/HttpRequestService.ts51
-rw-r--r--packages/backend/src/core/InstanceActorService.ts57
-rw-r--r--packages/backend/src/core/MetaService.ts23
-rw-r--r--packages/backend/src/core/MfmService.ts3
-rw-r--r--packages/backend/src/core/NoteCreateService.ts27
-rw-r--r--packages/backend/src/core/NoteDeleteService.ts24
-rw-r--r--packages/backend/src/core/NoteReadService.ts147
-rw-r--r--packages/backend/src/core/ProxyAccountService.ts28
-rw-r--r--packages/backend/src/core/RelayService.ts33
-rw-r--r--packages/backend/src/core/RemoteUserResolveService.ts2
-rw-r--r--packages/backend/src/core/RoleService.ts25
-rw-r--r--packages/backend/src/core/S3Service.ts2
-rw-r--r--packages/backend/src/core/SignupService.ts21
-rw-r--r--packages/backend/src/core/SystemAccountService.ts172
-rw-r--r--packages/backend/src/core/UserFollowingService.ts28
-rw-r--r--packages/backend/src/core/UserListService.ts10
-rw-r--r--packages/backend/src/core/UserWebhookService.ts2
-rw-r--r--packages/backend/src/core/UtilityService.ts8
-rw-r--r--packages/backend/src/core/WebAuthnService.ts18
-rw-r--r--packages/backend/src/core/WebhookTestService.ts334
-rw-r--r--packages/backend/src/core/activitypub/ApDeliverManagerService.ts19
-rw-r--r--packages/backend/src/core/activitypub/ApInboxService.ts11
-rw-r--r--packages/backend/src/core/activitypub/ApRendererService.ts72
-rw-r--r--packages/backend/src/core/activitypub/ApRequestService.ts7
-rw-r--r--packages/backend/src/core/activitypub/ApResolverService.ts34
-rw-r--r--packages/backend/src/core/activitypub/misc/check-against-url.ts38
-rw-r--r--packages/backend/src/core/activitypub/models/ApPersonService.ts8
-rw-r--r--packages/backend/src/core/entities/ChatEntityService.ts376
-rw-r--r--packages/backend/src/core/entities/MetaEntityService.ts13
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts34
-rw-r--r--packages/backend/src/di-symbols.ts7
-rw-r--r--packages/backend/src/misc/FileWriterStream.ts1
-rw-r--r--packages/backend/src/misc/is-native-token.ts7
-rw-r--r--packages/backend/src/misc/json-schema.ts85
-rw-r--r--packages/backend/src/misc/json-value.ts2
-rw-r--r--packages/backend/src/misc/token.ts (renamed from packages/backend/src/misc/generate-native-user-token.ts)5
-rw-r--r--packages/backend/src/models/ChatApproval.ts39
-rw-r--r--packages/backend/src/models/ChatMessage.ts85
-rw-r--r--packages/backend/src/models/ChatRoom.ts41
-rw-r--r--packages/backend/src/models/ChatRoomInvitation.ts45
-rw-r--r--packages/backend/src/models/ChatRoomMembership.ts45
-rw-r--r--packages/backend/src/models/Meta.ts33
-rw-r--r--packages/backend/src/models/NoteUnread.ts68
-rw-r--r--packages/backend/src/models/Notification.ts4
-rw-r--r--packages/backend/src/models/RepositoryModule.ts69
-rw-r--r--packages/backend/src/models/SystemAccount.ts31
-rw-r--r--packages/backend/src/models/User.ts25
-rw-r--r--packages/backend/src/models/_.ts28
-rw-r--r--packages/backend/src/models/json-schema/chat-message.ts146
-rw-r--r--packages/backend/src/models/json-schema/chat-room-invitation.ts37
-rw-r--r--packages/backend/src/models/json-schema/chat-room-membership.ts37
-rw-r--r--packages/backend/src/models/json-schema/chat-room.ts40
-rw-r--r--packages/backend/src/models/json-schema/meta.ts4
-rw-r--r--packages/backend/src/models/json-schema/notification.ts10
-rw-r--r--packages/backend/src/models/json-schema/role.ts4
-rw-r--r--packages/backend/src/models/json-schema/user.ts9
-rw-r--r--packages/backend/src/postgres.ts27
-rw-r--r--packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts2
-rw-r--r--packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts2
-rw-r--r--packages/backend/src/queue/processors/InboxProcessorService.ts4
-rw-r--r--packages/backend/src/queue/types.ts10
-rw-r--r--packages/backend/src/server/ActivityPubServerService.ts77
-rw-r--r--packages/backend/src/server/FileServerService.ts2
-rw-r--r--packages/backend/src/server/NodeinfoServerService.ts8
-rw-r--r--packages/backend/src/server/ServerModule.ts4
-rw-r--r--packages/backend/src/server/ServerService.ts37
-rw-r--r--packages/backend/src/server/WellKnownServerService.ts25
-rw-r--r--packages/backend/src/server/api/ApiCallService.ts6
-rw-r--r--packages/backend/src/server/api/ApiServerService.ts3
-rw-r--r--packages/backend/src/server/api/AuthenticateService.ts4
-rw-r--r--packages/backend/src/server/api/StreamingApiServerService.ts3
-rw-r--r--packages/backend/src/server/api/endpoint-list.ts24
-rw-r--r--packages/backend/src/server/api/endpoints.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/admin/accounts/create.ts12
-rw-r--r--packages/backend/src/server/api/endpoints/admin/accounts/delete.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/copy.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/delete.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/list.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/update.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts14
-rw-r--r--packages/backend/src/server/api/endpoints/admin/reset-password.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts13
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-proxy-account.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/notes.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/ap/show.ts12
-rw-r--r--packages/backend/src/server/api/endpoints/chat/history.ts73
-rw-r--r--packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts105
-rw-r--r--packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts122
-rw-r--r--packages/backend/src/server/api/endpoints/chat/messages/delete.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/chat/messages/react.ts49
-rw-r--r--packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts73
-rw-r--r--packages/backend/src/server/api/endpoints/chat/messages/search.ts76
-rw-r--r--packages/backend/src/server/api/endpoints/chat/messages/show.ts63
-rw-r--r--packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts71
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/create.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/delete.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts68
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts48
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/join.ts48
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/joining.ts58
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/leave.ts48
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/members.ts74
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/mute.ts49
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/owned.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/show.ts58
-rw-r--r--packages/backend/src/server/api/endpoints/chat/rooms/update.ts65
-rw-r--r--packages/backend/src/server/api/endpoints/clips/create.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/clips/update.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/following/invalidate.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-antennas.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-blocking.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-following.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-muting.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-user-lists.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/move.ts9
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications-grouped.ts13
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts11
-rw-r--r--packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/i/regenerate-token.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/invite/create.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/invite/delete.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/invite/limit.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/invite/list.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/miauth/gen-token.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/mentions.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/reset-db.ts19
-rw-r--r--packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts2
-rw-r--r--packages/backend/src/server/api/openapi/gen-spec.ts10
-rw-r--r--packages/backend/src/server/api/stream/ChannelsService.ts6
-rw-r--r--packages/backend/src/server/api/stream/Connection.ts17
-rw-r--r--packages/backend/src/server/api/stream/channel.ts6
-rw-r--r--packages/backend/src/server/api/stream/channels/chat-room.ts78
-rw-r--r--packages/backend/src/server/api/stream/channels/chat-user.ts78
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts63
-rw-r--r--packages/backend/src/server/web/boot.embed.js11
-rw-r--r--packages/backend/src/server/web/boot.js38
-rw-r--r--packages/backend/src/server/web/error.css107
-rw-r--r--packages/backend/src/server/web/error.js40
-rw-r--r--packages/backend/src/server/web/views/error.pug46
-rw-r--r--packages/backend/src/server/web/views/oauth.pug2
-rw-r--r--packages/backend/src/types.ts31
-rw-r--r--packages/backend/test-federation/compose.tpl.yml7
-rw-r--r--packages/backend/test-federation/compose.yml21
-rw-r--r--packages/backend/test-federation/test/abuse-report.test.ts2
-rw-r--r--packages/backend/test-federation/test/note.test.ts106
-rw-r--r--packages/backend/test-federation/test/timeline.test.ts2
-rw-r--r--packages/backend/test-federation/test/user.test.ts7
-rw-r--r--packages/backend/test-federation/test/utils.ts4
-rw-r--r--packages/backend/test-server/.swcrc2
-rw-r--r--packages/backend/test/e2e/clips.ts36
-rw-r--r--packages/backend/test/e2e/fetch-resource.ts40
-rw-r--r--packages/backend/test/e2e/mute.ts22
-rw-r--r--packages/backend/test/e2e/oauth.ts56
-rw-r--r--packages/backend/test/e2e/thread-mute.ts42
-rw-r--r--packages/backend/test/e2e/timelines.ts2
-rw-r--r--packages/backend/test/e2e/users.ts6
-rw-r--r--packages/backend/test/misc/mock-resolver.ts9
-rw-r--r--packages/backend/test/unit/AbuseReportNotificationService.ts6
-rw-r--r--packages/backend/test/unit/FlashService.ts6
-rw-r--r--packages/backend/test/unit/MfmService.ts4
-rw-r--r--packages/backend/test/unit/RelayService.ts24
-rw-r--r--packages/backend/test/unit/RoleService.ts22
-rw-r--r--packages/backend/test/unit/SystemWebhookService.ts2
-rw-r--r--packages/backend/test/unit/UserSearchService.ts2
-rw-r--r--packages/backend/test/unit/UserWebhookService.ts2
-rw-r--r--packages/backend/test/unit/WebhookTestService.ts10
-rw-r--r--packages/backend/test/unit/ap-request.ts110
-rw-r--r--packages/backend/test/unit/entities/UserEntityService.ts2
-rw-r--r--packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts4
-rw-r--r--packages/backend/test/utils.ts6
-rw-r--r--packages/frontend-embed/eslint.config.js1
-rw-r--r--packages/frontend-embed/package.json61
-rw-r--r--packages/frontend-embed/src/components/EmImgWithBlurhash.vue2
-rw-r--r--packages/frontend-embed/src/components/EmMfm.ts3
-rw-r--r--packages/frontend-embed/src/components/EmNotes.vue3
-rw-r--r--packages/frontend-embed/src/components/EmPagination.vue20
-rw-r--r--packages/frontend-embed/src/components/EmReactionsViewer.vue12
-rw-r--r--packages/frontend-embed/src/pages/not-found.vue4
-rw-r--r--packages/frontend-embed/src/post-message.ts2
-rw-r--r--packages/frontend-embed/tsconfig.json1
-rw-r--r--packages/frontend-shared/eslint.config.js5
-rw-r--r--packages/frontend-shared/js/config.ts1
-rw-r--r--packages/frontend-shared/js/const.ts10
-rw-r--r--packages/frontend-shared/js/embed-page.ts2
-rw-r--r--packages/frontend-shared/js/emojilist.ts4
-rw-r--r--packages/frontend-shared/js/i18n.ts1
-rw-r--r--packages/frontend-shared/js/scroll.ts13
-rw-r--r--packages/frontend-shared/package.json18
-rw-r--r--packages/frontend-shared/themes/_dark.json58
-rw-r--r--packages/frontend-shared/themes/_light.json58
-rw-r--r--packages/frontend-shared/themes/d-astro.json58
-rw-r--r--packages/frontend-shared/themes/d-u0.json58
-rw-r--r--packages/frontend-shared/themes/l-u0.json58
-rw-r--r--packages/frontend-shared/themes/l-vivid.json58
-rw-r--r--packages/frontend-shared/tsconfig.json1
-rw-r--r--packages/frontend/.storybook/charts.ts3
-rw-r--r--packages/frontend/.storybook/fake-utils.ts2
-rw-r--r--packages/frontend/.storybook/generate.tsx62
-rw-r--r--packages/frontend/.storybook/main.ts4
-rw-r--r--packages/frontend/.storybook/preview-head.html2
-rw-r--r--packages/frontend/.storybook/preview.ts28
-rw-r--r--packages/frontend/@types/theme.d.ts2
-rw-r--r--packages/frontend/assets/bell_3d.pngbin0 -> 25849 bytes
-rw-r--r--packages/frontend/assets/cloud_3d.pngbin0 -> 18854 bytes
-rw-r--r--packages/frontend/assets/desktop_computer_3d.pngbin0 -> 35207 bytes
-rw-r--r--packages/frontend/assets/electric_plug_3d.pngbin0 -> 15948 bytes
-rw-r--r--packages/frontend/assets/gear_3d.pngbin0 -> 39892 bytes
-rw-r--r--packages/frontend/assets/link_3d.pngbin0 -> 30987 bytes
-rw-r--r--packages/frontend/assets/locked_with_key_3d.pngbin0 -> 43609 bytes
-rw-r--r--packages/frontend/assets/mens_room_3d.pngbin0 -> 31451 bytes
-rw-r--r--packages/frontend/assets/musical_note_3d.pngbin0 -> 31724 bytes
-rw-r--r--packages/frontend/assets/package_3d.pngbin0 -> 31112 bytes
-rw-r--r--packages/frontend/assets/prohibited_3d.pngbin0 -> 40839 bytes
-rw-r--r--packages/frontend/assets/speaker_high_volume_3d.pngbin0 -> 28034 bytes
-rw-r--r--packages/frontend/assets/unlocked_3d.pngbin0 -> 30397 bytes
-rw-r--r--packages/frontend/eslint.config.js46
-rw-r--r--packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts20
-rw-r--r--packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts2
-rw-r--r--packages/frontend/lib/vite-plugin-create-search-index.ts1521
-rw-r--r--packages/frontend/package.json135
-rw-r--r--packages/frontend/scripts/generate-search-index.ts15
-rw-r--r--packages/frontend/src/_boot_.ts4
-rw-r--r--packages/frontend/src/account.ts393
-rw-r--r--packages/frontend/src/accounts.ts339
-rw-r--r--packages/frontend/src/aiscript/api.ts (renamed from packages/frontend/src/scripts/aiscript/api.ts)6
-rw-r--r--packages/frontend/src/aiscript/common.ts (renamed from packages/frontend/src/scripts/aiscript/common.ts)3
-rw-r--r--packages/frontend/src/aiscript/ui.ts (renamed from packages/frontend/src/scripts/aiscript/ui.ts)3
-rw-r--r--packages/frontend/src/analytics.ts107
-rw-r--r--packages/frontend/src/boot/common.ts184
-rw-r--r--packages/frontend/src/boot/main-boot.ts118
-rw-r--r--packages/frontend/src/boot/sub-boot.ts2
-rw-r--r--packages/frontend/src/cache.ts4
-rw-r--r--packages/frontend/src/components/MkAbuseReport.vue11
-rw-r--r--packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAbuseReportWindow.vue4
-rw-r--r--packages/frontend/src/components/MkAccountMoved.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAccountMoved.vue2
-rw-r--r--packages/frontend/src/components/MkAchievements.stories.impl.ts4
-rw-r--r--packages/frontend/src/components/MkAchievements.vue4
-rw-r--r--packages/frontend/src/components/MkAnalogClock.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAnalogClock.vue4
-rw-r--r--packages/frontend/src/components/MkAnimBg.vue6
-rw-r--r--packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAnnouncementDialog.vue13
-rw-r--r--packages/frontend/src/components/MkAntennaEditor.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAntennaEditor.vue6
-rw-r--r--packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAntennaEditorDialog.vue4
-rw-r--r--packages/frontend/src/components/MkAsUi.vue5
-rw-r--r--packages/frontend/src/components/MkAuthConfirm.vue13
-rw-r--r--packages/frontend/src/components/MkAutocomplete.stories.impl.ts4
-rw-r--r--packages/frontend/src/components/MkAutocomplete.vue36
-rw-r--r--packages/frontend/src/components/MkAvatars.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAvatars.vue2
-rw-r--r--packages/frontend/src/components/MkButton.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkButton.vue36
-rw-r--r--packages/frontend/src/components/MkCaptcha.vue16
-rw-r--r--packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkChannelFollowButton.vue2
-rw-r--r--packages/frontend/src/components/MkChannelList.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkChannelList.vue5
-rw-r--r--packages/frontend/src/components/MkChannelPreview.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkChart.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkChart.vue30
-rw-r--r--packages/frontend/src/components/MkChartLegend.vue3
-rw-r--r--packages/frontend/src/components/MkClickerGame.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkClickerGame.vue4
-rw-r--r--packages/frontend/src/components/MkClipPreview.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkClipPreview.vue2
-rw-r--r--packages/frontend/src/components/MkCode.core.vue8
-rw-r--r--packages/frontend/src/components/MkCode.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCode.vue9
-rw-r--r--packages/frontend/src/components/MkCodeEditor.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCodeEditor.vue4
-rw-r--r--packages/frontend/src/components/MkCodeInline.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkColorInput.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkColorInput.vue4
-rw-r--r--packages/frontend/src/components/MkContainer.vue18
-rw-r--r--packages/frontend/src/components/MkContextMenu.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkContextMenu.vue20
-rw-r--r--packages/frontend/src/components/MkCropperDialog.stories.impl.ts6
-rw-r--r--packages/frontend/src/components/MkCropperDialog.vue20
-rw-r--r--packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue10
-rw-r--r--packages/frontend/src/components/MkCwButton.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCwButton.vue2
-rw-r--r--packages/frontend/src/components/MkDateSeparatedList.vue31
-rw-r--r--packages/frontend/src/components/MkDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDialog.vue9
-rw-r--r--packages/frontend/src/components/MkDigitalClock.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDigitalClock.vue2
-rw-r--r--packages/frontend/src/components/MkDisableSection.vue (renamed from idea/MkDisableSection.vue)5
-rw-r--r--packages/frontend/src/components/MkDonation.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDrive.file.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDrive.file.vue8
-rw-r--r--packages/frontend/src/components/MkDrive.folder.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDrive.folder.vue20
-rw-r--r--packages/frontend/src/components/MkDrive.navFolder.vue2
-rw-r--r--packages/frontend/src/components/MkDrive.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDrive.vue20
-rw-r--r--packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDriveSelectDialog.vue4
-rw-r--r--packages/frontend/src/components/MkEmbedCodeGenDialog.vue15
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.section.vue6
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.vue48
-rw-r--r--packages/frontend/src/components/MkEmojiPickerDialog.vue14
-rw-r--r--packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkExtensionInstaller.vue128
-rw-r--r--packages/frontend/src/components/MkFeatureBanner.vue43
-rw-r--r--packages/frontend/src/components/MkFileCaptionEditWindow.vue4
-rw-r--r--packages/frontend/src/components/MkFlashPreview.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkFoldableSection.vue16
-rw-r--r--packages/frontend/src/components/MkFolder.vue18
-rw-r--r--packages/frontend/src/components/MkFollowButton.vue16
-rw-r--r--packages/frontend/src/components/MkFormDialog.file.vue4
-rw-r--r--packages/frontend/src/components/MkFormDialog.vue8
-rw-r--r--packages/frontend/src/components/MkFukidashi.vue9
-rw-r--r--packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkGalleryPostPreview.vue4
-rw-r--r--packages/frontend/src/components/MkHeatmap.vue18
-rw-r--r--packages/frontend/src/components/MkHorizontalSwipe.vue23
-rw-r--r--packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts40
-rw-r--r--packages/frontend/src/components/MkImgPreviewDialog.vue58
-rw-r--r--packages/frontend/src/components/MkImgWithBlurhash.vue60
-rw-r--r--packages/frontend/src/components/MkInfo.vue2
-rw-r--r--packages/frontend/src/components/MkInput.vue16
-rw-r--r--packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts5
-rw-r--r--packages/frontend/src/components/MkInstanceCardMini.vue4
-rw-r--r--packages/frontend/src/components/MkInstanceStats.vue19
-rw-r--r--packages/frontend/src/components/MkInstanceTicker.vue5
-rw-r--r--packages/frontend/src/components/MkInviteCode.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkInviteCode.vue5
-rw-r--r--packages/frontend/src/components/MkKeyValue.vue5
-rw-r--r--packages/frontend/src/components/MkLaunchPad.vue10
-rw-r--r--packages/frontend/src/components/MkLink.vue4
-rw-r--r--packages/frontend/src/components/MkMediaAudio.vue66
-rw-r--r--packages/frontend/src/components/MkMediaBanner.vue4
-rw-r--r--packages/frontend/src/components/MkMediaImage.vue67
-rw-r--r--packages/frontend/src/components/MkMediaList.vue29
-rw-r--r--packages/frontend/src/components/MkMediaRange.vue5
-rw-r--r--packages/frontend/src/components/MkMediaVideo.vue76
-rw-r--r--packages/frontend/src/components/MkMention.vue12
-rw-r--r--packages/frontend/src/components/MkMenu.child.vue4
-rw-r--r--packages/frontend/src/components/MkMenu.vue109
-rw-r--r--packages/frontend/src/components/MkMiniChart.vue2
-rw-r--r--packages/frontend/src/components/MkModal.vue22
-rw-r--r--packages/frontend/src/components/MkModalWindow.vue8
-rw-r--r--packages/frontend/src/components/MkNote.vue103
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue94
-rw-r--r--packages/frontend/src/components/MkNoteHeader.vue6
-rw-r--r--packages/frontend/src/components/MkNoteMediaGrid.vue74
-rw-r--r--packages/frontend/src/components/MkNoteSub.vue26
-rw-r--r--packages/frontend/src/components/MkNotes.vue9
-rw-r--r--packages/frontend/src/components/MkNotification.vue22
-rw-r--r--packages/frontend/src/components/MkNotificationSelectWindow.vue9
-rw-r--r--packages/frontend/src/components/MkNotifications.vue16
-rw-r--r--packages/frontend/src/components/MkOmit.vue4
-rw-r--r--packages/frontend/src/components/MkPageWindow.vue80
-rw-r--r--packages/frontend/src/components/MkPagination.vue63
-rw-r--r--packages/frontend/src/components/MkPasswordDialog.vue12
-rw-r--r--packages/frontend/src/components/MkPolkadots.vue40
-rw-r--r--packages/frontend/src/components/MkPoll.vue8
-rw-r--r--packages/frontend/src/components/MkPollEditor.vue4
-rw-r--r--packages/frontend/src/components/MkPopupMenu.vue4
-rw-r--r--packages/frontend/src/components/MkPostForm.TextCounter.vue95
-rw-r--r--packages/frontend/src/components/MkPostForm.vue224
-rw-r--r--packages/frontend/src/components/MkPostFormAttaches.vue37
-rw-r--r--packages/frontend/src/components/MkPostFormDialog.vue26
-rw-r--r--packages/frontend/src/components/MkPreferenceContainer.vue103
-rw-r--r--packages/frontend/src/components/MkPreview.vue2
-rw-r--r--packages/frontend/src/components/MkPullToRefresh.vue8
-rw-r--r--packages/frontend/src/components/MkPushNotificationAllowButton.vue5
-rw-r--r--packages/frontend/src/components/MkRadios.vue3
-rw-r--r--packages/frontend/src/components/MkRange.vue16
-rw-r--r--packages/frontend/src/components/MkReactionIcon.vue6
-rw-r--r--packages/frontend/src/components/MkReactionsViewer.reaction.vue36
-rw-r--r--packages/frontend/src/components/MkReactionsViewer.vue15
-rw-r--r--packages/frontend/src/components/MkRemoteCaution.vue4
-rw-r--r--packages/frontend/src/components/MkRetentionHeatmap.vue18
-rw-r--r--packages/frontend/src/components/MkRetentionLineChart.vue20
-rw-r--r--packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkRoleSelectDialog.vue6
-rw-r--r--packages/frontend/src/components/MkSelect.vue131
-rw-r--r--packages/frontend/src/components/MkSignin.input.vue2
-rw-r--r--packages/frontend/src/components/MkSignin.vue12
-rw-r--r--packages/frontend/src/components/MkSigninDialog.vue6
-rw-r--r--packages/frontend/src/components/MkSignupDialog.form.vue9
-rw-r--r--packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkSignupDialog.vue4
-rw-r--r--packages/frontend/src/components/MkSortOrderEditor.define.ts4
-rw-r--r--packages/frontend/src/components/MkSortOrderEditor.vue4
-rw-r--r--packages/frontend/src/components/MkSparkle.vue4
-rw-r--r--packages/frontend/src/components/MkSuperMenu.vue229
-rw-r--r--packages/frontend/src/components/MkSwitch.button.vue3
-rw-r--r--packages/frontend/src/components/MkSwitch.vue3
-rw-r--r--packages/frontend/src/components/MkSystemWebhookEditor.vue14
-rw-r--r--packages/frontend/src/components/MkTagCloud.vue12
-rw-r--r--packages/frontend/src/components/MkTagItem.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkTextarea.vue9
-rw-r--r--packages/frontend/src/components/MkThemePreview.vue96
-rw-r--r--packages/frontend/src/components/MkTimeline.vue38
-rw-r--r--packages/frontend/src/components/MkToast.vue10
-rw-r--r--packages/frontend/src/components/MkTokenGenerateWindow.vue10
-rw-r--r--packages/frontend/src/components/MkTooltip.vue16
-rw-r--r--packages/frontend/src/components/MkTutorialDialog.Note.vue2
-rw-r--r--packages/frontend/src/components/MkTutorialDialog.Sensitive.vue2
-rw-r--r--packages/frontend/src/components/MkTutorialDialog.vue8
-rw-r--r--packages/frontend/src/components/MkUpdated.vue8
-rw-r--r--packages/frontend/src/components/MkUrlPreview.vue20
-rw-r--r--packages/frontend/src/components/MkUrlPreviewPopup.vue4
-rw-r--r--packages/frontend/src/components/MkUserAnnouncementEditDialog.vue4
-rw-r--r--packages/frontend/src/components/MkUserCardMini.vue2
-rw-r--r--packages/frontend/src/components/MkUserInfo.vue10
-rw-r--r--packages/frontend/src/components/MkUserList.vue5
-rw-r--r--packages/frontend/src/components/MkUserPopup.vue22
-rw-r--r--packages/frontend/src/components/MkUserSelectDialog.vue20
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Follow.vue3
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Privacy.vue2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Profile.vue6
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.User.vue2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.vue21
-rw-r--r--packages/frontend/src/components/MkVisibilityPicker.vue4
-rw-r--r--packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue18
-rw-r--r--packages/frontend/src/components/MkVisitorDashboard.vue2
-rw-r--r--packages/frontend/src/components/MkWaitingDialog.vue4
-rw-r--r--packages/frontend/src/components/MkWindow.vue18
-rw-r--r--packages/frontend/src/components/MkYouTubePlayer.vue8
-rw-r--r--packages/frontend/src/components/global/MkA.stories.impl.ts4
-rw-r--r--packages/frontend/src/components/global/MkA.vue12
-rw-r--r--packages/frontend/src/components/global/MkAcct.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkAcct.vue3
-rw-r--r--packages/frontend/src/components/global/MkAd.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkAd.vue13
-rw-r--r--packages/frontend/src/components/global/MkAvatar.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkAvatar.vue17
-rw-r--r--packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkCustomEmoji.vue19
-rw-r--r--packages/frontend/src/components/global/MkEllipsis.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkEmoji.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkEmoji.vue13
-rw-r--r--packages/frontend/src/components/global/MkError.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkError.stories.meta.ts2
-rw-r--r--packages/frontend/src/components/global/MkError.vue6
-rw-r--r--packages/frontend/src/components/global/MkFooterSpacer.vue32
-rw-r--r--packages/frontend/src/components/global/MkLazy.vue4
-rw-r--r--packages/frontend/src/components/global/MkLoading.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkMfm.stories.impl.ts4
-rw-r--r--packages/frontend/src/components/global/MkMfm.ts23
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.tabs.vue30
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.vue29
-rw-r--r--packages/frontend/src/components/global/MkSpacer.vue2
-rw-r--r--packages/frontend/src/components/global/MkStickyContainer.vue13
-rw-r--r--packages/frontend/src/components/global/MkTime.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkUrl.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkUrl.vue4
-rw-r--r--packages/frontend/src/components/global/MkUserName.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/NestedRouterView.vue60
-rw-r--r--packages/frontend/src/components/global/PageWithAnimBg.vue21
-rw-r--r--packages/frontend/src/components/global/PageWithHeader.vue44
-rw-r--r--packages/frontend/src/components/global/RouterView.vue142
-rw-r--r--packages/frontend/src/components/global/SearchKeyword.vue14
-rw-r--r--packages/frontend/src/components/global/SearchLabel.vue14
-rw-r--r--packages/frontend/src/components/global/SearchMarker.vue116
-rw-r--r--packages/frontend/src/components/global/StackingRouterView.vue243
-rw-r--r--packages/frontend/src/components/grid/MkDataCell.vue27
-rw-r--r--packages/frontend/src/components/grid/MkDataRow.vue7
-rw-r--r--packages/frontend/src/components/grid/MkGrid.stories.impl.ts8
-rw-r--r--packages/frontend/src/components/grid/MkGrid.vue21
-rw-r--r--packages/frontend/src/components/grid/MkHeaderCell.vue5
-rw-r--r--packages/frontend/src/components/grid/MkHeaderRow.vue7
-rw-r--r--packages/frontend/src/components/grid/MkNumberCell.vue3
-rw-r--r--packages/frontend/src/components/grid/cell-validators.ts14
-rw-r--r--packages/frontend/src/components/grid/cell.ts18
-rw-r--r--packages/frontend/src/components/grid/column.ts14
-rw-r--r--packages/frontend/src/components/grid/grid-event.ts10
-rw-r--r--packages/frontend/src/components/grid/grid-utils.ts16
-rw-r--r--packages/frontend/src/components/grid/grid.ts14
-rw-r--r--packages/frontend/src/components/grid/row.ts16
-rw-r--r--packages/frontend/src/components/index.ts28
-rw-r--r--packages/frontend/src/components/page/page.note.vue2
-rw-r--r--packages/frontend/src/components/page/page.text.vue2
-rw-r--r--packages/frontend/src/custom-emojis.ts4
-rw-r--r--packages/frontend/src/debug.ts3
-rw-r--r--packages/frontend/src/deck.ts353
-rw-r--r--packages/frontend/src/di.ts17
-rw-r--r--packages/frontend/src/directives/adaptive-bg.ts4
-rw-r--r--packages/frontend/src/directives/adaptive-border.ts31
-rw-r--r--packages/frontend/src/directives/anim.ts2
-rw-r--r--packages/frontend/src/directives/appear.ts2
-rw-r--r--packages/frontend/src/directives/click-anime.ts6
-rw-r--r--packages/frontend/src/directives/follow-append.ts2
-rw-r--r--packages/frontend/src/directives/get-size.ts2
-rw-r--r--packages/frontend/src/directives/hotkey.ts8
-rw-r--r--packages/frontend/src/directives/index.ts2
-rw-r--r--packages/frontend/src/directives/panel.ts6
-rw-r--r--packages/frontend/src/directives/ripple.ts2
-rw-r--r--packages/frontend/src/directives/tooltip.ts7
-rw-r--r--packages/frontend/src/directives/user-preview.ts7
-rw-r--r--packages/frontend/src/events.ts2
-rw-r--r--packages/frontend/src/filters/number.ts2
-rw-r--r--packages/frontend/src/i.ts34
-rw-r--r--packages/frontend/src/instance.ts4
-rw-r--r--packages/frontend/src/lib/nirax.ts (renamed from packages/frontend/src/nirax.ts)223
-rw-r--r--packages/frontend/src/lib/pizzax.ts (renamed from packages/frontend/src/pizzax.ts)76
-rw-r--r--packages/frontend/src/local-storage.ts13
-rw-r--r--packages/frontend/src/memory-storage.ts57
-rw-r--r--packages/frontend/src/navbar.ts20
-rw-r--r--packages/frontend/src/os.ts71
-rw-r--r--packages/frontend/src/page.ts (renamed from packages/frontend/src/scripts/page-metadata.ts)16
-rw-r--r--packages/frontend/src/pages/_error_.vue16
-rw-r--r--packages/frontend/src/pages/about.emojis.vue2
-rw-r--r--packages/frontend/src/pages/about.federation.vue3
-rw-r--r--packages/frontend/src/pages/about.overview.vue2
-rw-r--r--packages/frontend/src/pages/about.vue11
-rw-r--r--packages/frontend/src/pages/achievements.vue13
-rw-r--r--packages/frontend/src/pages/admin-file.vue34
-rw-r--r--packages/frontend/src/pages/admin-user.vue53
-rw-r--r--packages/frontend/src/pages/admin/RolesEditorFormula.vue2
-rw-r--r--packages/frontend/src/pages/admin/_header_.vue20
-rw-r--r--packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue9
-rw-r--r--packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue2
-rw-r--r--packages/frontend/src/pages/admin/abuses.vue14
-rw-r--r--packages/frontend/src/pages/admin/ads.vue6
-rw-r--r--packages/frontend/src/pages/admin/announcements.vue6
-rw-r--r--packages/frontend/src/pages/admin/bot-protection.vue6
-rw-r--r--packages/frontend/src/pages/admin/branding.vue6
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue20
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue26
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue21
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts2
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager2.vue4
-rw-r--r--packages/frontend/src/pages/admin/database.vue11
-rw-r--r--packages/frontend/src/pages/admin/email-settings.vue6
-rw-r--r--packages/frontend/src/pages/admin/external-services.vue31
-rw-r--r--packages/frontend/src/pages/admin/federation.vue4
-rw-r--r--packages/frontend/src/pages/admin/files.vue6
-rw-r--r--packages/frontend/src/pages/admin/index.vue17
-rw-r--r--packages/frontend/src/pages/admin/invites.vue13
-rw-r--r--packages/frontend/src/pages/admin/moderation.vue6
-rw-r--r--packages/frontend/src/pages/admin/modlog.ModLog.vue5
-rw-r--r--packages/frontend/src/pages/admin/modlog.vue8
-rw-r--r--packages/frontend/src/pages/admin/object-storage.vue6
-rw-r--r--packages/frontend/src/pages/admin/overview.active-users.vue16
-rw-r--r--packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts2
-rw-r--r--packages/frontend/src/pages/admin/overview.ap-requests.vue20
-rw-r--r--packages/frontend/src/pages/admin/overview.federation.vue7
-rw-r--r--packages/frontend/src/pages/admin/overview.instances.vue6
-rw-r--r--packages/frontend/src/pages/admin/overview.moderators.vue6
-rw-r--r--packages/frontend/src/pages/admin/overview.pie.vue10
-rw-r--r--packages/frontend/src/pages/admin/overview.queue.chart.vue16
-rw-r--r--packages/frontend/src/pages/admin/overview.queue.vue10
-rw-r--r--packages/frontend/src/pages/admin/overview.stats.vue6
-rw-r--r--packages/frontend/src/pages/admin/overview.users.vue6
-rw-r--r--packages/frontend/src/pages/admin/overview.vue10
-rw-r--r--packages/frontend/src/pages/admin/performance.vue8
-rw-r--r--packages/frontend/src/pages/admin/queue.chart.chart.vue16
-rw-r--r--packages/frontend/src/pages/admin/queue.chart.vue12
-rw-r--r--packages/frontend/src/pages/admin/queue.vue18
-rw-r--r--packages/frontend/src/pages/admin/relays.vue6
-rw-r--r--packages/frontend/src/pages/admin/roles.edit.vue8
-rw-r--r--packages/frontend/src/pages/admin/roles.editor.vue22
-rw-r--r--packages/frontend/src/pages/admin/roles.role.vue10
-rw-r--r--packages/frontend/src/pages/admin/roles.vue16
-rw-r--r--packages/frontend/src/pages/admin/security.vue8
-rw-r--r--packages/frontend/src/pages/admin/server-rules.vue6
-rw-r--r--packages/frontend/src/pages/admin/settings.vue40
-rw-r--r--packages/frontend/src/pages/admin/system-webhook.vue6
-rw-r--r--packages/frontend/src/pages/admin/users.vue52
-rw-r--r--packages/frontend/src/pages/ads.vue10
-rw-r--r--packages/frontend/src/pages/announcement.vue26
-rw-r--r--packages/frontend/src/pages/announcements.vue18
-rw-r--r--packages/frontend/src/pages/antenna-timeline.vue21
-rw-r--r--packages/frontend/src/pages/api-console.vue13
-rw-r--r--packages/frontend/src/pages/auth.form.vue4
-rw-r--r--packages/frontend/src/pages/auth.vue16
-rw-r--r--packages/frontend/src/pages/avatar-decoration-edit-dialog.vue6
-rw-r--r--packages/frontend/src/pages/avatar-decorations.vue15
-rw-r--r--packages/frontend/src/pages/channel-editor.vue15
-rw-r--r--packages/frontend/src/pages/channel.vue48
-rw-r--r--packages/frontend/src/pages/channels.vue21
-rw-r--r--packages/frontend/src/pages/chat/XMessage.vue245
-rw-r--r--packages/frontend/src/pages/chat/XRoom.vue41
-rw-r--r--packages/frontend/src/pages/chat/home.home.vue252
-rw-r--r--packages/frontend/src/pages/chat/home.invitations.vue98
-rw-r--r--packages/frontend/src/pages/chat/home.joiningRooms.vue54
-rw-r--r--packages/frontend/src/pages/chat/home.ownedRooms.vue54
-rw-r--r--packages/frontend/src/pages/chat/home.vue60
-rw-r--r--packages/frontend/src/pages/chat/message.vue55
-rw-r--r--packages/frontend/src/pages/chat/room.form.vue333
-rw-r--r--packages/frontend/src/pages/chat/room.info.vue87
-rw-r--r--packages/frontend/src/pages/chat/room.members.vue73
-rw-r--r--packages/frontend/src/pages/chat/room.search.vue68
-rw-r--r--packages/frontend/src/pages/chat/room.vue426
-rw-r--r--packages/frontend/src/pages/clicker.vue9
-rw-r--r--packages/frontend/src/pages/clip.vue20
-rw-r--r--packages/frontend/src/pages/contact.vue9
-rw-r--r--packages/frontend/src/pages/custom-emojis-manager.vue133
-rw-r--r--packages/frontend/src/pages/drive.file.info.vue6
-rw-r--r--packages/frontend/src/pages/drive.file.notes.vue2
-rw-r--r--packages/frontend/src/pages/drive.file.vue8
-rw-r--r--packages/frontend/src/pages/drive.vue4
-rw-r--r--packages/frontend/src/pages/drop-and-fusion.game.vue56
-rw-r--r--packages/frontend/src/pages/drop-and-fusion.vue6
-rw-r--r--packages/frontend/src/pages/emoji-edit-dialog.vue4
-rw-r--r--packages/frontend/src/pages/emojis.emoji.vue9
-rw-r--r--packages/frontend/src/pages/explore.roles.vue2
-rw-r--r--packages/frontend/src/pages/explore.users.vue6
-rw-r--r--packages/frontend/src/pages/explore.vue19
-rw-r--r--packages/frontend/src/pages/favorites.vue11
-rw-r--r--packages/frontend/src/pages/flash/flash-edit.vue13
-rw-r--r--packages/frontend/src/pages/flash/flash-index.vue17
-rw-r--r--packages/frontend/src/pages/flash/flash.vue40
-rw-r--r--packages/frontend/src/pages/follow-requests.vue73
-rw-r--r--packages/frontend/src/pages/gallery/edit.vue15
-rw-r--r--packages/frontend/src/pages/gallery/index.vue17
-rw-r--r--packages/frontend/src/pages/gallery/post.vue28
-rw-r--r--packages/frontend/src/pages/games.vue9
-rw-r--r--packages/frontend/src/pages/install-extensions.vue73
-rw-r--r--packages/frontend/src/pages/instance-info.vue33
-rw-r--r--packages/frontend/src/pages/invite.vue24
-rw-r--r--packages/frontend/src/pages/list.vue19
-rw-r--r--packages/frontend/src/pages/lookup.vue19
-rw-r--r--packages/frontend/src/pages/miauth.vue16
-rw-r--r--packages/frontend/src/pages/my-antennas/create.vue12
-rw-r--r--packages/frontend/src/pages/my-antennas/edit.vue14
-rw-r--r--packages/frontend/src/pages/my-antennas/index.vue11
-rw-r--r--packages/frontend/src/pages/my-clips/index.vue19
-rw-r--r--packages/frontend/src/pages/my-lists/index.vue15
-rw-r--r--packages/frontend/src/pages/my-lists/list.vue27
-rw-r--r--packages/frontend/src/pages/not-found.vue8
-rw-r--r--packages/frontend/src/pages/note.vue27
-rw-r--r--packages/frontend/src/pages/notifications.vue17
-rw-r--r--packages/frontend/src/pages/oauth.vue28
-rw-r--r--packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue2
-rw-r--r--packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue2
-rw-r--r--packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue4
-rw-r--r--packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue7
-rw-r--r--packages/frontend/src/pages/page-editor/page-editor.container.vue4
-rw-r--r--packages/frontend/src/pages/page-editor/page-editor.vue23
-rw-r--r--packages/frontend/src/pages/page.vue40
-rw-r--r--packages/frontend/src/pages/pages.vue17
-rw-r--r--packages/frontend/src/pages/preview.vue4
-rw-r--r--packages/frontend/src/pages/registry.keys.vue11
-rw-r--r--packages/frontend/src/pages/registry.value.vue11
-rw-r--r--packages/frontend/src/pages/registry.vue11
-rw-r--r--packages/frontend/src/pages/reset-password.vue11
-rw-r--r--packages/frontend/src/pages/reversi/game.board.vue12
-rw-r--r--packages/frontend/src/pages/reversi/game.setting.vue8
-rw-r--r--packages/frontend/src/pages/reversi/game.vue12
-rw-r--r--packages/frontend/src/pages/reversi/index.vue14
-rw-r--r--packages/frontend/src/pages/role.vue19
-rw-r--r--packages/frontend/src/pages/scratchpad.vue25
-rw-r--r--packages/frontend/src/pages/search.note.vue328
-rw-r--r--packages/frontend/src/pages/search.stories.impl.ts2
-rw-r--r--packages/frontend/src/pages/search.user.vue15
-rw-r--r--packages/frontend/src/pages/search.vue16
-rw-r--r--packages/frontend/src/pages/settings/2fa.qrdialog.vue15
-rw-r--r--packages/frontend/src/pages/settings/2fa.vue133
-rw-r--r--packages/frontend/src/pages/settings/accessibility.vue173
-rw-r--r--packages/frontend/src/pages/settings/account-data.vue277
-rw-r--r--packages/frontend/src/pages/settings/accounts.vue110
-rw-r--r--packages/frontend/src/pages/settings/api.vue53
-rw-r--r--packages/frontend/src/pages/settings/apps.vue8
-rw-r--r--packages/frontend/src/pages/settings/avatar-decoration.decoration.vue4
-rw-r--r--packages/frontend/src/pages/settings/avatar-decoration.dialog.vue8
-rw-r--r--packages/frontend/src/pages/settings/avatar-decoration.vue70
-rw-r--r--packages/frontend/src/pages/settings/connect.vue112
-rw-r--r--packages/frontend/src/pages/settings/custom-css.vue6
-rw-r--r--packages/frontend/src/pages/settings/deck.vue81
-rw-r--r--packages/frontend/src/pages/settings/drive-cleaner.vue12
-rw-r--r--packages/frontend/src/pages/settings/drive.vue148
-rw-r--r--packages/frontend/src/pages/settings/email.vue59
-rw-r--r--packages/frontend/src/pages/settings/emoji-palette.palette.vue166
-rw-r--r--packages/frontend/src/pages/settings/emoji-palette.vue251
-rw-r--r--packages/frontend/src/pages/settings/emoji-picker.vue316
-rw-r--r--packages/frontend/src/pages/settings/general.vue623
-rw-r--r--packages/frontend/src/pages/settings/import-export.vue263
-rw-r--r--packages/frontend/src/pages/settings/index.vue148
-rw-r--r--packages/frontend/src/pages/settings/migration.vue14
-rw-r--r--packages/frontend/src/pages/settings/mute-block.instance-mute.vue6
-rw-r--r--packages/frontend/src/pages/settings/mute-block.vue284
-rw-r--r--packages/frontend/src/pages/settings/navbar.vue20
-rw-r--r--packages/frontend/src/pages/settings/notifications.vue31
-rw-r--r--packages/frontend/src/pages/settings/other.vue241
-rw-r--r--packages/frontend/src/pages/settings/plugin.install.vue16
-rw-r--r--packages/frontend/src/pages/settings/plugin.vue203
-rw-r--r--packages/frontend/src/pages/settings/preferences-backups.vue489
-rw-r--r--packages/frontend/src/pages/settings/preferences.vue756
-rw-r--r--packages/frontend/src/pages/settings/privacy.vue411
-rw-r--r--packages/frontend/src/pages/settings/profile.vue271
-rw-r--r--packages/frontend/src/pages/settings/roles.vue48
-rw-r--r--packages/frontend/src/pages/settings/security.vue78
-rw-r--r--packages/frontend/src/pages/settings/sounds.sound.vue8
-rw-r--r--packages/frontend/src/pages/settings/sounds.vue117
-rw-r--r--packages/frontend/src/pages/settings/statusbar.statusbar.vue14
-rw-r--r--packages/frontend/src/pages/settings/statusbar.vue14
-rw-r--r--packages/frontend/src/pages/settings/theme.install.vue14
-rw-r--r--packages/frontend/src/pages/settings/theme.manage.vue10
-rw-r--r--packages/frontend/src/pages/settings/theme.vue364
-rw-r--r--packages/frontend/src/pages/settings/webhook.edit.vue8
-rw-r--r--packages/frontend/src/pages/settings/webhook.new.vue4
-rw-r--r--packages/frontend/src/pages/settings/webhook.vue57
-rw-r--r--packages/frontend/src/pages/share.vue17
-rw-r--r--packages/frontend/src/pages/signup-complete.vue14
-rw-r--r--packages/frontend/src/pages/tag.vue25
-rw-r--r--packages/frontend/src/pages/theme-editor.vue29
-rw-r--r--packages/frontend/src/pages/timeline.vue79
-rw-r--r--packages/frontend/src/pages/user-list-timeline.vue23
-rw-r--r--packages/frontend/src/pages/user-tag.vue10
-rw-r--r--packages/frontend/src/pages/user/achievements.vue4
-rw-r--r--packages/frontend/src/pages/user/activity.following.vue23
-rw-r--r--packages/frontend/src/pages/user/activity.notes.vue23
-rw-r--r--packages/frontend/src/pages/user/activity.pv.vue23
-rw-r--r--packages/frontend/src/pages/user/followers.vue11
-rw-r--r--packages/frontend/src/pages/user/following.vue11
-rw-r--r--packages/frontend/src/pages/user/home.stories.impl.ts2
-rw-r--r--packages/frontend/src/pages/user/home.vue23
-rw-r--r--packages/frontend/src/pages/user/index.files.vue2
-rw-r--r--packages/frontend/src/pages/user/index.vue53
-rw-r--r--packages/frontend/src/pages/welcome.entrance.a.vue234
-rw-r--r--packages/frontend/src/pages/welcome.setup.vue10
-rw-r--r--packages/frontend/src/pages/welcome.timeline.note.vue4
-rw-r--r--packages/frontend/src/pages/welcome.timeline.vue8
-rw-r--r--packages/frontend/src/pages/welcome.vue4
-rw-r--r--packages/frontend/src/plugin.ts488
-rw-r--r--packages/frontend/src/pref-migrate.ts142
-rw-r--r--packages/frontend/src/preferences.ts150
-rw-r--r--packages/frontend/src/preferences/def.ts384
-rw-r--r--packages/frontend/src/preferences/manager.ts476
-rw-r--r--packages/frontend/src/preferences/utility.ts226
-rw-r--r--packages/frontend/src/router.definition.ts (renamed from packages/frontend/src/router/definition.ts)85
-rw-r--r--packages/frontend/src/router.ts46
-rw-r--r--packages/frontend/src/router/main.ts198
-rw-r--r--packages/frontend/src/router/supplier.ts30
-rw-r--r--packages/frontend/src/scripts/gen-search-query.ts35
-rw-r--r--packages/frontend/src/scripts/install-plugin.ts135
-rw-r--r--packages/frontend/src/scripts/install-theme.ts37
-rw-r--r--packages/frontend/src/scripts/lookup.ts84
-rw-r--r--packages/frontend/src/server-context.ts2
-rw-r--r--packages/frontend/src/signout.ts55
-rw-r--r--packages/frontend/src/store.ts353
-rw-r--r--packages/frontend/src/stream.ts10
-rw-r--r--packages/frontend/src/style.scss60
-rw-r--r--packages/frontend/src/tab-id.ts11
-rw-r--r--packages/frontend/src/theme-store.ts36
-rw-r--r--packages/frontend/src/theme.ts (renamed from packages/frontend/src/scripts/theme.ts)53
-rw-r--r--packages/frontend/src/timelines.ts2
-rw-r--r--packages/frontend/src/types/menu.ts30
-rw-r--r--packages/frontend/src/ui/_common_/PreferenceRestore.vue64
-rw-r--r--packages/frontend/src/ui/_common_/announcements.vue2
-rw-r--r--packages/frontend/src/ui/_common_/common.ts2
-rw-r--r--packages/frontend/src/ui/_common_/common.vue34
-rw-r--r--packages/frontend/src/ui/_common_/navbar-for-mobile.vue7
-rw-r--r--packages/frontend/src/ui/_common_/navbar.vue162
-rw-r--r--packages/frontend/src/ui/_common_/statusbar-federation.vue4
-rw-r--r--packages/frontend/src/ui/_common_/statusbar-rss.vue2
-rw-r--r--packages/frontend/src/ui/_common_/statusbar-user-list.vue4
-rw-r--r--packages/frontend/src/ui/_common_/statusbars.vue4
-rw-r--r--packages/frontend/src/ui/_common_/stream-indicator.vue6
-rw-r--r--packages/frontend/src/ui/_common_/sw-inject.ts11
-rw-r--r--packages/frontend/src/ui/_common_/upload.vue2
-rw-r--r--packages/frontend/src/ui/classic.header.vue11
-rw-r--r--packages/frontend/src/ui/classic.sidebar.vue18
-rw-r--r--packages/frontend/src/ui/classic.vue58
-rw-r--r--packages/frontend/src/ui/deck.vue108
-rw-r--r--packages/frontend/src/ui/deck/antenna-column.vue27
-rw-r--r--packages/frontend/src/ui/deck/channel-column.vue32
-rw-r--r--packages/frontend/src/ui/deck/column.vue17
-rw-r--r--packages/frontend/src/ui/deck/deck-store.ts331
-rw-r--r--packages/frontend/src/ui/deck/direct-column.vue5
-rw-r--r--packages/frontend/src/ui/deck/list-column.vue35
-rw-r--r--packages/frontend/src/ui/deck/main-column.vue25
-rw-r--r--packages/frontend/src/ui/deck/mentions-column.vue5
-rw-r--r--packages/frontend/src/ui/deck/notifications-column.vue9
-rw-r--r--packages/frontend/src/ui/deck/role-timeline-column.vue25
-rw-r--r--packages/frontend/src/ui/deck/tl-column.vue14
-rw-r--r--packages/frontend/src/ui/deck/tl-note-notification.ts7
-rw-r--r--packages/frontend/src/ui/deck/widgets-column.vue5
-rw-r--r--packages/frontend/src/ui/minimum.vue24
-rw-r--r--packages/frontend/src/ui/universal.vue272
-rw-r--r--packages/frontend/src/ui/universal.widgets.vue26
-rw-r--r--packages/frontend/src/ui/visitor.vue304
-rw-r--r--packages/frontend/src/ui/zen.vue52
-rw-r--r--packages/frontend/src/use/use-chart-tooltip.ts (renamed from packages/frontend/src/scripts/use-chart-tooltip.ts)0
-rw-r--r--packages/frontend/src/use/use-form.ts (renamed from packages/frontend/src/scripts/use-form.ts)3
-rw-r--r--packages/frontend/src/use/use-leave-guard.ts (renamed from packages/frontend/src/scripts/use-leave-guard.ts)2
-rw-r--r--packages/frontend/src/use/use-note-capture.ts (renamed from packages/frontend/src/scripts/use-note-capture.ts)7
-rw-r--r--packages/frontend/src/use/use-tooltip.ts (renamed from packages/frontend/src/scripts/use-tooltip.ts)7
-rw-r--r--packages/frontend/src/utility/achievements.ts (renamed from packages/frontend/src/scripts/achievements.ts)4
-rw-r--r--packages/frontend/src/utility/admin-lookup.ts (renamed from packages/frontend/src/scripts/admin-lookup.ts)2
-rw-r--r--packages/frontend/src/utility/array.ts (renamed from packages/frontend/src/scripts/array.ts)0
-rw-r--r--packages/frontend/src/utility/autocomplete.ts (renamed from packages/frontend/src/scripts/autocomplete.ts)15
-rw-r--r--packages/frontend/src/utility/autogen/settings-search-index.ts937
-rw-r--r--packages/frontend/src/utility/boost-quote.ts (renamed from packages/frontend/src/scripts/boost-quote.ts)0
-rw-r--r--packages/frontend/src/utility/cache.ts (renamed from packages/frontend/src/scripts/cache.ts)0
-rw-r--r--packages/frontend/src/utility/chart-legend.ts (renamed from packages/frontend/src/scripts/chart-legend.ts)2
-rw-r--r--packages/frontend/src/utility/chart-vline.ts (renamed from packages/frontend/src/scripts/chart-vline.ts)2
-rw-r--r--packages/frontend/src/utility/check-animated-mfm.ts (renamed from packages/frontend/src/scripts/check-animated-mfm.ts)0
-rw-r--r--packages/frontend/src/utility/check-permissions.ts (renamed from packages/frontend/src/scripts/check-permissions.ts)2
-rw-r--r--packages/frontend/src/utility/check-reaction-permissions.ts (renamed from packages/frontend/src/scripts/check-reaction-permissions.ts)2
-rw-r--r--packages/frontend/src/utility/check-word-mute.ts (renamed from packages/frontend/src/scripts/check-word-mute.ts)0
-rw-r--r--packages/frontend/src/utility/chiptune2.ts (renamed from packages/frontend/src/scripts/chiptune2.ts)0
-rw-r--r--packages/frontend/src/utility/clear-cache.ts (renamed from packages/frontend/src/scripts/clear-cache.ts)2
-rw-r--r--packages/frontend/src/utility/clicker-game.ts (renamed from packages/frontend/src/scripts/clicker-game.ts)2
-rw-r--r--packages/frontend/src/utility/clone.ts (renamed from packages/frontend/src/scripts/clone.ts)0
-rw-r--r--packages/frontend/src/utility/code-highlighter.ts (renamed from packages/frontend/src/scripts/code-highlighter.ts)32
-rw-r--r--packages/frontend/src/utility/collect-page-vars.ts (renamed from packages/frontend/src/scripts/collect-page-vars.ts)0
-rw-r--r--packages/frontend/src/utility/color.ts (renamed from packages/frontend/src/scripts/color.ts)0
-rw-r--r--packages/frontend/src/utility/confetti.ts (renamed from packages/frontend/src/scripts/confetti.ts)0
-rw-r--r--packages/frontend/src/utility/contains.ts (renamed from packages/frontend/src/scripts/contains.ts)0
-rw-r--r--packages/frontend/src/utility/copy-to-clipboard.ts (renamed from packages/frontend/src/scripts/copy-to-clipboard.ts)10
-rw-r--r--packages/frontend/src/utility/deep-equal.ts40
-rw-r--r--packages/frontend/src/utility/device-kind.ts (renamed from packages/frontend/src/scripts/device-kind.ts)0
-rw-r--r--packages/frontend/src/utility/emoji-picker.ts (renamed from packages/frontend/src/scripts/emoji-picker.ts)14
-rw-r--r--packages/frontend/src/utility/extract-mentions.ts (renamed from packages/frontend/src/scripts/extract-mentions.ts)0
-rw-r--r--packages/frontend/src/utility/extract-url-from-mfm.ts (renamed from packages/frontend/src/scripts/extract-url-from-mfm.ts)2
-rw-r--r--packages/frontend/src/utility/favicon-dot.ts (renamed from packages/frontend/src/scripts/favicon-dot.ts)0
-rw-r--r--packages/frontend/src/utility/file-drop.ts (renamed from packages/frontend/src/scripts/file-drop.ts)2
-rw-r--r--packages/frontend/src/utility/focus-trap.ts (renamed from packages/frontend/src/scripts/focus-trap.ts)6
-rw-r--r--packages/frontend/src/utility/focus.ts (renamed from packages/frontend/src/scripts/focus.ts)6
-rw-r--r--packages/frontend/src/utility/following-feed-utils.ts (renamed from packages/frontend/src/scripts/following-feed-utils.ts)0
-rw-r--r--packages/frontend/src/utility/form.ts (renamed from packages/frontend/src/scripts/form.ts)0
-rw-r--r--packages/frontend/src/utility/format-time-string.ts (renamed from packages/frontend/src/scripts/format-time-string.ts)2
-rw-r--r--packages/frontend/src/utility/fullscreen.ts (renamed from packages/frontend/src/scripts/fullscreen.ts)4
-rw-r--r--packages/frontend/src/utility/get-account-from-id.ts (renamed from packages/frontend/src/scripts/get-account-from-id.ts)2
-rw-r--r--packages/frontend/src/utility/get-appear-note.ts (renamed from packages/frontend/src/scripts/get-appear-note.ts)0
-rw-r--r--packages/frontend/src/utility/get-bg-color.ts (renamed from packages/frontend/src/scripts/get-bg-color.ts)0
-rw-r--r--packages/frontend/src/utility/get-dom-node-or-null.ts (renamed from packages/frontend/src/scripts/get-dom-node-or-null.ts)0
-rw-r--r--packages/frontend/src/utility/get-drive-file-menu.ts (renamed from packages/frontend/src/scripts/get-drive-file-menu.ts)13
-rw-r--r--packages/frontend/src/utility/get-embed-code.ts (renamed from packages/frontend/src/scripts/get-embed-code.ts)7
-rw-r--r--packages/frontend/src/utility/get-note-menu.ts (renamed from packages/frontend/src/scripts/get-note-menu.ts)41
-rw-r--r--packages/frontend/src/utility/get-note-summary.ts (renamed from packages/frontend/src/scripts/get-note-summary.ts)0
-rw-r--r--packages/frontend/src/utility/get-note-versions-menu.ts (renamed from packages/frontend/src/scripts/get-note-versions-menu.ts)0
-rw-r--r--packages/frontend/src/utility/get-user-menu.ts (renamed from packages/frontend/src/scripts/get-user-menu.ts)103
-rw-r--r--packages/frontend/src/utility/get-user-name.ts (renamed from packages/frontend/src/scripts/get-user-name.ts)0
-rw-r--r--packages/frontend/src/utility/hotkey.ts (renamed from packages/frontend/src/scripts/hotkey.ts)8
-rw-r--r--packages/frontend/src/utility/idb-proxy.ts (renamed from packages/frontend/src/scripts/idb-proxy.ts)0
-rw-r--r--packages/frontend/src/utility/idle-render.ts (renamed from packages/frontend/src/scripts/idle-render.ts)0
-rw-r--r--packages/frontend/src/utility/init-chart.ts (renamed from packages/frontend/src/scripts/init-chart.ts)6
-rw-r--r--packages/frontend/src/utility/initialize-sw.ts (renamed from packages/frontend/src/scripts/initialize-sw.ts)0
-rw-r--r--packages/frontend/src/utility/intl-const.ts (renamed from packages/frontend/src/scripts/intl-const.ts)0
-rw-r--r--packages/frontend/src/utility/intl-string.ts97
-rw-r--r--packages/frontend/src/utility/is-device-darkmode.ts (renamed from packages/frontend/src/scripts/is-device-darkmode.ts)0
-rw-r--r--packages/frontend/src/utility/isFfVisibleForMe.ts (renamed from packages/frontend/src/scripts/isFfVisibleForMe.ts)2
-rw-r--r--packages/frontend/src/utility/key-event.ts (renamed from packages/frontend/src/scripts/key-event.ts)14
-rw-r--r--packages/frontend/src/utility/langmap.ts (renamed from packages/frontend/src/scripts/langmap.ts)0
-rw-r--r--packages/frontend/src/utility/libopenmpt/LICENSE (renamed from packages/frontend/src/scripts/libopenmpt/LICENSE)0
-rw-r--r--packages/frontend/src/utility/libopenmpt/libopenmpt.js (renamed from packages/frontend/src/scripts/libopenmpt/libopenmpt.js)0
-rw-r--r--packages/frontend/src/utility/libopenmpt/libopenmpt.wasm (renamed from packages/frontend/src/scripts/libopenmpt/libopenmpt.wasm)bin1101696 -> 1101696 bytes
-rw-r--r--packages/frontend/src/utility/libopenmpt/readme.md (renamed from packages/frontend/src/scripts/libopenmpt/readme.md)0
-rw-r--r--packages/frontend/src/utility/login-id.ts (renamed from packages/frontend/src/scripts/login-id.ts)0
-rw-r--r--packages/frontend/src/utility/lookup.ts84
-rw-r--r--packages/frontend/src/utility/media-has-audio.ts (renamed from packages/frontend/src/scripts/media-has-audio.ts)0
-rw-r--r--packages/frontend/src/utility/media-proxy.ts (renamed from packages/frontend/src/scripts/media-proxy.ts)0
-rw-r--r--packages/frontend/src/utility/merge.ts (renamed from packages/frontend/src/scripts/merge.ts)0
-rw-r--r--packages/frontend/src/utility/mfm-function-picker.ts (renamed from packages/frontend/src/scripts/mfm-function-picker.ts)3
-rw-r--r--packages/frontend/src/utility/misskey-api.ts (renamed from packages/frontend/src/scripts/misskey-api.ts)2
-rw-r--r--packages/frontend/src/utility/navigator.ts (renamed from packages/frontend/src/scripts/navigator.ts)0
-rw-r--r--packages/frontend/src/utility/physics.ts (renamed from packages/frontend/src/scripts/physics.ts)2
-rw-r--r--packages/frontend/src/utility/player-url-transform.ts (renamed from packages/frontend/src/scripts/player-url-transform.ts)0
-rw-r--r--packages/frontend/src/utility/please-login.ts (renamed from packages/frontend/src/scripts/please-login.ts)2
-rw-r--r--packages/frontend/src/utility/popout.ts (renamed from packages/frontend/src/scripts/popout.ts)0
-rw-r--r--packages/frontend/src/utility/popup-position.ts (renamed from packages/frontend/src/scripts/popup-position.ts)0
-rw-r--r--packages/frontend/src/utility/post-message.ts (renamed from packages/frontend/src/scripts/post-message.ts)0
-rw-r--r--packages/frontend/src/utility/random-id.ts15
-rw-r--r--packages/frontend/src/utility/reaction-picker.ts (renamed from packages/frontend/src/scripts/reaction-picker.ts)14
-rw-r--r--packages/frontend/src/utility/reload-ask.ts (renamed from packages/frontend/src/scripts/reload-ask.ts)4
-rw-r--r--packages/frontend/src/utility/sanitize-html.ts (renamed from packages/frontend/src/scripts/sanitize-html.ts)0
-rw-r--r--packages/frontend/src/utility/search-emoji.ts (renamed from packages/frontend/src/scripts/search-emoji.ts)0
-rw-r--r--packages/frontend/src/utility/search-engine-map.ts (renamed from packages/frontend/src/scripts/search-engine-map.ts)0
-rw-r--r--packages/frontend/src/utility/select-file.ts (renamed from packages/frontend/src/scripts/select-file.ts)16
-rw-r--r--packages/frontend/src/utility/show-moved-dialog.ts (renamed from packages/frontend/src/scripts/show-moved-dialog.ts)2
-rw-r--r--packages/frontend/src/utility/show-suspended-dialog.ts (renamed from packages/frontend/src/scripts/show-suspended-dialog.ts)0
-rw-r--r--packages/frontend/src/utility/show-system-account-dialog.ts (renamed from packages/frontend/src/scripts/show-system-account-dialog.ts)0
-rw-r--r--packages/frontend/src/utility/shuffle.ts (renamed from packages/frontend/src/scripts/shuffle.ts)0
-rw-r--r--packages/frontend/src/utility/snowfall-effect.ts (renamed from packages/frontend/src/scripts/snowfall-effect.ts)6
-rw-r--r--packages/frontend/src/utility/sound.ts (renamed from packages/frontend/src/scripts/sound.ts)28
-rw-r--r--packages/frontend/src/utility/sticky-sidebar.ts (renamed from packages/frontend/src/scripts/sticky-sidebar.ts)2
-rw-r--r--packages/frontend/src/utility/stream-mock.ts (renamed from packages/frontend/src/scripts/stream-mock.ts)6
-rw-r--r--packages/frontend/src/utility/test-utils.ts (renamed from packages/frontend/src/scripts/test-utils.ts)0
-rw-r--r--packages/frontend/src/utility/theme-editor.ts (renamed from packages/frontend/src/scripts/theme-editor.ts)3
-rw-r--r--packages/frontend/src/utility/time.ts (renamed from packages/frontend/src/scripts/time.ts)0
-rw-r--r--packages/frontend/src/utility/timezones.ts (renamed from packages/frontend/src/scripts/timezones.ts)0
-rw-r--r--packages/frontend/src/utility/touch.ts (renamed from packages/frontend/src/scripts/touch.ts)2
-rw-r--r--packages/frontend/src/utility/unison-reload.ts (renamed from packages/frontend/src/scripts/unison-reload.ts)4
-rw-r--r--packages/frontend/src/utility/upload.ts (renamed from packages/frontend/src/scripts/upload.ts)12
-rw-r--r--packages/frontend/src/utility/upload/compress-config.ts (renamed from packages/frontend/src/scripts/upload/compress-config.ts)0
-rw-r--r--packages/frontend/src/utility/upload/isWebpSupported.ts (renamed from packages/frontend/src/scripts/upload/isWebpSupported.ts)2
-rw-r--r--packages/frontend/src/utility/warning-external-website.ts (renamed from packages/frontend/src/scripts/warning-external-website.ts)0
-rw-r--r--packages/frontend/src/widgets/WidgetActivity.vue9
-rw-r--r--packages/frontend/src/widgets/WidgetAichan.vue9
-rw-r--r--packages/frontend/src/widgets/WidgetAiscript.vue9
-rw-r--r--packages/frontend/src/widgets/WidgetAiscriptApp.vue15
-rw-r--r--packages/frontend/src/widgets/WidgetBirthdayFollowings.vue11
-rw-r--r--packages/frontend/src/widgets/WidgetButton.vue9
-rw-r--r--packages/frontend/src/widgets/WidgetCalendar.vue7
-rw-r--r--packages/frontend/src/widgets/WidgetClicker.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetClock.vue7
-rw-r--r--packages/frontend/src/widgets/WidgetDigitalClock.vue7
-rw-r--r--packages/frontend/src/widgets/WidgetFederation.vue15
-rw-r--r--packages/frontend/src/widgets/WidgetInstanceCloud.vue17
-rw-r--r--packages/frontend/src/widgets/WidgetInstanceInfo.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetJobQueue.vue13
-rw-r--r--packages/frontend/src/widgets/WidgetMemo.vue13
-rw-r--r--packages/frontend/src/widgets/WidgetNotifications.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetOnlineUsers.vue7
-rw-r--r--packages/frontend/src/widgets/WidgetPhotos.vue13
-rw-r--r--packages/frontend/src/widgets/WidgetPostForm.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetProfile.vue7
-rw-r--r--packages/frontend/src/widgets/WidgetRss.vue13
-rw-r--r--packages/frontend/src/widgets/WidgetRssTicker.vue9
-rw-r--r--packages/frontend/src/widgets/WidgetSlideshow.vue15
-rw-r--r--packages/frontend/src/widgets/WidgetTimeline.vue7
-rw-r--r--packages/frontend/src/widgets/WidgetTrends.vue13
-rw-r--r--packages/frontend/src/widgets/WidgetUnixClock.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetUserList.vue7
-rw-r--r--packages/frontend/src/widgets/index.ts3
-rw-r--r--packages/frontend/src/widgets/server-metric/index.vue7
-rw-r--r--packages/frontend/src/widgets/widget.ts4
-rw-r--r--packages/frontend/test/aiscript/api.test.ts8
-rw-r--r--packages/frontend/test/aiscript/common.test.ts2
-rw-r--r--packages/frontend/test/aiscript/ui.test.ts4
-rw-r--r--packages/frontend/test/autocomplete.test.ts2
-rw-r--r--packages/frontend/test/emoji.test.ts6
-rw-r--r--packages/frontend/test/home.test.ts4
-rw-r--r--packages/frontend/test/i18n.test.ts2
-rw-r--r--packages/frontend/test/init.ts10
-rw-r--r--packages/frontend/test/intl-string.test.ts142
-rw-r--r--packages/frontend/test/note.test.ts2
-rw-r--r--packages/frontend/test/scroll.test.ts12
-rw-r--r--packages/frontend/test/url-preview.test.ts7
-rw-r--r--packages/frontend/tsconfig.json1
-rw-r--r--packages/frontend/vite-node.config.ts3
-rw-r--r--packages/frontend/vite.config.ts25
-rw-r--r--packages/misskey-bubble-game/package.json20
-rw-r--r--packages/misskey-js/.swcrc2
-rw-r--r--packages/misskey-js/README.md10
-rw-r--r--packages/misskey-js/eslint.config.js16
-rw-r--r--packages/misskey-js/etc/misskey-js.api.md422
-rw-r--r--packages/misskey-js/generator/eslint.config.js6
-rw-r--r--packages/misskey-js/generator/package.json14
-rw-r--r--packages/misskey-js/generator/src/generator.ts35
-rw-r--r--packages/misskey-js/generator/tsconfig.json2
-rw-r--r--packages/misskey-js/package.json20
-rw-r--r--packages/misskey-js/src/api.types.ts8
-rw-r--r--packages/misskey-js/src/autogen/apiClientJSDoc.ts264
-rw-r--r--packages/misskey-js/src/autogen/endpoint.ts72
-rw-r--r--packages/misskey-js/src/autogen/entities.ts46
-rw-r--r--packages/misskey-js/src/autogen/models.ts5
-rw-r--r--packages/misskey-js/src/autogen/types.ts4886
-rw-r--r--packages/misskey-js/src/consts.ts6
-rw-r--r--packages/misskey-js/src/entities.ts6
-rw-r--r--packages/misskey-js/src/streaming.ts6
-rw-r--r--packages/misskey-js/src/streaming.types.ts7
-rw-r--r--packages/misskey-js/test/streaming.ts38
-rw-r--r--packages/misskey-reversi/package.json16
-rw-r--r--packages/sw/package.json12
-rw-r--r--pnpm-lock.yaml10258
-rw-r--r--pnpm-workspace.yaml39
-rw-r--r--renovate.json588
-rw-r--r--scripts/build-assets.mjs3
-rw-r--r--scripts/dependency-patches/re2.patch13
-rw-r--r--scripts/tarball.mjs2
-rw-r--r--sharkey-locales/en-US.yml4
1065 files changed, 32761 insertions, 19900 deletions
diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml
index 9e4961c325..cf3786120d 100644
--- a/.config/cypress-devcontainer.yml
+++ b/.config/cypress-devcontainer.yml
@@ -265,6 +265,11 @@ allowedPrivateNetworks: [
'127.0.0.1/32'
]
+# Disable automatic redirect for ActivityPub object lookup. (default: false)
+# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
+# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
+#disallowExternalApRedirect: true
+
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index acbaec8023..1992c87f3d 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -1,5 +1,5 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# Misskey configuration
+# Sharkey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌──────────────────────────────┐
@@ -9,14 +9,14 @@
# First of all, let me tell you a story that may possibly be
# boring to you and possibly important to you.
#
-# Misskey is licensed under the AGPLv3 license. This license is
+# Sharkey is licensed under the AGPLv3 license. This license is
# known to be often misunderstood. Please read the following
# instructions carefully and select the appropriate option so
# that you do not negligently cause a license violation.
#
# --------
-# Option 1: If you host Misskey AS-IS (without any changes to
+# Option 1: If you host Sharkey AS-IS (without any changes to
# the source code. forks are not included).
#
# Step 1: Congratulations! You don't need to do anything.
@@ -28,8 +28,8 @@
# this repository. Strictly speaking, it doesn't have
# to be a Git repository, but you'll probably use Git!
#
-# Step 1: Build and run the Misskey server first.
-# Step 2: Open <https://your.misskey.example/admin/settings> in
+# Step 1: Build and run the Sharkey server first.
+# Step 2: Open <https://your.sharkey.example/admin/settings> in
# your browser with the administrator account.
# Step 3: Enter the URL of your Git repository in the
# "Repository URL" field.
@@ -37,7 +37,7 @@
# --------
# Option 3: If neither of the above applies to you.
# (In this case, the source code should be published
-# on the Misskey interface. IT IS NOT ENOUGH TO
+# on the Sharkey interface. IT IS NOT ENOUGH TO
# DISCLOSE THE SOURCE CODE WHEN A USER REQUESTS IT BY
# E-MAIL OR OTHER MEANS. If you are not satisfied
# with this, it is recommended that you read the
@@ -73,11 +73,11 @@ url: https://example.tld/
#───┘ Port and TLS settings └───────────────────────────────────
#
-# Misskey requires a reverse proxy to support HTTPS connections.
+# Sharkey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
-# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
+# | User | ---> || Proxy (443) | ---> | Sharkey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
@@ -85,7 +85,7 @@ url: https://example.tld/
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
-# The port that your Misskey server should listen on.
+# The port that your Sharkey server should listen on.
port: 3000
# ┌──────────────────────────┐
@@ -336,6 +336,11 @@ attachLdSignatureForRelays: true
#customMOTD: ['Hello World', 'The sharks rule all', 'Shonks']
+# Disable automatic redirect for ActivityPub object lookup. (default: false)
+# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
+# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
+#disallowExternalApRedirect: true
+
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
diff --git a/.config/example.yml b/.config/example.yml
index f3caa0d676..dd24981c1f 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -1,5 +1,5 @@
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-# Misskey configuration
+# Sharkey configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ┌──────────────────────────────┐
@@ -9,14 +9,14 @@
# First of all, let me tell you a story that may possibly be
# boring to you and possibly important to you.
#
-# Misskey is licensed under the AGPLv3 license. This license is
+# Sharkey is licensed under the AGPLv3 license. This license is
# known to be often misunderstood. Please read the following
# instructions carefully and select the appropriate option so
# that you do not negligently cause a license violation.
#
# --------
-# Option 1: If you host Misskey AS-IS (without any changes to
+# Option 1: If you host Sharkey AS-IS (without any changes to
# the source code. forks are not included).
#
# Step 1: Congratulations! You don't need to do anything.
@@ -28,8 +28,8 @@
# this repository. Strictly speaking, it doesn't have
# to be a Git repository, but you'll probably use Git!
#
-# Step 1: Build and run the Misskey server first.
-# Step 2: Open <https://your.misskey.example/admin/settings> in
+# Step 1: Build and run the Sharkey server first.
+# Step 2: Open <https://your.sharkey.example/admin/settings> in
# your browser with the administrator account.
# Step 3: Enter the URL of your Git repository in the
# "Repository URL" field.
@@ -37,7 +37,7 @@
# --------
# Option 3: If neither of the above applies to you.
# (In this case, the source code should be published
-# on the Misskey interface. IT IS NOT ENOUGH TO
+# on the Sharkey interface. IT IS NOT ENOUGH TO
# DISCLOSE THE SOURCE CODE WHEN A USER REQUESTS IT BY
# E-MAIL OR OTHER MEANS. If you are not satisfied
# with this, it is recommended that you read the
@@ -65,10 +65,10 @@
# 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.
+# Be sure to change this when you set up Sharkey 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,
+# The provider of the service who sets up Sharkey on behalf of the customer should
+# set this value to something unique when generating the Sharkey config file,
# and provide it to the customer.
#
# setupPassword: example_password_please_change_this_or_you_will_get_hacked
@@ -86,11 +86,11 @@ url: https://example.tld/
#───┘ Port and TLS settings └───────────────────────────────────
#
-# Misskey requires a reverse proxy to support HTTPS connections.
+# Sharkey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
-# | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
+# | User | ---> || Proxy (443) | ---> | Sharkey (3000) ||
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
@@ -98,13 +98,13 @@ url: https://example.tld/
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
-# The port that your Misskey server should listen on.
+# The port that your Sharkey server should listen on.
port: 3000
# the address to bind to, defaults to "every address"
# address: '0.0.0.0'
# You can also use UNIX domain socket.
-# socket: /path/to/misskey.sock
+# socket: /path/to/sharkey.sock
# chmodSocket: '777'
# ┌──────────────────────────┐
@@ -358,7 +358,7 @@ proxyRemoteFiles: true
# Movie Thumbnail Generation URL
# There is no reference implementation.
-# For example, Misskey will point to the following URL:
+# For example, Sharkey will point to the following URL:
# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
#videoThumbnailGenerator: https://example.com
@@ -379,6 +379,11 @@ attachLdSignatureForRelays: true
#customMOTD: ['Hello World', 'The sharks rule all', 'Shonks']
+# Disable automatic redirect for ActivityPub object lookup. (default: false)
+# This is a strong defense against potential impersonation attacks if the viewer instance has inadequate validation.
+# However it will make it impossible for other instances to lookup third-party user and notes through your URL.
+#disallowExternalApRedirect: true
+
# Upload or download file size limits (bytes)
#maxFileSize: 262144000
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 713c2e5fdd..c506c36f6b 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -7,7 +7,9 @@
"ghcr.io/devcontainers/features/node:1": {
"version": "22.11.0"
},
- "ghcr.io/devcontainers-contrib/features/corepack:1": {}
+ "ghcr.io/devcontainers-extra/features/pnpm:2": {
+ "version": "10.6.1"
+ }
},
"forwardPorts": [3000],
"postCreateCommand": "/bin/bash .devcontainer/init.sh",
diff --git a/.devcontainer/init.sh b/.devcontainer/init.sh
index e02a533c15..216292b082 100755
--- a/.devcontainer/init.sh
+++ b/.devcontainer/init.sh
@@ -7,8 +7,6 @@ sudo apt-get update
sudo apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb
git config --global --add safe.directory /workspace
git submodule update --init
-corepack install
-corepack enable
pnpm config set store-dir /home/node/.local/share/pnpm/store
pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml
diff --git a/.npmrc b/.npmrc
index b949431c77..d007b27572 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1,2 +1,4 @@
@transfem-org:registry=https://activitypub.software/api/v4/packages/npm/
engine-strict = true
+save-exact = true
+shell-emulator = true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d36875eb4..280b79d763 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,136 @@
+## 2025.3.2
+
+### General
+- Feat: チャットがリニューアルして復活しました(beta)
+ - 既存のDM機能よりも便利で効率的な実装になっています
+ - チャットを受け付ける相手を制限可能です
+ - 誰でも / フォローユーザーのみ / フォロワーのみ / 相互のみ / 受け付けない から選択できます
+ - 自分からメッセージを送った相手とは上記の設定に関わらずチャット可能です
+ - チャット機能を開放するかどうかをロールで制御可能です
+ - ルームを作成して、複数人でのチャットも可能です
+ - 過去自分が送ったメッセージ・自分に送られたメッセージの検索が可能です
+ - 参加中のルームをミュートして通知が来ないように設定可能です
+ - メッセージにはリアクションも可能です
+- Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
+ - Misskeyネイティブでダッシュボードを実装予定です
+
+### Client
+- Feat: 設定の管理が強化されました
+ - 内部処理が一新され、安定性とパフォーマンスが向上しました
+ - 全てのクライアント設定がエクスポート(バックアップ)/インポート対象に含まれるようになりました
+ - プラグイン、テーマ、クライアントに追加されたすべてのアカウント情報も含まれるようになりました
+ - 自動で設定データをサーバーにバックアップできるように
+ - 設定→設定のプロファイル→自動バックアップ で有効にできます
+ - 新しいデバイスからログインしたり、ブラウザから設定データが消えてしまったときに自動で復元されます(復元をスキップすることも可能)
+ - 任意の設定項目をデバイス間で同期できるように
+ - 設定項目の「...」メニュー→「デバイス間で同期」
+ - 同期をオンにした際にサーバーに保存された値とローカルの値が競合する場合はどちらを優先するか選択できます
+ - 任意の設定項目を初期値にリセットできるように
+ - 設定項目の「...」メニュー→「初期値にリセット」
+ - アカウントごとに設定値が分離される設定とそうでないクライアント設定が混在していた(かつ分離するかどうかを設定不可だった)のを、基本的に一律でクライアント全体に適用されるようにし、個別でアカウントごとに異なる設定を行えるように
+ - 設定項目の「...」メニュー→「アカウントで上書き」をオンにすることで、設定値をそのアカウントでだけ適用するようにできます
+ - ログアウトすると設定データもブラウザから消去されるようになりプライバシーが向上しました
+ - 再度ログインすればサーバーのバックアップから設定データを復元可能です
+ - エクスポートした設定データを他のサーバーでインポートして適用すること(設定の持ち運び)が可能になりました
+- Feat: 画面を重ねて表示するオプションを実装(実験的)
+ - 設定 → その他 → 実験的機能 → Enable stacking router view
+- Enhance: プラグインの管理が強化されました
+ - インストール/アンインストール/設定の変更時にリロード不要になりました
+- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
+- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
+- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
+- Enhance: テーマ設定画面のデザインを改善
+- Enhance: 投稿フォームの設定メニューを改良
+ - 投稿フォームをリセットできるように
+ - 文字数カウントを復活
+- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように
+- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
+
+### Server
+- Enhance 全体的なパフォーマンス向上
+- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
+- Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正
+- Fix: 連合無しモードでも外部から照会可能だった問題を修正
+- Fix: テスト用WebHookのペイロードの`emojis`パラメータが実際のものと異なる問題を修正
+
+## 2025.3.1
+
+### General
+- pnpmをv10に更新
+- Corepackを削除
+
+### Client
+- Feat: 設定の検索を追加(実験的)
+- Enhance: 設定項目の再配置
+
+### Server
+- Fix: DBマイグレーション際にシステムアカウントのユーザーID判定が正しくない問題を修正
+- Fix: user.featured列が状況によってJSON文字列になっていたのを修正
+
+
+## 2025.3.0
+
+### General
+- Enhance: プロキシアカウントをシステムアカウントとして作成するように
+- Enhance: OAuthで外部アプリからロゴが提供されている場合、それを表示できるように
+ 書式は https://indieauth.spec.indieweb.org/20220212/#example-2 に準じます。
+- Fix: システムアカウントが削除できる問題を修正
+
+### Client
+- Enhance: モデレーターがセンシティブ設定を変更する際に確認ダイアログを出すように
+- Enhance: 「UIのアニメーションを減らす」で画面上のエフェクトも減らせるように
+- Enhance: 投稿フォームにおける、メディアの添付可能個数のカウントを反転しました
+ - これまでの表示は`添付可能残り個数/上限数`でしたが、`添付個数/上限数`としました
+- Fix: フォローされたときのメッセージがちらつくことがある問題を修正
+- Fix: 投稿ダイアログがサイズ限界を超えた際にスクロールできない問題を修正
+
+### Server
+- Fix: 特定のケースでActivityPubの処理がデッドロックになることがあるのを修正
+- Fix: S3互換オブジェクトストレージでファイルのアップロードに失敗することがある問題を修正
+ (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/895)
+
+
+## 2025.2.1
+
+### General
+- Feat: アクセストークン発行時に通知するように
+- Feat: 実験的なGoogleAnalyticsサポートを追加
+- 依存関係の更新
+
+### Client
+- Feat: 投稿フォームで画像をプレビュー可能に
+- Enhance: 投稿フォームの「迷惑になる可能性があります」のダイアログを表示する条件においてCWを考慮するように
+- Enhance: アンテナ、リスト等の名前をカラム名のデフォルト値にするように `#13992`
+- Enhance: クライアントエラー画面の多言語対応
+- Enhance: 開発者モードでメニューからファイルIDをコピー出来るように `#15441'
+- Enhance: ノートに埋め込まれたメディアのコンテキストメニューから管理者用のファイル管理画面を開けるように ( #15440 )
+- Enhance: リアクションする際に確認ダイアログを表示できるように
+- Enhance: コントロールパネルのユーザ検索で入力された情報をページ遷移で損なわないように `#15437`
+- Enhance: CWの注釈で入力済みの文字数を表示
+- Enhance: ノート検索ページのデザイン調整
+ (Cherry-picked from https://github.com/taiyme/misskey/pull/273)
+- Fix: ノートページで、クリップ一覧が表示されないことがある問題を修正
+- Fix: コンディショナルロールを手動で割り当てできる導線を削除 `#13529`
+- Fix: 埋め込みプレイヤーから外部ページに移動できない問題を修正
+- Fix: Play の再読込時に UI が以前の状態を引き継いでしまう問題を修正 `#14378`
+- Fix: カスタム絵文字管理画面(beta)にてisSensitive/localOnlyの絞り込みが上手くいかない問題の修正 ( #15445 )
+- Fix: ユーザのサジェスト中に@を入力してもサジェスト結果が消えないように `#14385`
+- Fix: CWの注釈が100文字を超えている場合、ノート投稿ボタンを非アクティブに
+- Fix: テーマ選択で現在のテーマが初期表示されていない問題を修正
+- 翻訳の更新
+
+### Server
+- Enhance: 成り済まし対策として、ActivityPub照会された時にリモートのリダイレクトを拒否できるように (config.disallowExternalApRedirect)
+- Fix: `following/invalidate`でフォロワーを解除しようとしているユーザーの情報を返すように
+- Fix: オブジェクトストレージの設定でPrefixを設定していなかった場合nullまたは空文字になる問題を修正
+- Fix: HTTPプロキシとその除外設定を行った状態でカスタム絵文字の一括インポートをしたとき、除外設定が効かないのを修正( #8766 )
+- Fix: pgroongaでの検索時にはじめのキーワードのみが検索に使用される問題を修正
+ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/886)
+- Fix: メールアドレスの形式が正しくなければ以降の処理を行わないように
+- Fix: `update-meta`でobjectStoragePrefixにS3_SAFEかつURL-safeでない文字列を使えないように
+- Fix: クリップの説明欄を更新する際に空にできない問題を修正
+- Fix: フォロワーではないユーザーにリノートもしくは返信された場合にノートのDeleteアクティビティが送られていない問題を修正
+
## 2025.2.0
### General
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4c0b370146..be2d905bf2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -281,7 +281,6 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
query?: Record<string, string>;
loginRequired?: boolean;
hash?: string;
- globalCacheKey?: string;
children?: RouteDef[];
}
```
diff --git a/assets/about/drive.png b/assets/about/drive.png
deleted file mode 100644
index 16037aae39..0000000000
--- a/assets/about/drive.png
+++ /dev/null
Binary files differ
diff --git a/assets/about/post.png b/assets/about/post.png
deleted file mode 100644
index 3c55f66c56..0000000000
--- a/assets/about/post.png
+++ /dev/null
Binary files differ
diff --git a/assets/about/reaction.png b/assets/about/reaction.png
deleted file mode 100644
index e4e7e06bc0..0000000000
--- a/assets/about/reaction.png
+++ /dev/null
Binary files differ
diff --git a/assets/about/ui.png b/assets/about/ui.png
deleted file mode 100644
index 0601837f4c..0000000000
--- a/assets/about/ui.png
+++ /dev/null
Binary files differ
diff --git a/assets/ss/explore.jpg b/assets/ss/explore.jpg
deleted file mode 100644
index bf81d794c3..0000000000
--- a/assets/ss/explore.jpg
+++ /dev/null
Binary files differ
diff --git a/assets/ss/user.jpg b/assets/ss/user.jpg
deleted file mode 100644
index 3ec595c199..0000000000
--- a/assets/ss/user.jpg
+++ /dev/null
Binary files differ
diff --git a/cypress/e2e/basic.cy.ts b/cypress/e2e/basic.cy.ts
index d2efbf709c..6471f96504 100644
--- a/cypress/e2e/basic.cy.ts
+++ b/cypress/e2e/basic.cy.ts
@@ -233,7 +233,7 @@ describe('After user setup', () => {
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
cy.get('[data-cy-open-post-form-submit]').click();
- cy.contains('Hello, Misskey!');
+ cy.contains('Hello, Misskey!', { timeout: 15000 });
});
it('open note form with hotkey', () => {
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 91c90ce75a..5e936da80c 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -1012,6 +1012,8 @@ sourceCode: "الشفرة المصدرية"
flip: "اقلب"
lastNDays: "آخر {n} أيام"
surrender: "ألغِ"
+postForm: "أنشئ ملاحظة"
+information: "عن"
_delivery:
stop: "مُعلّق"
_initialAccountSetting:
@@ -1584,3 +1586,7 @@ _offlineScreen:
_remoteLookupErrors:
_noSuchObject:
title: "غير موجود"
+_search:
+ searchScopeAll: "الكل"
+ searchScopeLocal: "المحلي"
+ searchScopeUser: "مستخدم محدد"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index 709874ac20..36d142bd0b 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -852,6 +852,8 @@ replies: "জবাব"
renotes: "রিনোট"
sourceCode: "সোর্স কোড"
flip: "উল্টান"
+postForm: "নোট লিখুন"
+information: "আপনার সম্পর্কে"
_delivery:
stop: "স্থগিত করা হয়েছে"
_type:
@@ -1348,3 +1350,6 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "পাওয়া যায়নি"
+_search:
+ searchScopeAll: "সবগুলো"
+ searchScopeLocal: "স্থানীয়"
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 7b029c6f41..23b958252a 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -66,7 +66,7 @@ copyFolderId: "Copiar ID de la carpeta"
copyProfileUrl: "Copiar adreça URL del perfil"
searchUser: "Cercar un usuari"
searchThisUsersNotes: "Cercar les publicacions de l'usuari"
-reply: "Respon"
+reply: "Respostes"
loadMore: "Carregar més"
showMore: "Veure més"
showLess: "Mostrar menys"
@@ -111,7 +111,7 @@ followRequests: "Peticions de seguiment"
unfollow: "Deixar de seguir"
followRequestPending: "Sol·licituds de seguiment pendents"
enterEmoji: "Introduir un emoji"
-renote: "Impulsar "
+renote: "Impulsar"
unrenote: "Anul·la l'impuls"
renoted: "S'ha impulsat"
renotedToX: "Impulsat per {name}."
@@ -260,7 +260,7 @@ noCustomEmojis: "No hi ha emojis personalitzats"
noJobs: "No hi ha feines"
federating: "Federant"
blocked: "Bloquejat"
-suspended: "Suspés"
+suspended: "Anul·lar subscripció "
all: "tot"
subscribing: "Subscrit a"
publishing: "S'està publicant"
@@ -646,7 +646,7 @@ disablePlayer: "Tanca el reproductor de vídeo"
expandTweet: "Expandir post"
themeEditor: "Editor de temes"
description: "Descripció"
-describeFile: "Afegir subtitulació"
+describeFile: "Afegeix una descripció "
enterFileDescription: "Escriu un peu de foto"
author: "Autor"
leaveConfirm: "Hi ha canvis sense guardar. Els vols descartar?"
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} està parlant sobre \"{word}\""
makeActive: "Activar"
display: "Veure"
copy: "Copiar"
+copiedToClipboard: "Copiat al porta papers"
metrics: "Mètriques"
overview: "Visió General"
logs: "Registres"
@@ -1114,7 +1115,7 @@ forceShowAds: "Mostra els anuncis sempre "
addMemo: "Afegir recordatori"
editMemo: "Editar recordatori"
reactionsList: "Reaccions"
-renotesList: "Impulsos"
+renotesList: "Llistat d'impulsos "
notificationDisplay: "Notificacions"
leftTop: "Dalt a l'esquerra "
rightTop: "Dalt a la dreta "
@@ -1139,7 +1140,7 @@ channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista d
thisChannelArchived: "Aquest Canal ha sigut arxivat."
displayOfNote: "Mostrar notes"
initialAccountSetting: "Configuració del perfil"
-youFollowing: "Seguint"
+youFollowing: "Segueixes "
preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)"
preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta."
options: "Opcions"
@@ -1189,8 +1190,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: "Respon"
-renotes: "Impulsar "
+replies: "Respostes"
+renotes: "Impulsos"
loadReplies: "Mostrar les respostes"
loadConversation: "Mostrar la conversació "
pinnedList: "Llista fixada"
@@ -1309,6 +1310,69 @@ availableRoles: "Roles disponibles "
acknowledgeNotesAndEnable: "Activa'l després de comprendre els possibles perills."
federationSpecified: "Aquest servidor treballa amb una federació de llistes blanques. No pot interactuar amb altres servidors que no siguin els especificats per l'administrador."
federationDisabled: "La unió es troba deshabilitada en aquest servidor. No es pot interactuar amb usuaris d'altres servidors."
+confirmOnReact: "Confirmar en reaccionar"
+reactAreYouSure: "Vols reaccionar amb \"{emoji}\"?"
+markAsSensitiveConfirm: "Vols marcar aquest contingut com a sensible?"
+unmarkAsSensitiveConfirm: "Vols deixar de marcar com a sensible aquest contingut?"
+preferences: "Preferències "
+accessibility: "Accessibilitat "
+preferencesProfile: "Perfil de configuració "
+copyPreferenceId: "Copiar l'ID de la configuració "
+resetToDefaultValue: "Restaura al valor per defecte "
+overrideByAccount: "Anul·lar per compte"
+untitled: "Sense títol "
+noName: "No hi ha un nom disponible "
+skip: "Ometre "
+restore: "Restaurar "
+syncBetweenDevices: "Sincronització entre dispositius"
+preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu"
+preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?"
+preferenceSyncConflictChoiceServer: "Valors de configuració del servidor"
+preferenceSyncConflictChoiceDevice: "Punts d'ajustos del dispositiu "
+preferenceSyncConflictChoiceCancel: "Cancel·lar l'activació de la sincronització "
+paste: "Pegar"
+emojiPalette: "Calaix d'emojis"
+postForm: "Formulari de publicació"
+textCount: "Nombre de caràcters "
+information: "Informació"
+_emojiPalette:
+ palettes: "Calaixos d'emojis"
+ enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius"
+ paletteForMain: "Calaix d'emojis principal"
+ paletteForReaction: "Calaix d'emojis per reaccions"
+_settings:
+ driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius."
+ pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se."
+ notificationsBanner: "Pots configurar el tipus i l'abast de les notificacions que es rebran del servidor, també les notificacions emergents."
+ api: "API"
+ webhook: "Webhook"
+ serviceConnection: "Relació entre serveis"
+ serviceConnectionBanner: "Pots configurar i gestionar tokens d'accés i webhooks per integrar serveis i aplicacions externes."
+ accountData: "Dades del compte"
+ accountDataBanner: "Exportació/Importació i gestió d'arxius amb dades del compte."
+ muteAndBlockBanner: "Pots configurar i gestionar els continguts que desitges amagar i restringir les accions de determinats usuaris."
+ accessibilityBanner: "Els clients poden personalitzar-se i configurar-se per un ús òptim en funció de la seva visió i comportament."
+ privacyBanner: "Pots establir la configuració de privacitat del compte, com el grau de visibilitat del teu contingut, la facilitat per trobar-ho i si es pot aprovar els seguidors."
+ securityBanner: "Configura les opcions relacionades amb la seguretat del teu compte com ara contrasenyes, mètodes per iniciar sessió, aplicacions d'autentificació i claus d'accés."
+ preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències."
+ appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer."
+ soundsBanner: "Configuració dels sons que reproduirà el client."
+ timelineAndNote: "Línia de temps i nota"
+ makeEveryTextElementsSelectable: "Fes que tots els elements del text siguin seleccionables"
+ makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions."
+_preferencesProfile:
+ profileName: "Nom del perfil"
+ profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
+ profileNameDescription2: "Per exemple: \"PC Principal\", \"Smartphone\", etc"
+_preferencesBackup:
+ autoBackup: "Còpia de seguretat automàtica "
+ restoreFromBackup: "Restaurar des d'una còpia de seguretat"
+ noBackupsFoundTitle: "No s'ha trobat cap còpia de seguretat"
+ noBackupsFoundDescription: "No s'han trobat còpies de seguretat creades automàticament, però si has desat, manualment, un arxiu de còpia de seguretat, pots importar-lo i carregar-lo."
+ selectBackupToRestore: "Seleccionar la còpia de seguretat que vols restaurar"
+ youNeedToNameYourProfileToEnableAutoBackup: "Has de posar-li un nom al teu perfil per poder activar les còpies de seguretat automàtiques."
+ autoPreferencesBackupIsNotEnabledForThisDevice: "La còpia de seguretat automàtica no es troba activada en aquest dispositiu."
+ backupFound: "Còpia de seguretat de la configuració trobada"
_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ó."
@@ -1319,6 +1383,7 @@ _accountSettings:
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."
+ mayNotEffectSomeSituations: "Aquestes restriccions són simplificades. Pot ser que no s'apliquin en determinades situacions, com quan es modera o visualitza un servidor remot."
notesHavePassedSpecifiedPeriod: "Notes publicades durant un període de temps especificat."
notesOlderThanSpecifiedDateAndTime: "Notes més antigues de la data i temps especificat "
_abuseUserReport:
@@ -1330,7 +1395,7 @@ _abuseUserReport:
resolveTutorial: "Si l'informe és legítim selecciona \"Acceptar\" per resoldre'l positivament. Però si l'informe no és legítim selecciona \"Rebutjar\" per resoldre'l negativament."
_delivery:
status: "Estat d'entrega "
- stop: "Suspés"
+ stop: "Anul·lar subscripció "
resume: "Torna a enviar"
_type:
none: "S'està publicant"
@@ -1967,6 +2032,7 @@ _theme:
installed: "{name} Instal·lat "
installedThemes: "Temes instal·lats "
builtinThemes: "Temes integrats"
+ instanceTheme: "Tema de la instància "
alreadyInstalled: "Aquest tema ja es troba instal·lat "
invalid: "El format d'aquest tema no és correcte"
make: "Crear un tema"
@@ -2006,7 +2072,7 @@ _theme:
hashtag: "Etiqueta"
mention: "Menció"
mentionMe: "Mencions (jo)"
- renote: "Renotar"
+ renote: "Impulsar"
modalBg: "Fons del modal"
divider: "Divisor"
scrollbarHandle: "Maneta de la barra de desplaçament"
@@ -2440,13 +2506,15 @@ _notification:
flushNotification: "Netejar notificacions"
exportOfXCompleted: "Completada l'exportació de {x}"
login: "Algú ha iniciat sessió "
+ createToken: "Token d'accés generat"
+ createTokenDescription: "Si no saps què és, esborra el token des de {text}."
_types:
all: "Tots"
note: "Notes noves"
follow: "Segueix-me"
mention: "Menció"
reply: "Respostes"
- renote: "Renotar"
+ renote: "Impulsos"
quote: "Citar"
reaction: "Reaccions"
pollEnded: "Enquesta terminada"
@@ -2456,12 +2524,13 @@ _notification:
achievementEarned: "Assoliment desbloquejat"
exportCompleted: "Exportació completada"
login: "Iniciar sessió"
+ createToken: "Creació de tokens d'accés "
test: "Prova la notificació"
app: "Notificacions d'aplicacions"
_actions:
followBack: "També et segueix"
reply: "Respondre"
- renote: "Renotar"
+ renote: "Impulsar"
_deck:
alwaysShowMainColumn: "Mostrar sempre la columna principal"
columnAlign: "Alinea les columnes"
@@ -2483,6 +2552,7 @@ _deck:
useSimpleUiForNonRootPages: "Usa una interfície senzilla per a les pàgines navegades"
usedAsMinWidthWhenFlexible: "L'amplada mínima es farà servir quan \"Ajust automàtic de l'amplada\" estigui activat"
flexible: "Ajust automàtic de l'amplada"
+ enableSyncBetweenDevicesForProfiles: "Activar la sincronització de la informació de perfils de dispositiu a dispositiu"
_columns:
main: "Principal"
widgets: "Ginys"
@@ -2590,6 +2660,7 @@ _moderationLogTypes:
deletePage: "Esborrar la pàgina"
deleteFlash: "Esborrar el guió"
deleteGalleryPost: "Esborrar la publicació de la galeria"
+ updateProxyAccountDescription: "Actualitzar descripció del compte proxy"
_fileViewer:
title: "Detall del fitxer"
type: "Tipus de fitxer"
@@ -2603,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Assegura't que qui distribueix aquest recurs és fiable abans d'instal·lar-ho."
_plugin:
title: "Vols instal·lar aquest afegit?"
- metaTitle: "Informació de l'afegit "
_theme:
title: "Vols instal·lar aquest tema?"
- metaTitle: "Informació del tema"
_meta:
base: "Paleta de colors base"
_vendorInfo:
@@ -2822,8 +2891,6 @@ _remoteLookupErrors:
_responseInvalid:
title: "La resposta no és correcta "
description: "Hem pogut comunicar-nos amb aquest servidor, però les dades rebudes no són correctes."
- _responseInvalidIdHostNotMatch:
- description: "El domini de l'adreça introduïda no és el mateix que el domini de l'adreça final obtinguda. Si estàs consultant continguts remots mitjançant servidors tercers, torna a fer la consulta fent servir l'adreça que es pot obtenir en el servidor origen."
_noSuchObject:
title: "No s'ha trobat"
description: "No es pot trobar el recurs sol·licitat, si us plau comprova l'adreça una altra vegada."
@@ -2840,3 +2907,23 @@ _captcha:
_unknown:
title: "Error CAPTCHA"
text: "S'ha produït un error inesperat."
+_bootErrors:
+ title: "Hi ha hagut en error en carregar"
+ serverError: "Si el problema persisteix després d'esperar una mica i recarregar, posa't en contacte amb l'administrador del servidor amb el següent codi d'error."
+ solution: "Per intentar resoldre el problema pots fer el següent."
+ solution1: "Actualitza el navegador i el sistema operatiu a l'última versió "
+ solution2: "Desactiva els adblockers"
+ solution3: "Esborra la memòria cau del navegador"
+ solution4: "(Navegador Tor) configura dom.webaudio.enabled a true"
+ otherOption: "Altres opcions"
+ otherOption1: "Esborrar la configuració i la memòria cau del client"
+ otherOption2: "Iniciar client senzill"
+ otherOption3: "Iniciar l'eina de reparació "
+_search:
+ searchScopeAll: "Tot"
+ searchScopeLocal: "Local"
+ searchScopeServer: "Instància "
+ searchScopeUser: "Especificar usuari"
+ pleaseEnterServerHost: "Introdueix l'adreça de la instància "
+ pleaseSelectUser: "Selecciona un usuari"
+ serverHostPlaceholder: "Ex: misskey.example.com"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index afa3047c1d..11ad97c3a5 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -5,6 +5,7 @@ introMisskey: "Vítejte! Misskey je otevřený a decentralizovaný microblogový
poweredByMisskeyDescription: "{name} je jeden ze serverů využívající open source platformu <b>Misskey<b> (nazývaná \"Misskey instance\")."
monthAndDay: "{day}. {month}."
search: "Vyhledávání"
+reset: "Obnovit"
notifications: "Oznámení"
username: "Uživatelské jméno"
password: "Heslo"
@@ -168,6 +169,9 @@ addAccount: "Přidat účet"
reloadAccountsList: "Obnovit list účtů"
loginFailed: "Přihlášení se nezdařilo."
showOnRemote: "Více na původním profilu"
+continueOnRemote: "Pokračujte na původní profil"
+chooseServerOnMisskeyHub: "Vyberete si server z Misskey Hubu"
+inputHostName: "Zadejte doménu"
general: "Obecně"
wallpaper: "Obrázek na pozadí"
setWallpaper: "Nastavení obrázku na pozadí"
@@ -192,6 +196,7 @@ perHour: "za hodinu"
perDay: "za den"
stopActivityDelivery: "Přestat zasílat aktivitu"
blockThisInstance: "Blokovat tuto instanci"
+silenceThisInstance: "Utišit tuto instanci"
operations: "Operace"
software: "Software"
version: "Verze"
@@ -365,8 +370,11 @@ hcaptcha: "hCaptcha"
enableHcaptcha: "Aktivovat hCaptchu"
hcaptchaSiteKey: "Klíč stránky"
hcaptchaSecretKey: "Tajný Klíč (Secret Key)"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "Aktivovat mCaptchu"
mcaptchaSiteKey: "Klíč stránky"
mcaptchaSecretKey: "Tajný Klíč (Secret Key)"
+mcaptchaInstanceUrl: "URL mCaptcha serveru"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Zapnout ReCAPTCHu"
recaptchaSiteKey: "Klíč stránky"
@@ -1094,6 +1102,8 @@ sourceCode: "Zdrojový kód"
flip: "Otočit"
lastNDays: "Posledních {n} dnů"
surrender: "Zrušit"
+postForm: "Formulář pro odeslání"
+information: "Informace"
_delivery:
stop: "Suspendováno"
_type:
@@ -2024,3 +2034,7 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Nenalezeno"
+_search:
+ searchScopeAll: "Vše"
+ searchScopeLocal: "Místní"
+ searchScopeUser: "Upřesnit uživatele"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 11fe6d3ff5..c6cc416638 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -49,7 +49,7 @@ pin: "An dein Profil anheften"
unpin: "Von deinem Profil lösen"
copyContent: "Inhalt kopieren"
copyLink: "Link kopieren"
-copyRemoteLink: "Renote-Link kopieren"
+copyRemoteLink: "Remote-Link kopieren"
copyLinkRenote: "Renote-Link kopieren"
delete: "Löschen"
deleteAndEdit: "Löschen und Bearbeiten"
@@ -863,7 +863,7 @@ administration: "Verwaltung"
accounts: "Benutzerkonten"
switch: "Wechseln"
noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert."
-noInquiryUrlWarning: "Keine gültige URL."
+noInquiryUrlWarning: "Keine gültige Kontakt-URL."
noBotProtectionWarning: "Schutz vor Bots ist nicht konfiguriert."
configure: "Konfigurieren"
postToGallery: "Neuen Galeriebeitrag erstellen"
@@ -1308,6 +1308,10 @@ pleaseSelectAccount: "Bitte Konto auswählen"
availableRoles: "Verfügbare Rollen"
federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren."
federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren."
+postForm: "Notizfenster"
+information: "Über"
+_settings:
+ webhook: "Webhook"
_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."
@@ -1383,6 +1387,9 @@ _initialTutorial:
title: "Was sind Notizen?"
description: "Beiträge auf Misskey heißen \"Notizen\". Notizen werden chronologisch in der Chronik angeordnet und in Echtzeit aktualisiert."
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."
+ renote: "Du kannst diese Notiz in deiner eigenen Chronik teilen. Du kannst sie auch mit deinen Kommentaren zitieren."
+ reaction: "Du kannst der Notiz Reaktionen hinzufügen. Weitere Einzelheiten werden auf der nächsten Seite erläutert."
+ menu: "Du kannst Details zu Notizen anzeigen, Links kopieren und verschiedene andere Aktionen durchführen."
_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."
@@ -1401,13 +1408,21 @@ _initialTutorial:
_visibility:
description: "Du kannst einschränken, wer deine Notiz sehen kann."
public: "Deine Notiz wird für alle Nutzer sichtbar sein."
+ direct: "Die Notiz wird nur für den angegebenen Benutzer veröffentlicht und der Empfänger wird benachrichtigt. Kann anstelle von Direktnachrichten verwendet werden."
doNotSendConfidencialOnDirect1: "Sei vorsichtig, wenn du sensible Informationen verschickst!"
+ doNotSendConfidencialOnDirect2: "Die Administratoren des Servers können den Inhalt der Notiz sehen. Sei vorsichtig mit sensiblen Informationen, wenn du Direktnachrichten an Benutzer auf nicht vertrauenswürdigen Servern sendest."
+ localOnly: "Wenn du eine Notiz mit dieser Einstellung veröffentlichst, wird sie nicht an andere Server weitergeleitet. Benutzer auf anderen Servern können diese Notizen nicht direkt sehen, unabhängig von den obigen Anzeigeeinstellungen."
_cw:
title: "Inhaltswarnung"
+ description: "Anstelle des Textes wird das angezeigt, was du im Abschnitt „Anmerkungen“ angibst. Drücke auf „Inhalt anzeigen“, um den vollständigen Text zu sehen."
_exampleNote:
+ cw: "Das wird dich bestimmt hungrig machen!"
note: "Ich hatte gerade einen Donut mit Schokoladenüberzug 🍩😋"
_howToMakeAttachmentsSensitive:
+ title: "Wie markiert man Anhänge als sensibel?"
tryThisFile: "Versuche, das angehängte Bild als sensibel zu markieren!"
+ _exampleNote:
+ note: "Ups, ich habe es vergeigt, den Natto-Deckel zu öffnen..."
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."
@@ -1431,6 +1446,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. "
+ reactionsBufferingDescription: "Wenn diese Option aktiviert ist, kann sie die Leistung beim Erstellen von Reaktionen erheblich verbessern und die Belastung der Datenbank verringern. Allerdings steigt die Speichernutzung von Redis."
openRegistrationWarning: "Das Aktivieren von Registrierungen ist riskant. Es wird empfohlen, sie nur dann zu aktivieren, wenn der Server ständig überwacht wird und im Falle eines Problems sofort reagiert werden kann."
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Wenn über einen bestimmten Zeitraum keine Moderatorenaktivität festgestellt wird, wird diese Einstellung automatisch deaktiviert, um Spam zu verhindern."
_accountMigration:
@@ -1835,6 +1851,7 @@ _plugin:
installWarn: "Installiere bitte nur vertrauenswürdige Plugins."
manage: "Plugins verwalten"
viewSource: "Quelltext anzeigen"
+ viewLog: "Protokoll anzeigen"
_preferencesBackups:
list: "Erstellte Backups"
saveNew: "Neu erstellen"
@@ -1864,6 +1881,8 @@ _aboutMisskey:
contributors: "Hauptmitwirkende"
allContributors: "Alle Mitwirkenden"
source: "Quellcode"
+ original: "Original"
+ thisIsModifiedVersion: "{name} verwendet eine modifizierte Version des ursprünglichen Misskey."
translation: "Misskey übersetzen"
donate: "An Misskey spenden"
morePatrons: "Wir schätzen ebenso die Unterstützung vieler anderer hier nicht gelisteter Personen sehr. Danke! 🥰"
@@ -1989,6 +2008,8 @@ _soundSettings:
driveFileTypeWarn: "Diese Datei wird nicht unterstützt"
driveFileTypeWarnDescription: "Bitte wähle eine Audiodatei"
driveFileDurationWarn: "Audio zu lang."
+ driveFileDurationWarnDescription: "Lange Töne kann die Verwendung von Misskey stören. Trotzdem fortfahren?"
+ driveFileError: "Audio konnte nicht geladen werden. Bitte ändere die Einstellung."
_ago:
future: "Zukunft"
justNow: "Gerade eben"
@@ -2000,6 +2021,10 @@ _ago:
monthsAgo: "vor {n} Monat(en)"
yearsAgo: "vor {n} Jahr(en)"
invalid: "Ungültig"
+_timeIn:
+ seconds: "In {n}s"
+ minutes: "In {n} Min."
+ hours: "In {n} Std."
_time:
second: "Sekunde(n)"
minute: "Minute(n)"
@@ -2105,6 +2130,7 @@ _auth:
permissionAsk: "Diese Anwendung fordert folgende Berechtigungen"
pleaseGoBack: "Bitte kehre zur Anwendung zurück"
callback: "Es wird zur Anwendung zurückgekehrt"
+ accepted: "Zugriff gewährt"
denied: "Zugriff verweigert"
pleaseLogin: "Bitte logge dich ein, um Apps zu authorisieren."
_antennaSources:
@@ -2215,6 +2241,7 @@ _profile:
changeBanner: "Banner ändern"
verifiedLinkDescription: "Gibst du hier eine URL ein, die einen Link zu deinem Profile enthält, wird neben diesem Feld ein Icon zur Besitzbestätigung angezeigt."
avatarDecorationMax: "Du kannst bis zu {max} Dekorationen hinzufügen."
+ followedMessage: "Nachricht, wenn dir jemand folgt"
followedMessageDescription: "Du kannst eine kurze Nachricht festlegen, die dem Empfänger angezeigt wird, wenn er dir folgt."
_exportOrImport:
allNotes: "Alle Notizen"
@@ -2343,8 +2370,11 @@ _notification:
sendTestNotification: "Testbenachrichtigung senden"
notificationWillBeDisplayedLikeThis: "Benachrichtigungen sehen so aus"
reactedBySomeUsers: "{n} Benutzer haben eine Reaktion geschickt"
+ likedBySomeUsers: "{n} Benutzer mochten deine Notiz"
renotedBySomeUsers: "Renote von {n} Benutzern"
followedBySomeUsers: "Von {n} Benutzern gefolgt"
+ flushNotification: "Benachrichtigungen löschen"
+ exportOfXCompleted: "Der Export von {x} ist abgeschlossen"
login: "Neue Anmeldung erfolgt"
_types:
all: "Alle"
@@ -2360,7 +2390,9 @@ _notification:
followRequestAccepted: "Akzeptierte Follow-Anfragen"
roleAssigned: "Rolle zugewiesen"
achievementEarned: "Errungenschaft freigeschaltet"
- login: "Anmelden"
+ exportCompleted: "Der Export ist abgeschlossen"
+ login: "Anmeldung"
+ test: "Test-Benachrichtigungen"
app: "Benachrichtigungen von Apps"
_actions:
followBack: "folgt dir nun auch"
@@ -2370,6 +2402,7 @@ _deck:
alwaysShowMainColumn: "Hauptspalte immer zeigen"
columnAlign: "Spaltenausrichtung"
addColumn: "Spalte hinzufügen"
+ newNoteNotificationSettings: "Benachrichtigungseinstellungen für neue Notizen"
configureColumn: "Spalteneinstellungen"
swapLeft: "Mit linker Spalte tauschen"
swapRight: "Mit rechter Spalte tauschen"
@@ -2408,6 +2441,7 @@ _drivecleaner:
orderByCreatedAtAsc: "Aufsteigendes Erstelldatum"
_webhookSettings:
createWebhook: "Webhook erstellen"
+ modifyWebhook: "Webhook bearbeiten"
name: "Name"
secret: "Secret"
trigger: "Auslöser"
@@ -2420,12 +2454,22 @@ _webhookSettings:
renote: "Wenn du ein Renote erhältst"
reaction: "Wenn du eine Reaktion erhältst"
mention: "Wenn du erwähnt wirst"
+ deleteConfirm: "Bist du sicher, dass du den Webhook löschen willst?"
_abuseReport:
_notificationRecipient:
createRecipient: "Meldungsempfänger hinzufügen"
+ modifyRecipient: "Bearbeite einen Empfänger für Meldungen"
+ recipientType: "Art der Benachrichtigung"
_recipientType:
mail: "Email"
+ webhook: "Webhook"
+ _captions:
+ mail: "Die Benachrichtigung wird bei Eingang einer Meldung an die E-Mail-Adressen der Moderatoren gesendet"
+ webhook: "Sendet eine Benachrichtigung an den System Webhook, wenn eine Meldung eingegangen ist oder gelöst wurde"
keywords: "Schlüsselwort"
+ notifiedUser: "Zu benachrichtigender Benutzer"
+ notifiedWebhook: "Zu verwendender Webhook"
+ deleteConfirm: "Bist du sicher, dass du den Empfänger der Benachrichtigung entfernen möchtest?"
_moderationLogTypes:
createRole: "Rolle erstellt"
deleteRole: "Rolle gelöscht"
@@ -2465,6 +2509,7 @@ _moderationLogTypes:
createSystemWebhook: "System-Webhook erstellt"
updateSystemWebhook: "System-Webhook aktualisiert"
deleteSystemWebhook: "System-Webhook gelöscht"
+ deleteAccount: "Benutzerkonto gelöscht"
deletePage: "Seite gelöscht"
deleteGalleryPost: "Galeriebeitrag gelöscht"
_fileViewer:
@@ -2480,10 +2525,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Überprüfe vor Installation die Vertrauenswürdigkeit des Vertreibers."
_plugin:
title: "Möchtest du dieses Plugin installieren?"
- metaTitle: "Plugininformation"
_theme:
title: "Möchten du dieses Farbschema installieren?"
- metaTitle: "Farbschemainfo"
_meta:
base: "Farbschemavorlage"
_vendorInfo:
@@ -2573,3 +2616,7 @@ _remoteLookupErrors:
_noSuchObject:
title: "Nicht gefunden"
description: "Die angeforderte Ressource konnte nicht gefunden werden, bitte überprüfe die URI erneut."
+_search:
+ searchScopeAll: "Alle"
+ searchScopeLocal: "Lokal"
+ searchScopeUser: "Spezifischer Benutzer"
diff --git a/locales/el-GR.yml b/locales/el-GR.yml
index 4657842ca5..727565ae21 100644
--- a/locales/el-GR.yml
+++ b/locales/el-GR.yml
@@ -288,6 +288,8 @@ cannotUploadBecauseNoFreeSpace: "Το ανέβασμα απέτυχε λόγω
icon: "Εικονίδιο"
replies: "Απάντηση"
renotes: "Κοινοποίηση σημειώματος"
+postForm: "Φόρμα δημοσίευσης"
+information: "Πληροφορίες"
_email:
_follow:
title: "Έχετε ένα νέο ακόλουθο"
@@ -397,3 +399,5 @@ _moderationLogTypes:
suspend: "Αποβολή"
_reversi:
total: "Σύνολο"
+_search:
+ searchScopeLocal: "Τοπικό"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 7a03669101..edc961da23 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -132,7 +132,7 @@ reaction: "Reactions"
reactions: "Reactions"
emojiPicker: "Emoji picker"
pinnedEmojisForReactionSettingDescription: "Set the emojis to be pinned and displayed when reacting."
-pinnedEmojisSettingDescription: "Set the emojis to be pinned and displayed when viewing emoji picker."
+pinnedEmojisSettingDescription: "Set the emojis to be pinned and displayed when viewing emoji picker"
emojiPickerDisplay: "Emoji picker display"
overwriteFromPinnedEmojisForReaction: "Override from reaction settings"
overwriteFromPinnedEmojis: "Override from general settings"
@@ -586,7 +586,7 @@ popout: "Pop-out"
volume: "Volume"
masterVolume: "Master volume"
notUseSound: "Disable sound"
-useSoundOnlyWhenActive: "Output sounds only if Misskey is active."
+useSoundOnlyWhenActive: "Output sounds only if Misskey is active"
details: "Details"
renoteDetails: "Renote details"
chooseEmoji: "Select an emoji"
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} said something about \"{word}\""
makeActive: "Activate"
display: "Display"
copy: "Copy"
+copiedToClipboard: "Copied to clipboard"
metrics: "Metrics"
overview: "Overview"
logs: "Logs"
@@ -1261,7 +1262,7 @@ copyReplayData: "Copy replay data"
ranking: "Ranking"
lastNDays: "Last {n} days"
backToTitle: "Go back to title"
-hemisphere: "Where are you located"
+hemisphere: "Where you live"
withSensitive: "Include notes with sensitive files"
userSaysSomethingSensitive: "Post by {name} contains sensitive content"
enableHorizontalSwipe: "Swipe to switch tabs"
@@ -1309,6 +1310,69 @@ availableRoles: "Available roles"
acknowledgeNotesAndEnable: "Turn on after understanding the precautions."
federationSpecified: "This server is operated in an allowlist federation. Interacting with servers other than those designated by the administrator is not allowed."
federationDisabled: "Federation is disabled on this server. You cannot interact with users on other servers."
+confirmOnReact: "Confirm when reacting"
+reactAreYouSure: "Would you like to add a \"{emoji}\" reaction?"
+markAsSensitiveConfirm: "Do you want to set this media as sensitive?"
+unmarkAsSensitiveConfirm: "Do you want to remove the sensitive designation for this media?"
+preferences: "Preferences"
+accessibility: "Accessibility"
+preferencesProfile: "Preferences profile"
+copyPreferenceId: "Copy the preference ID"
+resetToDefaultValue: "Revert to default"
+overrideByAccount: "Override by the account"
+untitled: "Untitled"
+noName: "No name"
+skip: "Skip"
+restore: "Restore"
+syncBetweenDevices: "Sync between devices"
+preferenceSyncConflictTitle: "The configured value exists on the server."
+preferenceSyncConflictText: "The sync enabled settings will save their values to the server. However, there are existing values on the server. Which set of values would you like to overwrite?"
+preferenceSyncConflictChoiceServer: "Configured value on server"
+preferenceSyncConflictChoiceDevice: "Configured value on device"
+preferenceSyncConflictChoiceCancel: "Cancel enabling sync"
+paste: "Paste"
+emojiPalette: "Emoji palette"
+postForm: "Posting form"
+textCount: "Character count"
+information: "About"
+_emojiPalette:
+ palettes: "Palette"
+ enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices"
+ paletteForMain: "Main palette"
+ paletteForReaction: "Reaction palette"
+_settings:
+ driveBanner: "You can manage and configure the drive, check usage, and configure file upload settings."
+ pluginBanner: "You can extend client features with plugins. You can install plugins, configure and manage individually."
+ notificationsBanner: "You can configure the types and range of notifications from the server and push notifications."
+ api: "API"
+ webhook: "Webhook"
+ serviceConnection: "Service integration"
+ serviceConnectionBanner: "Manage and configure access tokens and Webhooks to integrate with external apps or services."
+ accountData: "Account data"
+ accountDataBanner: "Export and import to manage account data."
+ muteAndBlockBanner: "You can configure and manage settings to hide content and restrict actions from specific users."
+ accessibilityBanner: "You can personalize the client's visuals and behavior, and configure settings to optimize usage."
+ privacyBanner: "You can configure settings related to account privacy, such as content visibility, discoverability, and follow approval."
+ securityBanner: "You can configure settings related to account security, such as password, login methods, authentication apps, and Passkeys."
+ preferencesBanner: "You can configure the overall behavior of the client according to your preferences."
+ appearanceBanner: "You can configure the appearance and display settings for the client according to your preferences."
+ soundsBanner: "You can configure the sound settings for playback in the client."
+ timelineAndNote: "Timeline and note"
+ makeEveryTextElementsSelectable: "Make all text elements selectable"
+ makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations."
+_preferencesProfile:
+ profileName: "Profile name"
+ profileNameDescription: "Set a name that identifies this device."
+ profileNameDescription2: "Example: \"Main PC\", \"Smartphone\""
+_preferencesBackup:
+ autoBackup: "Auto backup"
+ restoreFromBackup: "Restore from backup"
+ noBackupsFoundTitle: "No backups found"
+ noBackupsFoundDescription: "No auto-created backups were found, but if you have manually saved a backup file, you can import and restore it."
+ selectBackupToRestore: "Select a backup to restore"
+ youNeedToNameYourProfileToEnableAutoBackup: "A profile name must be set to enable auto backup."
+ autoPreferencesBackupIsNotEnabledForThisDevice: "Settings auto backup is not enabled on this device."
+ backupFound: "Settings backup is found"
_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."
@@ -1319,6 +1383,7 @@ _accountSettings:
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."
+ mayNotEffectSomeSituations: "These restrictions are simplified. They may not apply in some situations, such as when viewing on a remote server or during moderation."
notesHavePassedSpecifiedPeriod: "Note that the specified time has passed"
notesOlderThanSpecifiedDateAndTime: "Notes before the specified date and time"
_abuseUserReport:
@@ -1967,6 +2032,7 @@ _theme:
installed: "{name} has been installed"
installedThemes: "Installed themes"
builtinThemes: "Built-in themes"
+ instanceTheme: "Server theme"
alreadyInstalled: "This theme is already installed"
invalid: "The format of this theme is invalid"
make: "Make a theme"
@@ -2440,6 +2506,8 @@ _notification:
flushNotification: "Clear notifications"
exportOfXCompleted: "Export of {x} has been completed"
login: "Someone logged in"
+ createToken: "An access token has been created"
+ createTokenDescription: "If you have no idea, delete the access token through \"{text}\"."
_types:
all: "All"
note: "New notes"
@@ -2456,6 +2524,7 @@ _notification:
achievementEarned: "Achievement unlocked"
exportCompleted: "The export has been completed"
login: "Sign In"
+ createToken: "Create access token"
test: "Notification test"
app: "Notifications from linked apps"
_actions:
@@ -2483,6 +2552,7 @@ _deck:
useSimpleUiForNonRootPages: "Use simple UI for navigated pages"
usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled"
flexible: "Auto-adjust width"
+ enableSyncBetweenDevicesForProfiles: "Enable profile information sync between devices"
_columns:
main: "Main"
widgets: "Widgets"
@@ -2590,6 +2660,7 @@ _moderationLogTypes:
deletePage: "Page deleted"
deleteFlash: "Play deleted"
deleteGalleryPost: "Gallery post deleted"
+ updateProxyAccountDescription: "Update the description of the proxy account"
_fileViewer:
title: "File details"
type: "File type"
@@ -2603,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Make sure the distributor of this resource is trustworthy before installation."
_plugin:
title: "Do you want to install this plugin?"
- metaTitle: "Plugin information"
_theme:
title: "Do you want to install this theme?"
- metaTitle: "Theme information"
_meta:
base: "Base color scheme"
_vendorInfo:
@@ -2645,7 +2714,7 @@ _dataSaver:
description: "Prevents images/videos from being loaded automatically. Hidden images/videos will be loaded when tapped."
_avatar:
title: "Avatar image"
- description: "Stop avatar image animation. Animated images can be larger in file size than normal images, potentially leading to further reductions in data traffic."
+ description: "Stop avatar image animation. Animated images can be larger in file size than normal images, potentially leading to further reductions in data traffic."
_urlPreview:
title: "URL preview thumbnails"
description: "URL preview thumbnail images will no longer be loaded."
@@ -2737,7 +2806,7 @@ _roleSelectDialog:
_customEmojisManager:
_gridCommon:
copySelectionRows: "Copy selected rows"
- copySelectionRanges: "Copy selected ranges"
+ copySelectionRanges: "Copy selection"
deleteSelectionRows: "Delete selected rows"
deleteSelectionRanges: "Delete rows in the selection"
searchSettings: "Search settings"
@@ -2807,8 +2876,8 @@ _selfXssPrevention:
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}"
_followRequest:
- recieved: "Received"
- sent: "Sent"
+ recieved: "Received request"
+ sent: "Sent request"
_remoteLookupErrors:
_federationNotAllowed:
title: "Unable to communicate with this server"
@@ -2822,8 +2891,6 @@ _remoteLookupErrors:
_responseInvalid:
title: "Response is invalid"
description: "It could communicate with this server, but the data obtained was incorrect."
- _responseInvalidIdHostNotMatch:
- description: "The domain of the entered URI differs from the domain of the final obtained URI. If you are looking up remote content through a third-party server, please look up again using a URI that can be obtained from the origin server."
_noSuchObject:
title: "Not found"
description: "The requested resource was not found, please recheck the URI."
@@ -2840,3 +2907,23 @@ _captcha:
_unknown:
title: "CAPTCHA error"
text: "An unexpected error occurred."
+_bootErrors:
+ title: "Failed to load"
+ serverError: "If the problem persists after waiting a moment and reloading, please contact the server administrator with the following Error ID."
+ solution: "The following may solve the problem."
+ solution1: "Update your browser and OS to the latest version"
+ solution2: "Disable ad blocker"
+ solution3: "Clear the browser cache"
+ solution4: "Set the dom.webaudio.enabled to true for Tor Browser"
+ otherOption: "Other options"
+ otherOption1: "Delete client settings and cache"
+ otherOption2: "Start the simple client"
+ otherOption3: "Launch the repair tool"
+_search:
+ searchScopeAll: "All"
+ searchScopeLocal: "Local"
+ searchScopeServer: "Specific server"
+ searchScopeUser: "Specific user"
+ pleaseEnterServerHost: "Enter the server host"
+ pleaseSelectUser: "Select user"
+ serverHostPlaceholder: "Example: misskey.example.com"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 0b1411d84b..cd96d348c3 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -605,6 +605,7 @@ descendingOrder: "Descendente"
scratchpad: "Scratch pad"
scratchpadDescription: "Scratchpad proporciona un entorno experimental para AiScript. Puede escribir, ejecutar y verificar los resultados que interactúan con Misskey."
uiInspector: "Inspector de UI"
+uiInspectorDescription: "Puedes visualizar una lista de elementos UI presentes en la memoria. Los componentes de la interfaz de usuario son generados por las funciones UI:C:"
output: "Salida"
script: "Script"
disablePagesScript: "Deshabilitar AiScript en Páginas"
@@ -693,6 +694,7 @@ regexpError: "Error de la expresión regular"
regexpErrorDescription: "Ocurrió un error en la expresión regular en la linea {line} de las palabras muteadas {tab}"
instanceMute: "Instancias silenciadas"
userSaysSomething: "{name} dijo algo"
+userSaysSomethingAbout: "{name} dijo algo sobre {word}"
makeActive: "Activar"
display: "Apariencia"
copy: "Copiar"
@@ -861,6 +863,7 @@ administration: "Administrar"
accounts: "Cuentas"
switch: "Cambiar"
noMaintainerInformationWarning: "No se ha establecido la información del administrador"
+noInquiryUrlWarning: "No se ha guardado la URL de consulta."
noBotProtectionWarning: "La protección contra los bots no está configurada"
configure: "Configurar"
postToGallery: "Crear una nueva publicación en la galería"
@@ -925,6 +928,7 @@ followersVisibility: "Visibilidad de seguidores"
continueThread: "Ver la continuación del hilo"
deleteAccountConfirm: "La cuenta será borrada. ¿Está seguro?"
incorrectPassword: "La contraseña es incorrecta"
+incorrectTotp: "La contraseña de un solo uso es incorrecta o ha caducado."
voteConfirm: "¿Confirma su voto a {choice}?"
hide: "Ocultar"
useDrawerReactionPickerForMobile: "Mostrar panel de reacciones en móviles"
@@ -1053,6 +1057,7 @@ thisPostMayBeAnnoyingHome: "Publicar en línea de tiempo 'Inicio'"
thisPostMayBeAnnoyingCancel: "detener"
thisPostMayBeAnnoyingIgnore: "Publicar de todos modos"
collapseRenotes: "Colapsar renotas que ya hayas visto"
+collapseRenotesDescription: "Contrae notas a las que ya has reaccionado o renotado "
internalServerError: "Error interno del servidor"
internalServerErrorDescription: "El servidor tuvo un error inesperado."
copyErrorInfo: "Copiar detalles del error"
@@ -1091,6 +1096,7 @@ retryAllQueuesConfirmTitle: "Desea ¿reintentar inmediatamente todas las colas?"
retryAllQueuesConfirmText: "La carga del servidor está incrementándose temporalmente "
enableChartsForRemoteUser: "Generar gráficas de usuarios remotos."
enableChartsForFederatedInstances: "Generar gráficos de servidores remotos"
+enableStatsForFederatedInstances: "Activar las estadísticas de las instancias remotas federadas"
showClipButtonInNoteFooter: "Añadir \"Clip\" al menú de notas"
reactionsDisplaySize: "Tamaño de las reacciones"
limitWidthOfReaction: "Limitar ancho de las reacciones"
@@ -1139,6 +1145,7 @@ preventAiLearningDescription: "Pedirle a las arañas (crawlers) no usar los text
options: "Opción"
specifyUser: "Especificar usuario"
lookupConfirm: "¿Quiere informarse?"
+openTagPageConfirm: "¿Quieres abrir la página de etiquetas?"
specifyHost: "Especificar Host"
failedToPreviewUrl: "No se pudo generar la vista previa"
update: "Actualizar"
@@ -1267,15 +1274,35 @@ useBackupCode: "Usar códigos de respaldo"
launchApp: "Ejecutar la app"
useNativeUIForVideoAudioPlayer: "Usar la interfaz del navegador cuando se reproduce audio y vídeo"
keepOriginalFilename: "Mantener el nombre original del archivo"
+keepOriginalFilenameDescription: "Si desactivas esta opción, los nombres de los archivos serán remplazados por una cadena de caracteres aleatoria cuando subas los archivos."
noDescription: "No hay descripción"
alwaysConfirmFollow: "Confirmar siempre cuando se sigue a alguien"
inquiry: "Contacto"
tryAgain: "Por favor , inténtalo de nuevo"
+confirmWhenRevealingSensitiveMedia: "Confirmación cuando se revele contenido sensible"
+sensitiveMediaRevealConfirm: "Esto puede contener contenido sensible. ¿Estás seguro/a de querer mostrarlo?"
+createdLists: "Listas creadas"
+createdAntennas: "Antenas creadas"
+fromX: "De {x}"
+genEmbedCode: "Obtener el código para incrustar"
+noteOfThisUser: "Notas de este usuario"
+clipNoteLimitExceeded: "No se pueden añadir más notas a este clip."
performance: "Rendimiento"
+modified: "Modificado"
+discard: "Descartar"
+thereAreNChanges: "Hay {n} cambio(s)"
+signinWithPasskey: "Iniciar sesión con clave de acceso"
unknownWebAuthnKey: "Esto no se ha registrado llave maestra."
+passkeyVerificationFailed: "La verificación de la clave de acceso ha fallado."
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificación de la clave de acceso ha sido satisfactoria pero se ha deshabilitado el inicio de sesión sin contraseña."
messageToFollower: "Mensaje a seguidores"
+target: "Para"
federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador."
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
+postForm: "Formulario"
+information: "Información"
+_settings:
+ webhook: "Webhook"
_accountSettings:
requireSigninToViewContents: "Se requiere iniciar sesión para ver el contenido"
requireSigninToViewContentsDescription1: "Requiere iniciar sesión para ver todas las notas y otros contenidos que hayas creado. Se espera que esto evite que los rastreadores recopilen información."
@@ -2495,10 +2522,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Asegúrate de que el distribuidor de este recurso es de confianza antes de proceder a la instalación."
_plugin:
title: "¿Quieres instalar este plugin?"
- metaTitle: "Información del plugin"
_theme:
title: "¿Quieres instalar este tema?"
- metaTitle: "Información del tema"
_meta:
base: "Esquema de color base"
_vendorInfo:
@@ -2560,6 +2585,13 @@ _mediaControls:
pip: "Picture in Picture"
playbackRate: "Velocidad de reproducción"
loop: "Reproducción en bucle"
+_followRequest:
+ recieved: "Petición de seguimiento recibida"
+ sent: "Petición de seguimiento enviada"
_remoteLookupErrors:
_noSuchObject:
title: "No se encuentra"
+_search:
+ searchScopeAll: "Todo"
+ searchScopeLocal: "Local"
+ searchScopeUser: "Especificar usuario"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index ccfd462a76..4bdcd89422 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -1277,6 +1277,8 @@ prohibitedWordsForNameOfUser: "Mots interdits pour les noms d'utilisateur·rices
lockdown: "Verrouiller"
pleaseSelectAccount: "Sélectionner un compte"
availableRoles: "Rôles disponibles"
+postForm: "Formulaire de publication"
+information: "Informations"
_abuseUserReport:
forward: "Transférer"
forwardDescription: "Transférer le signalement vers une instance distante en tant qu'anonyme."
@@ -2294,10 +2296,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Veuillez confirmer que le distributeur est fiable avant l'installation."
_plugin:
title: "Voulez-vous installer cette extension ?"
- metaTitle: "Informations sur l'extension"
_theme:
title: "Voulez-vous installer ce thème ?"
- metaTitle: "Informations sur le thème"
_meta:
base: "Palette de couleurs de base"
_vendorInfo:
@@ -2364,3 +2364,7 @@ _embedCodeGen:
_remoteLookupErrors:
_noSuchObject:
title: "Non trouvé"
+_search:
+ searchScopeAll: "Tous"
+ searchScopeLocal: "Local"
+ searchScopeUser: "Spécifier l'utilisateur·rice"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 7be56b1494..9a291ca381 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -1261,6 +1261,10 @@ performance: "Kinerja"
modified: "Diubah"
thereAreNChanges: "Ada {n} perubahan"
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
+postForm: "Buat catatan"
+information: "Informasi"
+_settings:
+ webhook: "Webhook"
_abuseUserReport:
accept: "Setuju"
reject: "Tolak"
@@ -2489,10 +2493,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Pastikan sumber dari sumber daya ini terpercaya sebelum melakukan pemasangan."
_plugin:
title: "Apakah kamu ingin memasang plugin ini?"
- metaTitle: "Informasi plugin"
_theme:
title: "Apakah kamu ingin memasang tema ini?"
- metaTitle: "Informasi tema"
_meta:
base: "Skema warna dasar"
_vendorInfo:
@@ -2610,3 +2612,7 @@ _mediaControls:
_remoteLookupErrors:
_noSuchObject:
title: "Tidak dapat ditemukan"
+_search:
+ searchScopeAll: "Semua"
+ searchScopeLocal: "Lokal"
+ searchScopeUser: "Pengguna spesifik"
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 4f75e0997c..3c8533b7df 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1175,10 +1175,6 @@ export interface Locale extends ILocale {
*/
"saved": string;
/**
- * チャット
- */
- "messaging": string;
- /**
* アップロード
*/
"upload": string;
@@ -1227,9 +1223,9 @@ export interface Locale extends ILocale {
*/
"noMoreHistory": string;
/**
- * チャットを開始
+ * チャットを始める
*/
- "startMessaging": string;
+ "startChat": string;
/**
* {n}人が読みました
*/
@@ -1983,14 +1979,6 @@ export interface Locale extends ILocale {
*/
"attachAsFileQuestion": string;
/**
- * まだチャットはありません
- */
- "noMessagesYet": string;
- /**
- * 新しいメッセージがあります
- */
- "newMessageExists": string;
- /**
* メッセージに添付できるファイルはひとつです
*/
"onlyOneFileCanBeAttached": string;
@@ -2811,6 +2799,10 @@ export interface Locale extends ILocale {
*/
"copy": string;
/**
+ * クリップボードにコピーされました
+ */
+ "copiedToClipboard": string;
+ /**
* メトリクス
*/
"metrics": string;
@@ -4972,7 +4964,7 @@ export interface Locale extends ILocale {
*/
"disableStreamingTimeline": string;
/**
- * 通知をグルーピングして表示する
+ * 通知をグルーピング
*/
"useGroupedNotifications": string;
/**
@@ -5255,6 +5247,415 @@ export interface Locale extends ILocale {
* このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。
*/
"federationDisabled": string;
+ /**
+ * リアクションする際に確認する
+ */
+ "confirmOnReact": string;
+ /**
+ * " {emoji} " をリアクションしますか?
+ */
+ "reactAreYouSure": ParameterizedString<"emoji">;
+ /**
+ * このメディアをセンシティブとして設定しますか?
+ */
+ "markAsSensitiveConfirm": string;
+ /**
+ * このメディアのセンシティブ指定を解除しますか?
+ */
+ "unmarkAsSensitiveConfirm": string;
+ /**
+ * 環境設定
+ */
+ "preferences": string;
+ /**
+ * アクセシビリティ
+ */
+ "accessibility": string;
+ /**
+ * 設定のプロファイル
+ */
+ "preferencesProfile": string;
+ /**
+ * 設定IDをコピー
+ */
+ "copyPreferenceId": string;
+ /**
+ * 初期値に戻す
+ */
+ "resetToDefaultValue": string;
+ /**
+ * アカウントで上書き
+ */
+ "overrideByAccount": string;
+ /**
+ * 無題
+ */
+ "untitled": string;
+ /**
+ * 名前はありません
+ */
+ "noName": string;
+ /**
+ * スキップ
+ */
+ "skip": string;
+ /**
+ * 復元
+ */
+ "restore": string;
+ /**
+ * デバイス間で同期
+ */
+ "syncBetweenDevices": string;
+ /**
+ * サーバーに設定値が存在します
+ */
+ "preferenceSyncConflictTitle": string;
+ /**
+ * 同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どちらの設定値で上書きしますか?
+ */
+ "preferenceSyncConflictText": string;
+ /**
+ * サーバーの設定値
+ */
+ "preferenceSyncConflictChoiceServer": string;
+ /**
+ * デバイスの設定値
+ */
+ "preferenceSyncConflictChoiceDevice": string;
+ /**
+ * 同期の有効化をキャンセル
+ */
+ "preferenceSyncConflictChoiceCancel": string;
+ /**
+ * ペースト
+ */
+ "paste": string;
+ /**
+ * 絵文字パレット
+ */
+ "emojiPalette": string;
+ /**
+ * 投稿フォーム
+ */
+ "postForm": string;
+ /**
+ * 文字数
+ */
+ "textCount": string;
+ /**
+ * 情報
+ */
+ "information": string;
+ /**
+ * チャット
+ */
+ "chat": string;
+ /**
+ * 旧設定情報を移行
+ */
+ "migrateOldSettings": string;
+ /**
+ * 通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。
+ */
+ "migrateOldSettings_description": string;
+ "_chat": {
+ /**
+ * まだメッセージはありません
+ */
+ "noMessagesYet": string;
+ /**
+ * 新しいメッセージ
+ */
+ "newMessage": string;
+ /**
+ * 個人チャット
+ */
+ "individualChat": string;
+ /**
+ * 特定ユーザーとの一対一のチャットができます。
+ */
+ "individualChat_description": string;
+ /**
+ * ルームチャット
+ */
+ "roomChat": string;
+ /**
+ * 複数人でのチャットができます。
+ * また、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。
+ */
+ "roomChat_description": string;
+ /**
+ * ルームを作成
+ */
+ "createRoom": string;
+ /**
+ * ユーザーを招待してチャットを始めましょう
+ */
+ "inviteUserToChat": string;
+ /**
+ * 作成したルーム
+ */
+ "yourRooms": string;
+ /**
+ * 参加中のルーム
+ */
+ "joiningRooms": string;
+ /**
+ * 招待
+ */
+ "invitations": string;
+ /**
+ * 招待はありません
+ */
+ "noInvitations": string;
+ /**
+ * 履歴
+ */
+ "history": string;
+ /**
+ * 履歴はありません
+ */
+ "noHistory": string;
+ /**
+ * ルームはありません
+ */
+ "noRooms": string;
+ /**
+ * ユーザーを招待
+ */
+ "inviteUser": string;
+ /**
+ * 参加
+ */
+ "join": string;
+ /**
+ * 無視
+ */
+ "ignore": string;
+ /**
+ * ルームから退出
+ */
+ "leave": string;
+ /**
+ * メンバー
+ */
+ "members": string;
+ /**
+ * メッセージを検索
+ */
+ "searchMessages": string;
+ /**
+ * ホーム
+ */
+ "home": string;
+ /**
+ * このルームをミュート
+ */
+ "muteThisRoom": string;
+ /**
+ * このユーザーとのチャットを開始できません
+ */
+ "cannotChatWithTheUser": string;
+ /**
+ * チャットが使えない状態になっているか、相手がチャットを開放していません。
+ */
+ "cannotChatWithTheUser_description": string;
+ /**
+ * チャットする
+ */
+ "chatWithThisUser": string;
+ /**
+ * このユーザーはフォロワーからのみチャットを受け付けています。
+ */
+ "thisUserAllowsChatOnlyFromFollowers": string;
+ /**
+ * このユーザーはフォローしているユーザーからのみチャットを受け付けています。
+ */
+ "thisUserAllowsChatOnlyFromFollowing": string;
+ /**
+ * このユーザーは相互フォローのユーザーからのみチャットを受け付けています。
+ */
+ "thisUserAllowsChatOnlyFromMutualFollowing": string;
+ /**
+ * このユーザーは誰からもチャットを受け付けていません。
+ */
+ "thisUserNotAllowedChatAnyone": string;
+ /**
+ * チャットを許可する相手
+ */
+ "chatAllowedUsers": string;
+ /**
+ * 自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。
+ */
+ "chatAllowedUsers_note": string;
+ "_chatAllowedUsers": {
+ /**
+ * 誰でも
+ */
+ "everyone": string;
+ /**
+ * 自分のフォロワーのみ
+ */
+ "followers": string;
+ /**
+ * 自分がフォローしているユーザーのみ
+ */
+ "following": string;
+ /**
+ * 相互フォローのユーザーのみ
+ */
+ "mutual": string;
+ /**
+ * 誰も許可しない
+ */
+ "none": string;
+ };
+ };
+ "_emojiPalette": {
+ /**
+ * パレット
+ */
+ "palettes": string;
+ /**
+ * パレットのデバイス間同期を有効にする
+ */
+ "enableSyncBetweenDevicesForPalettes": string;
+ /**
+ * メインで使用するパレット
+ */
+ "paletteForMain": string;
+ /**
+ * リアクションで使用するパレット
+ */
+ "paletteForReaction": string;
+ };
+ "_settings": {
+ /**
+ * ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。
+ */
+ "driveBanner": string;
+ /**
+ * プラグインを利用するとクライアントの機能を拡張することができます。プラグインのインストール、個別の設定と管理が行えます。
+ */
+ "pluginBanner": string;
+ /**
+ * サーバーからの受信する通知の種類と範囲や、プッシュ通知の設定が行えます。
+ */
+ "notificationsBanner": string;
+ /**
+ * API
+ */
+ "api": string;
+ /**
+ * Webhook
+ */
+ "webhook": string;
+ /**
+ * サービス連携
+ */
+ "serviceConnection": string;
+ /**
+ * 外部のアプリ・サービスと連携するためのアクセストークンやWebhookの管理と設定が行えます。
+ */
+ "serviceConnectionBanner": string;
+ /**
+ * アカウントのデータ
+ */
+ "accountData": string;
+ /**
+ * アカウントデータのアーカイブをエクスポート/インポートして管理できます。
+ */
+ "accountDataBanner": string;
+ /**
+ * 非表示にするコンテンツの設定や、特定のユーザーからのアクションを制限する設定と管理を行えます。
+ */
+ "muteAndBlockBanner": string;
+ /**
+ * クライアントの視覚や動作に関するパーソナライズを行い、より最適に使用できるように設定できます。
+ */
+ "accessibilityBanner": string;
+ /**
+ * コンテンツの公開範囲、見つけやすさ、フォローの承認制などアカウントのプライバシーに関する設定を行えます。
+ */
+ "privacyBanner": string;
+ /**
+ * パスワード、ログイン方法、認証アプリ、パスキーなどアカウントのセキュリティに関する設定を行えます。
+ */
+ "securityBanner": string;
+ /**
+ * 好みに応じた、クライアントの全体的な動作の設定が行えます。
+ */
+ "preferencesBanner": string;
+ /**
+ * 好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。
+ */
+ "appearanceBanner": string;
+ /**
+ * クライアントで再生するサウンドの設定が行えます。
+ */
+ "soundsBanner": string;
+ /**
+ * タイムラインとノート
+ */
+ "timelineAndNote": string;
+ /**
+ * 全てのテキスト要素を選択可能にする
+ */
+ "makeEveryTextElementsSelectable": string;
+ /**
+ * 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
+ */
+ "makeEveryTextElementsSelectable_description": string;
+ };
+ "_preferencesProfile": {
+ /**
+ * プロファイル名
+ */
+ "profileName": string;
+ /**
+ * このデバイスを識別する名前を設定してください。
+ */
+ "profileNameDescription": string;
+ /**
+ * 例: 「メインPC」、「スマホ」など
+ */
+ "profileNameDescription2": string;
+ };
+ "_preferencesBackup": {
+ /**
+ * 自動バックアップ
+ */
+ "autoBackup": string;
+ /**
+ * バックアップから復元
+ */
+ "restoreFromBackup": string;
+ /**
+ * バックアップが見つかりませんでした
+ */
+ "noBackupsFoundTitle": string;
+ /**
+ * 自動で作成されたバックアップは見つかりませんでしたが、バックアップファイルを手動で保存している場合、それをインポートして復元することはできます。
+ */
+ "noBackupsFoundDescription": string;
+ /**
+ * 復元するバックアップを選択してください
+ */
+ "selectBackupToRestore": string;
+ /**
+ * 自動バックアップを有効にするにはプロファイル名の設定が必要です。
+ */
+ "youNeedToNameYourProfileToEnableAutoBackup": string;
+ /**
+ * このデバイスで設定の自動バックアップは有効になっていません。
+ */
+ "autoPreferencesBackupIsNotEnabledForThisDevice": string;
+ /**
+ * 設定のバックアップが見つかりました
+ */
+ "backupFound": string;
+ };
"_accountSettings": {
/**
* コンテンツの表示にログインを必須にする
@@ -5293,6 +5694,10 @@ export interface Locale extends ILocale {
*/
"mayNotEffectForFederatedNotes": string;
/**
+ * これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。
+ */
+ "mayNotEffectSomeSituations": string;
+ /**
* 指定した時間を経過しているノート
*/
"notesHavePassedSpecifiedPeriod": string;
@@ -7058,6 +7463,10 @@ export interface Locale extends ILocale {
*/
"canImportUserLists": string;
/**
+ * チャットを許可
+ */
+ "canChat": string;
+ /**
* Can view the bubble timeline
*/
"btlAvailable": string;
@@ -7707,6 +8116,10 @@ export interface Locale extends ILocale {
*/
"builtinThemes": string;
/**
+ * サーバーのテーマ
+ */
+ "instanceTheme": string;
+ /**
* そのテーマは既にインストールされています
*/
"alreadyInstalled": string;
@@ -7966,6 +8379,10 @@ export interface Locale extends ILocale {
* リアクション選択時
*/
"reaction": string;
+ /**
+ * チャットのメッセージ
+ */
+ "chatMessage": string;
};
"_soundSettings": {
/**
@@ -8539,6 +8956,14 @@ export interface Locale extends ILocale {
*/
"write:report-abuse": string;
/**
+ * チャットを操作する
+ */
+ "write:chat": string;
+ /**
+ * チャットを閲覧する
+ */
+ "read:chat": string;
+ /**
* Approve new users
*/
"write:admin:approve-user": string;
@@ -8551,7 +8976,7 @@ export interface Locale extends ILocale {
*/
"write:admin:nsfw-user": string;
/**
- * Mark users an not NSFW
+ * Mark users as not NSFW
*/
"write:admin:unnsfw-user": string;
/**
@@ -9628,6 +10053,14 @@ export interface Locale extends ILocale {
* ログインがありました
*/
"login": string;
+ /**
+ * アクセストークンが作成されました
+ */
+ "createToken": string;
+ /**
+ * 心当たりがない場合は「{text}」を通じてアクセストークンを削除してください。
+ */
+ "createTokenDescription": ParameterizedString<"text">;
"_types": {
/**
* すべて
@@ -9690,6 +10123,10 @@ export interface Locale extends ILocale {
*/
"login": string;
/**
+ * アクセストークンの作成
+ */
+ "createToken": string;
+ /**
* 通知のテスト
*/
"test": string;
@@ -9818,6 +10255,10 @@ export interface Locale extends ILocale {
* 幅を自動調整
*/
"flexible": string;
+ /**
+ * プロファイル情報のデバイス間同期を有効にする
+ */
+ "enableSyncBetweenDevicesForProfiles": string;
"_columns": {
/**
* メイン
@@ -10227,6 +10668,10 @@ export interface Locale extends ILocale {
*/
"deleteGalleryPost": string;
/**
+ * プロキシアカウントの説明を更新
+ */
+ "updateProxyAccountDescription": string;
+ /**
* Approved
*/
"approve": string;
@@ -10243,7 +10688,7 @@ export interface Locale extends ILocale {
*/
"setRemoteInstanceNSFW": string;
/**
- * Set remote instance as NSFW
+ * Unset remote instance as NSFW
*/
"unsetRemoteInstanceNSFW": string;
/**
@@ -10367,20 +10812,12 @@ export interface Locale extends ILocale {
* このプラグインをインストールしますか?
*/
"title": string;
- /**
- * プラグイン情報
- */
- "metaTitle": string;
};
"_theme": {
/**
* このテーマをインストールしますか?
*/
"title": string;
- /**
- * テーマ情報
- */
- "metaTitle": string;
};
"_meta": {
/**
@@ -11114,11 +11551,11 @@ export interface Locale extends ILocale {
};
"_followRequest": {
/**
- * 受け取った申請
+ * Received
*/
"recieved": string;
/**
- * 送った申請
+ * Sent
*/
"sent": string;
};
@@ -11160,13 +11597,7 @@ export interface Locale extends ILocale {
*/
"title": string;
/**
- * このサーバーと通信することはできましたが、得られたデータが不正なものでした。
- */
- "description": string;
- };
- "_responseInvalidIdHostNotMatch": {
- /**
- * 入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。
+ * このサーバーと通信することはできましたが、得られたデータが不正なものでした。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。
*/
"description": string;
};
@@ -11224,6 +11655,82 @@ export interface Locale extends ILocale {
};
};
};
+ "_bootErrors": {
+ /**
+ * 読み込みに失敗しました
+ */
+ "title": string;
+ /**
+ * 少し待ってからリロードしてもまだ問題が解決されない場合、以下のError IDを添えてサーバー管理者に連絡してください。
+ */
+ "serverError": string;
+ /**
+ * 以下を行うと解決する可能性があります。
+ */
+ "solution": string;
+ /**
+ * ブラウザおよびOSを最新バージョンに更新する
+ */
+ "solution1": string;
+ /**
+ * アドブロッカーを無効にする
+ */
+ "solution2": string;
+ /**
+ * ブラウザのキャッシュをクリアする
+ */
+ "solution3": string;
+ /**
+ * (Tor Browser) dom.webaudio.enabledをtrueに設定する
+ */
+ "solution4": string;
+ /**
+ * その他のオプション
+ */
+ "otherOption": string;
+ /**
+ * クライアント設定とキャッシュを削除
+ */
+ "otherOption1": string;
+ /**
+ * 簡易クライアントを起動
+ */
+ "otherOption2": string;
+ /**
+ * 修復ツールを起動
+ */
+ "otherOption3": string;
+ };
+ "_search": {
+ /**
+ * 全て
+ */
+ "searchScopeAll": string;
+ /**
+ * ローカル
+ */
+ "searchScopeLocal": string;
+ /**
+ * サーバー指定
+ */
+ "searchScopeServer": string;
+ /**
+ * ユーザー指定
+ */
+ "searchScopeUser": string;
+ /**
+ * サーバーのホストを入力してください
+ */
+ "pleaseEnterServerHost": string;
+ /**
+ * ユーザーを選択してください
+ */
+ "pleaseSelectUser": string;
+ /**
+ * 例: misskey.example.com
+ */
+ "serverHostPlaceholder": string;
+ };
/**
* Approvals
*/
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index c233e3ab87..4d17d0bf7b 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -126,7 +126,7 @@ pinnedNote: "Nota in primo piano"
pinned: "Fissa sul profilo"
you: "Tu"
clickToShow: "Contenuto occultato, cliccare solo se si intende vedere"
-sensitive: "Allegato esplicito"
+sensitive: "Esplicito"
add: "Aggiungi"
reaction: "Reazioni"
reactions: "Reazioni"
@@ -228,7 +228,7 @@ jobQueue: "Coda di lavoro"
cpuAndMemory: "CPU e Memoria"
network: "Rete"
disk: "Disco"
-instanceInfo: "Informazioni sull'istanza"
+instanceInfo: "Informazioni sul server"
statistics: "Statistiche"
clearQueue: "Svuota coda"
clearQueueConfirmTitle: "Vuoi davvero svuotare la coda?"
@@ -445,7 +445,7 @@ exploreFediverse: "Esplora il Fediverso"
popularTags: "Hashtag popolari"
userList: "Liste"
about: "Informazioni"
-aboutMisskey: "Informazioni di Misskey"
+aboutMisskey: "A proposito di Misskey"
administrator: "Amministratore"
token: "Token"
2fa: "Autenticazione a due fattori"
@@ -606,7 +606,7 @@ scratchpad: "ScratchPad"
scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey."
uiInspector: "UI Inspector"
uiInspectorDescription: "Puoi visualizzare un elenco di elementi UI presenti in memoria. I componenti dell'interfaccia utente vengono generati dalle funzioni Ui:C:."
-output: "Uscita"
+output: "Output"
script: "Script"
disablePagesScript: "Disabilita AiScript nelle pagine"
updateRemoteUser: "Aggiorna dati dal profilo remoto"
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\""
makeActive: "Attiva"
display: "Visualizza"
copy: "Copia"
+copiedToClipboard: "Copiato negli appunti"
metrics: "Statistiche"
overview: "Anteprima"
logs: "Log"
@@ -766,7 +767,7 @@ driveUsage: "Utilizzazione del Drive"
noCrawle: "Rifiuta l'indicizzazione dai robot."
noCrawleDescription: "Richiedi che i motori di ricerca non indicizzino la tua pagina di profilo, le tue note, pagine, ecc."
lockedAccountInfo: "A meno che non imposti la visibilità delle tue note su \"Solo ai follower\", le tue note sono visibili da tutti, anche se hai configurato l'account per confermare manualmente le richieste di follow."
-alwaysMarkSensitive: "Segnare gli allegati come espliciti come opzione predefinita"
+alwaysMarkSensitive: "Segnare automaticamente come espliciti gli allegati"
loadRawImages: "Visualizza le intere immagini allegate invece delle miniature."
disableShowingAnimatedImages: "Disabilita le immagini animate"
highlightSensitiveMedia: "Evidenzia i media espliciti"
@@ -893,7 +894,7 @@ searchResult: "Risultati della Ricerca"
hashtags: "Hashtag"
troubleshooting: "Risoluzione problemi"
useBlurEffect: "Utilizza effetto sfocatura"
-learnMore: "Più dettagli"
+learnMore: "Per saperne di più"
misskeyUpdated: "Misskey è stato aggiornato!"
whatIsNew: "Informazioni sull'aggiornamento"
translate: "Traduci"
@@ -901,7 +902,7 @@ translatedFrom: "Traduzione da {x}"
accountDeletionInProgress: "È in corso l'eliminazione del profilo"
usernameInfo: "Un nome per identificare univocamente il tuo profilo sull'istanza. Puoi utilizzare caratteri alfanumerici maiuscoli, minuscoli e il trattino basso (_). Non potrai cambiare nome utente in seguito."
aiChanMode: "Modalità Ai"
-devMode: "Modalità sviluppatori"
+devMode: "Modalità sviluppo"
keepCw: "Mostra i contenuti espliciti"
pubSub: "Publish/Subscribe del profilo"
lastCommunication: "La comunicazione più recente"
@@ -973,7 +974,7 @@ check: "Verifica"
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"
+isSystemAccount: "Si tratta di un profilo creato e gestito automaticamente dal sistema."
typeToConfirm: "Digita {x} per continuare"
deleteAccount: "Eliminazione profilo"
document: "Documentazione"
@@ -1049,7 +1050,7 @@ permissionDeniedError: "Errore, attività non autorizzata"
permissionDeniedErrorDescription: "Non si dispone dell'autorizzazione per eseguire questa operazione."
preset: "Preimpostato"
selectFromPresets: "Seleziona preimpostato"
-achievements: "Obiettivi raggiunti"
+achievements: "Conquiste"
gotInvalidResponseError: "Risposta del server non valida"
gotInvalidResponseErrorDescription: "Il server potrebbe essere irraggiungibile o in manutenzione. Riprova più tardi."
thisPostMayBeAnnoying: "Questa nota potrebbe essere offensiva"
@@ -1090,7 +1091,7 @@ notesSearchNotAvailable: "Non è possibile cercare tra le Note."
license: "Licenza"
unfavoriteConfirm: "Vuoi davvero rimuovere la preferenza?"
myClips: "Le mie Clip"
-drivecleaner: "Drive cleaner"
+drivecleaner: "Pulizia del Drive"
retryAllQueuesNow: "Ritenta di consumare tutte le code"
retryAllQueuesConfirmTitle: "Vuoi ritentare adesso?"
retryAllQueuesConfirmText: "Potrebbe sovraccaricare il server temporaneamente."
@@ -1309,6 +1310,69 @@ availableRoles: "Ruoli disponibili"
acknowledgeNotesAndEnable: "Attivare dopo averne compreso il comportamento."
federationSpecified: "Questo server è federato solo con istanze specifiche del Fediverso. Puoi interagire solo con quelle scelte dall'amministrazione."
federationDisabled: "Questo server ha la federazione disabilitata. Non puoi interagire con profili provenienti da altri server."
+confirmOnReact: "Confermare le reazioni"
+reactAreYouSure: "Vuoi davvero reagire con {emoji} ?"
+markAsSensitiveConfirm: "Vuoi davvero indicare questo contenuto multimediale come esplicito?"
+unmarkAsSensitiveConfirm: "Vuoi davvero indicare come non esplicito il contenuto multimediale?"
+preferences: "Preferenze"
+accessibility: "Accessibilità"
+preferencesProfile: "Profilo preferenze"
+copyPreferenceId: "Copia ID preferenze"
+resetToDefaultValue: "Ripristina a predefinito"
+overrideByAccount: "Sovrascrivere col profilo"
+untitled: "Senza titolo"
+noName: "Senza nome"
+skip: "Salta"
+restore: "Ripristina"
+syncBetweenDevices: "Sincronizzazione tra i dispositivi"
+preferenceSyncConflictTitle: "Sul server esiste già il valore impostato"
+preferenceSyncConflictText: "Le impostazione sincronizzata salverà il valore sul server. Però, bada che esiste già un valore sul server. Quale vorresti sovrascrivere?"
+preferenceSyncConflictChoiceServer: "Valore del server"
+preferenceSyncConflictChoiceDevice: "Valore del dispositivo"
+preferenceSyncConflictChoiceCancel: "Annulla la sincronizzazione"
+paste: "Incolla"
+emojiPalette: "Tavolozza emoji"
+postForm: "Finestra di pubblicazione"
+textCount: "Il numero di caratteri"
+information: "Informazioni"
+_emojiPalette:
+ palettes: "Tavolozza"
+ enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi"
+ paletteForMain: "Tavolozza principale"
+ paletteForReaction: "Tavolozza per reazioni"
+_settings:
+ driveBanner: "Permette di gestire e configurare il Drive, controllare il consumo di spazio e configurare il caricamento dei file."
+ pluginBanner: "Consentono di migliorare le funzionalità. Le estensioni si possono configurare e gestire singolarmente."
+ notificationsBanner: "Puoi impostare il tipo di notifiche da ricevere dal server e anche le notifiche push."
+ api: "API"
+ webhook: "Webhook"
+ serviceConnection: "Integrazione servizi"
+ serviceConnectionBanner: "Puoi gestire i codici di accesso e i Webhook per collegare App o servizi esterni."
+ accountData: "Dati del profilo"
+ accountDataBanner: "Puoi gestire i dati del tuo profilo, esportando e importando."
+ muteAndBlockBanner: "Puoi configurare la visibiltà dei contenuti e limitare le attività provenienti da profili specifici."
+ accessibilityBanner: "Puoi personalizzare e migliorare la lettura sul tuo dispositivo in modo che sia più chiaro e reattivo."
+ privacyBanner: "Puoi configurare la privacy del tuo profilo, come la visibilità delle Note, la visibilità del profilo nelle ricerche e l'approvazione delle relazioni tra profili."
+ securityBanner: "Puoi gestire la sicurezza del tuo account, la password, i modi di accesso, la generazione di codici OTP per accesso multi fattore (MFA/2FA) e la passkey."
+ preferencesBanner: "Puoi personalizzare il comportamento del tuo dispositivo."
+ appearanceBanner: "Puoi personalizzare l'aspetto nel dispositivo, in base alle tue preferenze."
+ soundsBanner: "Puoi personalizzare i suoni emessi dagli eventi sul tuo dispositivo."
+ timelineAndNote: "Note e Timeline"
+ makeEveryTextElementsSelectable: "Imposta ogni elemento come selezionabile"
+ makeEveryTextElementsSelectable_description: "Potrebbe ridurre l'usabilità in alcune situazioni."
+_preferencesProfile:
+ profileName: "Nome del profilo"
+ profileNameDescription: "Impostare il nome che indentifica questo dispositivo."
+ profileNameDescription2: "Es: \"PC principale\" o \"Cellulare\""
+_preferencesBackup:
+ autoBackup: "Backup automatico"
+ restoreFromBackup: "Ripristinare da backup"
+ noBackupsFoundTitle: "Nessun backup trovato"
+ noBackupsFoundDescription: "Impossibile trovare un backup creato automaticamente. Se se hai salvato il file di backup manualmente, puoi importarlo e ripristinarlo."
+ selectBackupToRestore: "Seleziona un backup da ripristinare"
+ youNeedToNameYourProfileToEnableAutoBackup: "Per abilitare i backup automatici, è necessario indicare il nome del profilo."
+ autoPreferencesBackupIsNotEnabledForThisDevice: "Su questo dispositivo non è stato attivato il backup automatico delle preferenze."
+ backupFound: "Esiste il Backup delle preferenze"
_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."
@@ -1319,6 +1383,7 @@ _accountSettings:
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."
+ mayNotEffectSomeSituations: "Queste restrizioni sono semplificate. In alcuni casi, potrebbero anche non avvenire. Ad esempio visionando un server remoto o durante la moderazione."
notesHavePassedSpecifiedPeriod: "Note antecedenti al periodo specificato"
notesOlderThanSpecifiedDateAndTime: "Note antecedenti al momento specificato"
_abuseUserReport:
@@ -1445,9 +1510,9 @@ _initialTutorial:
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 (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."
+ local: "La Timeline Locale è un flusso di Note pubblicate dai profili iscritti a questo server."
+ social: "La Timeline Sociale elenca, in ordine cronologico, il flusso di Note nella Timeline Home e Locale."
+ global: "Nella Timeline Federata trovi il flusso di Note provenienti da profili iscritti ad altri server, federati a questo."
_serverRules:
description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio."
_serverSettings:
@@ -1471,8 +1536,8 @@ _serverSettings:
_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 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"
+ moveFromLabel: "Profilo da cui migrare n. {n}"
+ 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@vecchia.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."
@@ -1548,13 +1613,13 @@ _achievements:
title: "Principiante III"
description: "Hai totalizzato 15 accessi!"
_login30:
- title: "Misskist I"
+ title: "Missalcolista I"
description: "Hai totalizzato 30 accessi!"
_login60:
- title: "Misskeist II"
+ title: "Missalcolista II"
description: "Hai totalizzato 60 accessi!"
_login100:
- title: "Misskeist III"
+ title: "Missalcolista III"
description: "Hai totalizzato 100 accessi!"
flavor: "Violent Misskeist"
_login200:
@@ -1640,10 +1705,10 @@ _achievements:
description: "Hai superato i 1.000 profili Follower"
_collectAchievements30:
title: "Collezionista di successi"
- description: "Hai raggiunto 30 obiettivi"
+ description: "Hai raggiunto 30 conquiste"
_viewAchievements3min:
title: "Mi piacciono i risultati"
- description: "Guarda la tua collezione di obiettivi per almeno 3 minuti"
+ description: "Ammira la tua collezione di conquiste per almeno 3 minuti"
_iLoveMisskey:
title: "I LOVE Misskey"
description: "Pubblica «I ♥ #Misskey»"
@@ -1908,7 +1973,7 @@ _registry:
domain: "Dominio"
createKey: "Crea chiave"
_aboutMisskey:
- about: "Misskey è un software libero e open source, sviluppato da syuilo dal 2014."
+ about: "Misskey è software libero, open source, sviluppato da Syuilo fin dal lontano 2014."
contributors: "Principali sostenitori"
allContributors: "Tutti i sostenitori"
source: "Codice sorgente"
@@ -1967,6 +2032,7 @@ _theme:
installed: "{name} è installato"
installedThemes: "Temi installati"
builtinThemes: "Temi integrati"
+ instanceTheme: "Tema dell'istanza"
alreadyInstalled: "Questo tema è già installato"
invalid: "Il formato tema non è valido"
make: "Crea un tema"
@@ -2235,7 +2301,7 @@ _widgets:
userList: "Elenco utenti"
_userList:
chooseList: "Seleziona una lista"
- clicker: "Cliccaggio"
+ clicker: "Cliccheria"
birthdayFollowings: "Compleanni del giorno"
_cw:
hide: "Nascondere"
@@ -2298,7 +2364,7 @@ _profile:
metadataContent: "Contenuto"
changeAvatar: "Modifica immagine profilo"
changeBanner: "Cambia intestazione"
- verifiedLinkDescription: "Puoi verificare il tuo profilo mostrando una icona. Devi inserire la URL alla pagina che contiene un link al tuo profilo."
+ verifiedLinkDescription: "Puoi verificare il tuo profilo mostrando una icona. Devi inserire la URL alla pagina che contiene un link al tuo profilo.\nPer verificare il profilo tramite la spunta di conferma, devi inserire la url alla pagina che contiene un link al tuo profilo Misskey. Deve avere attributo rel='me'."
avatarDecorationMax: "Puoi aggiungere fino a {max} decorazioni."
followedMessage: "Messaggio, quando qualcuno ti segue"
followedMessageDescription: "Puoi impostare un breve messaggio da mostrare agli altri profili quando ti seguono."
@@ -2440,6 +2506,8 @@ _notification:
flushNotification: "Azzera le notifiche"
exportOfXCompleted: "Abbiamo completato l'esportazione di {x}"
login: "Autenticazione avvenuta"
+ createToken: "È stato creato un token di accesso"
+ createTokenDescription: "In caso contrario, eliminare il token di accesso tramite ({text})."
_types:
all: "Tutto"
note: "Nuove Note"
@@ -2456,6 +2524,7 @@ _notification:
achievementEarned: "Risultato raggiunto"
exportCompleted: "Esportazione completata"
login: "Accessi"
+ createToken: "Creare un token di accesso"
test: "Notifiche di test"
app: "Notifiche da applicazioni"
_actions:
@@ -2483,6 +2552,7 @@ _deck:
useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice"
usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima"
flexible: "Larghezza flessibile"
+ enableSyncBetweenDevicesForProfiles: "Abilita la sincronizzazione delle informazioni profilo tra dispositivi"
_columns:
main: "Principale"
widgets: "Riquadri"
@@ -2501,8 +2571,8 @@ _disabledTimeline:
title: "Timeline disabilitata"
description: "Il ruolo in cui sei non ti permette di leggere questa timeline"
_drivecleaner:
- orderBySizeDesc: "Dal più grande al più piccolo"
- orderByCreatedAtAsc: "Dal più vecchio al più recente"
+ orderBySizeDesc: "Dal file più grosso al più piccolo"
+ orderByCreatedAtAsc: "Dal file più vecchio al più recente"
_webhookSettings:
createWebhook: "Creazione Webhook"
modifyWebhook: "Modifica Webhook"
@@ -2590,6 +2660,7 @@ _moderationLogTypes:
deletePage: "Pagina eliminata"
deleteFlash: "Play eliminato"
deleteGalleryPost: "Eliminazione pubblicazione nella Galleria"
+ updateProxyAccountDescription: "Aggiornata la descrizione del profilo proxy"
_fileViewer:
title: "Dettagli del file"
type: "Tipo di file"
@@ -2603,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Prima di installare, assicurati che la fonte sia affidabile."
_plugin:
title: "Vuoi davvero installare questo componente aggiuntivo?"
- metaTitle: "Informazioni sul componente aggiuntivo"
_theme:
title: "Vuoi davvero installare questa variazione grafica?"
- metaTitle: "Informazioni sulla variazione grafica"
_meta:
base: "Combinazione base di colori"
_vendorInfo:
@@ -2807,8 +2876,8 @@ _selfXssPrevention:
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}"
_followRequest:
- recieved: "Ricezione richiesta di Follow"
- sent: "Richiesta di Follow, inviata"
+ recieved: "Richieste in ingresso"
+ sent: "Richieste in uscita"
_remoteLookupErrors:
_federationNotAllowed:
title: "Server irraggiungibile"
@@ -2822,8 +2891,6 @@ _remoteLookupErrors:
_responseInvalid:
title: "Risposta non valida"
description: "La comunicazione col server è andata a buon fine, ma abbiamo ricevuto dati non validi."
- _responseInvalidIdHostNotMatch:
- description: "L'indirizzo immesso non coincide con la URL finale. Interrogando i server per un contenuto remoto, assicurarsi di utilizzare la URL finale e non quella di un server intermedio."
_noSuchObject:
title: "Non trovato"
description: "La risorsa richiesta non è stata trovata. Verificare nuovamente la URL."
@@ -2840,3 +2907,23 @@ _captcha:
_unknown:
title: "Errore CAPTCHA"
text: "Si è verificato un errore imprevisto."
+_bootErrors:
+ title: "Caricamento non riuscito"
+ serverError: "Dopo una breve attesa, e dopo aver ricaricato la pagina, se il problema persiste, contatta l'amministrazione comunicando il seguente ID di errore."
+ solution: "Di seguito, alcune probabili soluzioni al problema."
+ solution1: "Aggiornare browser e il sistema operativo all'ultima versione"
+ solution2: "Disattivare gli adblocker"
+ solution3: "Cancellare la cache del browser"
+ solution4: "(Per chi utilizza il Browser Tor) Impostare dom.webaudio.enabled = vero"
+ otherOption: "Altre opzioni"
+ otherOption1: "Nelle impostazioni, cancellare le impostazioni del client e svuotare la cache"
+ otherOption2: "Avviare il client predefinito"
+ otherOption3: "Avviare lo strumento di riparazione"
+_search:
+ searchScopeAll: "Tutte"
+ searchScopeLocal: "Locale"
+ searchScopeServer: "Specifiche del server"
+ searchScopeUser: "Profilo specifico"
+ pleaseEnterServerHost: "Inserire il nome host"
+ pleaseSelectUser: "Per favore, seleziona un profilo"
+ serverHostPlaceholder: "Es: misskey.example.com"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 13d8aec9b8..9b4d74fbfb 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -289,7 +289,6 @@ deleteAreYouSure: "「{x}」を削除しますか?"
resetAreYouSure: "リセットしますか?"
areYouSure: "よろしいですか?"
saved: "保存しました"
-messaging: "チャット"
upload: "アップロード"
keepOriginalUploading: "オリジナル画像を保持"
keepOriginalUploadingDescription: "画像をアップロードする時にオリジナル版を保持します。オフにするとアップロード時にブラウザでWeb公開用画像を生成します。"
@@ -302,7 +301,7 @@ uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がか
explore: "みつける"
messageRead: "既読"
noMoreHistory: "これより過去の履歴はありません"
-startMessaging: "チャットを開始"
+startChat: "チャットを始める"
nUsersRead: "{n}人が読みました"
agreeTo: "{0}に同意"
agree: "同意する"
@@ -491,8 +490,6 @@ noteOf: "{user}のノート"
quoteAttached: "引用付き"
quoteQuestion: "引用として添付しますか?"
attachAsFileQuestion: "クリップボードのテキストが長いです。テキストファイルとして添付しますか?"
-noMessagesYet: "まだチャットはありません"
-newMessageExists: "新しいメッセージがあります"
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
signinRequired: "続行する前に、登録またはログインが必要です"
signinOrContinueOnRemote: "続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります"
@@ -698,6 +695,7 @@ userSaysSomethingAbout: "{name}が「{word}」について何かを言いまし
makeActive: "アクティブにする"
display: "表示"
copy: "コピー"
+copiedToClipboard: "クリップボードにコピーされました"
metrics: "メトリクス"
overview: "概要"
logs: "ログ"
@@ -1238,7 +1236,7 @@ releaseToRefresh: "離してリロード"
refreshing: "リロード中"
pullDownToRefresh: "引っ張ってリロード"
disableStreamingTimeline: "タイムラインのリアルタイム更新を無効にする"
-useGroupedNotifications: "通知をグルーピングして表示する"
+useGroupedNotifications: "通知をグルーピング"
signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。"
cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。"
doReaction: "リアクションする"
@@ -1309,6 +1307,116 @@ availableRoles: "利用可能なロール"
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。"
federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。"
+confirmOnReact: "リアクションする際に確認する"
+reactAreYouSure: "\" {emoji} \" をリアクションしますか?"
+markAsSensitiveConfirm: "このメディアをセンシティブとして設定しますか?"
+unmarkAsSensitiveConfirm: "このメディアのセンシティブ指定を解除しますか?"
+preferences: "環境設定"
+accessibility: "アクセシビリティ"
+preferencesProfile: "設定のプロファイル"
+copyPreferenceId: "設定IDをコピー"
+resetToDefaultValue: "初期値に戻す"
+overrideByAccount: "アカウントで上書き"
+untitled: "無題"
+noName: "名前はありません"
+skip: "スキップ"
+restore: "復元"
+syncBetweenDevices: "デバイス間で同期"
+preferenceSyncConflictTitle: "サーバーに設定値が存在します"
+preferenceSyncConflictText: "同期が有効にされた設定項目は設定値をサーバーに保存しますが、この設定項目のサーバーに保存された設定値が見つかりました。どちらの設定値で上書きしますか?"
+preferenceSyncConflictChoiceServer: "サーバーの設定値"
+preferenceSyncConflictChoiceDevice: "デバイスの設定値"
+preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
+paste: "ペースト"
+emojiPalette: "絵文字パレット"
+postForm: "投稿フォーム"
+textCount: "文字数"
+information: "情報"
+chat: "チャット"
+migrateOldSettings: "旧設定情報を移行"
+migrateOldSettings_description: "通常これは自動で行われていますが、何らかの理由により上手く移行されなかった場合は手動で移行処理をトリガーできます。現在の設定情報は上書きされます。"
+
+_chat:
+ noMessagesYet: "まだメッセージはありません"
+ newMessage: "新しいメッセージ"
+ individualChat: "個人チャット"
+ individualChat_description: "特定ユーザーとの一対一のチャットができます。"
+ roomChat: "ルームチャット"
+ roomChat_description: "複数人でのチャットができます。\nまた、個人チャットを許可していないユーザーとでも、相手が受け入れればチャットができます。"
+ createRoom: "ルームを作成"
+ inviteUserToChat: "ユーザーを招待してチャットを始めましょう"
+ yourRooms: "作成したルーム"
+ joiningRooms: "参加中のルーム"
+ invitations: "招待"
+ noInvitations: "招待はありません"
+ history: "履歴"
+ noHistory: "履歴はありません"
+ noRooms: "ルームはありません"
+ inviteUser: "ユーザーを招待"
+ join: "参加"
+ ignore: "無視"
+ leave: "ルームから退出"
+ members: "メンバー"
+ searchMessages: "メッセージを検索"
+ home: "ホーム"
+ muteThisRoom: "このルームをミュート"
+ cannotChatWithTheUser: "このユーザーとのチャットを開始できません"
+ cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。"
+ chatWithThisUser: "チャットする"
+ thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみチャットを受け付けています。"
+ thisUserAllowsChatOnlyFromFollowing: "このユーザーはフォローしているユーザーからのみチャットを受け付けています。"
+ thisUserAllowsChatOnlyFromMutualFollowing: "このユーザーは相互フォローのユーザーからのみチャットを受け付けています。"
+ thisUserNotAllowedChatAnyone: "このユーザーは誰からもチャットを受け付けていません。"
+ chatAllowedUsers: "チャットを許可する相手"
+ chatAllowedUsers_note: "自分からチャットメッセージを送った相手とはこの設定に関わらずチャットが可能です。"
+ _chatAllowedUsers:
+ everyone: "誰でも"
+ followers: "自分のフォロワーのみ"
+ following: "自分がフォローしているユーザーのみ"
+ mutual: "相互フォローのユーザーのみ"
+ none: "誰も許可しない"
+
+_emojiPalette:
+ palettes: "パレット"
+ enableSyncBetweenDevicesForPalettes: "パレットのデバイス間同期を有効にする"
+ paletteForMain: "メインで使用するパレット"
+ paletteForReaction: "リアクションで使用するパレット"
+
+_settings:
+ driveBanner: "ドライブの管理と設定、使用量の確認、ファイルをアップロードする際の設定を行えます。"
+ pluginBanner: "プラグインを利用するとクライアントの機能を拡張することができます。プラグインのインストール、個別の設定と管理が行えます。"
+ notificationsBanner: "サーバーからの受信する通知の種類と範囲や、プッシュ通知の設定が行えます。"
+ api: "API"
+ webhook: "Webhook"
+ serviceConnection: "サービス連携"
+ serviceConnectionBanner: "外部のアプリ・サービスと連携するためのアクセストークンやWebhookの管理と設定が行えます。"
+ accountData: "アカウントのデータ"
+ accountDataBanner: "アカウントデータのアーカイブをエクスポート/インポートして管理できます。"
+ muteAndBlockBanner: "非表示にするコンテンツの設定や、特定のユーザーからのアクションを制限する設定と管理を行えます。"
+ accessibilityBanner: "クライアントの視覚や動作に関するパーソナライズを行い、より最適に使用できるように設定できます。"
+ privacyBanner: "コンテンツの公開範囲、見つけやすさ、フォローの承認制などアカウントのプライバシーに関する設定を行えます。"
+ securityBanner: "パスワード、ログイン方法、認証アプリ、パスキーなどアカウントのセキュリティに関する設定を行えます。"
+ preferencesBanner: "好みに応じた、クライアントの全体的な動作の設定が行えます。"
+ appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
+ soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
+ timelineAndNote: "タイムラインとノート"
+ makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
+ makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
+
+_preferencesProfile:
+ profileName: "プロファイル名"
+ profileNameDescription: "このデバイスを識別する名前を設定してください。"
+ profileNameDescription2: "例: 「メインPC」、「スマホ」など"
+
+_preferencesBackup:
+ autoBackup: "自動バックアップ"
+ restoreFromBackup: "バックアップから復元"
+ noBackupsFoundTitle: "バックアップが見つかりませんでした"
+ noBackupsFoundDescription: "自動で作成されたバックアップは見つかりませんでしたが、バックアップファイルを手動で保存している場合、それをインポートして復元することはできます。"
+ selectBackupToRestore: "復元するバックアップを選択してください"
+ youNeedToNameYourProfileToEnableAutoBackup: "自動バックアップを有効にするにはプロファイル名の設定が必要です。"
+ autoPreferencesBackupIsNotEnabledForThisDevice: "このデバイスで設定の自動バックアップは有効になっていません。"
+ backupFound: "設定のバックアップが見つかりました"
_accountSettings:
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
@@ -1320,6 +1428,7 @@ _accountSettings:
makeNotesHiddenBefore: "過去のノートを非公開化する"
makeNotesHiddenBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。"
mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
+ mayNotEffectSomeSituations: "これらの制限は簡易的なものです。リモートサーバーでの閲覧やモデレーション時など、一部のシチュエーションでは適用されない場合があります。"
notesHavePassedSpecifiedPeriod: "指定した時間を経過しているノート"
notesOlderThanSpecifiedDateAndTime: "指定した日時より前のノート"
@@ -1817,6 +1926,7 @@ _role:
canImportFollowing: "フォローのインポートを許可"
canImportMuting: "ミュートのインポートを許可"
canImportUserLists: "リストのインポートを許可"
+ canChat: "チャットを許可"
_condition:
roleAssignedTo: "マニュアルロールにアサイン済み"
isLocal: "ローカルユーザー"
@@ -2001,6 +2111,7 @@ _theme:
installed: "{name}をインストールしました"
installedThemes: "インストールされたテーマ"
builtinThemes: "標準のテーマ"
+ instanceTheme: "サーバーのテーマ"
alreadyInstalled: "そのテーマは既にインストールされています"
invalid: "テーマの形式が間違っています"
make: "テーマを作る"
@@ -2069,6 +2180,7 @@ _sfx:
noteMy: "ノート(自分)"
notification: "通知"
reaction: "リアクション選択時"
+ chatMessage: "チャットのメッセージ"
_soundSettings:
driveFile: "ドライブの音声を使用"
@@ -2221,6 +2333,8 @@ _permissions:
"read:clip-favorite": "クリップのいいねを見る"
"read:federation": "連合に関する情報を取得する"
"write:report-abuse": "違反を報告する"
+ "write:chat": "チャットを操作する"
+ "read:chat": "チャットを閲覧する"
_auth:
shareAccessTitle: "アプリへのアクセス許可"
@@ -2500,6 +2614,8 @@ _notification:
flushNotification: "通知の履歴をリセットする"
exportOfXCompleted: "{x}のエクスポートが完了しました"
login: "ログインがありました"
+ createToken: "アクセストークンが作成されました"
+ createTokenDescription: "心当たりがない場合は「{text}」を通じてアクセストークンを削除してください。"
_types:
all: "すべて"
@@ -2517,6 +2633,7 @@ _notification:
achievementEarned: "実績の獲得"
exportCompleted: "エクスポートが完了した"
login: "ログイン"
+ createToken: "アクセストークンの作成"
test: "通知のテスト"
app: "連携アプリからの通知"
@@ -2546,6 +2663,7 @@ _deck:
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
usedAsMinWidthWhenFlexible: "「幅を自動調整」が有効の場合、これが幅の最小値となります"
flexible: "幅を自動調整"
+ enableSyncBetweenDevicesForProfiles: "プロファイル情報のデバイス間同期を有効にする"
_columns:
main: "メイン"
@@ -2660,6 +2778,7 @@ _moderationLogTypes:
deletePage: "ページを削除"
deleteFlash: "Playを削除"
deleteGalleryPost: "ギャラリーの投稿を削除"
+ updateProxyAccountDescription: "プロキシアカウントの説明を更新"
_fileViewer:
title: "ファイルの詳細"
@@ -2675,10 +2794,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "配布元が信頼できるかを確認した上でインストールしてください。"
_plugin:
title: "このプラグインをインストールしますか?"
- metaTitle: "プラグイン情報"
_theme:
title: "このテーマをインストールしますか?"
- metaTitle: "テーマ情報"
_meta:
base: "基本のカラースキーム"
_vendorInfo:
@@ -2907,9 +3024,7 @@ _remoteLookupErrors:
description: "このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。"
_responseInvalid:
title: "レスポンスが不正です"
- description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。"
- _responseInvalidIdHostNotMatch:
- description: "入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。"
+ description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。"
_noSuchObject:
title: "見つかりません"
description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。"
@@ -2927,3 +3042,25 @@ _captcha:
_unknown:
title: "CAPTCHAエラー"
text: "想定外のエラーが発生しました。"
+
+_bootErrors:
+ title: "読み込みに失敗しました"
+ serverError: "少し待ってからリロードしてもまだ問題が解決されない場合、以下のError IDを添えてサーバー管理者に連絡してください。"
+ solution: "以下を行うと解決する可能性があります。"
+ solution1: "ブラウザおよびOSを最新バージョンに更新する"
+ solution2: "アドブロッカーを無効にする"
+ solution3: "ブラウザのキャッシュをクリアする"
+ solution4: "(Tor Browser) dom.webaudio.enabledをtrueに設定する"
+ otherOption: "その他のオプション"
+ otherOption1: "クライアント設定とキャッシュを削除"
+ otherOption2: "簡易クライアントを起動"
+ otherOption3: "修復ツールを起動"
+
+_search:
+ searchScopeAll: "全て"
+ searchScopeLocal: "ローカル"
+ searchScopeServer: "サーバー指定"
+ searchScopeUser: "ユーザー指定"
+ pleaseEnterServerHost: "サーバーのホストを入力してください"
+ pleaseSelectUser: "ユーザーを選択してください"
+ serverHostPlaceholder: "例: misskey.example.com"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 66560f524b..9c0bff4f95 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -5,6 +5,7 @@ introMisskey: "ようお越し!Misskeyは、オープンソースの分散型
poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Misskey</b>のサーバーのひとつなんやで。"
monthAndDay: "{month}月 {day}日"
search: "探す"
+reset: "リセット"
notifications: "通知"
username: "ユーザー名"
password: "パスワード"
@@ -48,6 +49,7 @@ pin: "ピン留めしとく"
unpin: "ピン留めやめる"
copyContent: "内容をコピー"
copyLink: "リンクをコピー"
+copyRemoteLink: "リモートのリンクをコピーするで?"
copyLinkRenote: "リノートのリンクをコピーするで?"
delete: "ほかす"
deleteAndEdit: "ほかして直す"
@@ -684,11 +686,15 @@ smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する"
smtpSecureInfo: "STARTTLS使っとる時はオフにしてや。"
testEmail: "配信テスト"
wordMute: "ワードミュート"
+wordMuteDescription: "指定した語句が入ってるノートを最小化するで。最小化されたノートをクリックしたら、表示できるようになるで。"
hardWordMute: "ハードワードミュート"
+showMutedWord: "ミュートされたワードを表示するで"
+hardWordMuteDescription: "指定した語句が入ってるノートを隠すで。ワードミュートとちゃうて、ノートは完全に表示されんようになるで。"
regexpError: "正規表現エラー"
regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが出てきたで:"
instanceMute: "サーバーミュート"
userSaysSomething: "{name}が何か言うとるわ"
+userSaysSomethingAbout: "{name}が「{word}」についてなんか言うてたで"
makeActive: "使うで"
display: "表示"
copy: "コピー"
@@ -1301,6 +1307,14 @@ lockdown: "ロックダウン"
pleaseSelectAccount: "アカウント選んでや"
availableRoles: "使えるロール"
acknowledgeNotesAndEnable: "注意事項をわかった上でオンにする。"
+federationSpecified: "このサーバーはホワイトリスト連合で運用されてるで。管理者が指定したサーバー以外とはやり取りできひんで。"
+federationDisabled: "このサーバーは連合が無効化されてるで。他のサーバーのユーザーとやり取りすることはできひんで。"
+confirmOnReact: "ツッコむときに確認とる"
+reactAreYouSure: "\" {emoji} \" でツッコむ?"
+postForm: "投稿フォーム"
+information: "情報"
+_settings:
+ webhook: "Webhook"
_accountSettings:
requireSigninToViewContents: "ログインしてもらってからコンテンツ見てもらう"
requireSigninToViewContentsDescription1: "あなたが作成した全部のノートとかのコンテンツを見れるようにするのにログインがいるようにするで。クローラーにいろいろ収集されるんを防げるかもしれん。"
@@ -2432,6 +2446,8 @@ _notification:
flushNotification: "通知の履歴をリセットする"
exportOfXCompleted: "{x}のエクスポートが終わったわ"
login: "ログインしとったで"
+ createToken: "アクセストークンが作成されたで"
+ createTokenDescription: "心当たりないんやったら「{text}」でアクセストークンを削除してやって。"
_types:
all: "すべて"
note: "あんたらの新規投稿"
@@ -2595,10 +2611,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "配ってるとこが信頼できるか確認した上でインストールしてな。"
_plugin:
title: "このプラグイン、インストールする?"
- metaTitle: "プラグイン情報"
_theme:
title: "このテーマインストールする?"
- metaTitle: "テーマ情報"
_meta:
base: ""
_vendorInfo:
@@ -2718,6 +2732,66 @@ _contextMenu:
app: "アプリ"
appWithShift: "Shiftキーでアプリ"
native: "ブラウザのUI"
+_gridComponent:
+ _error:
+ requiredValue: "この値は必須項目やで"
+ columnTypeNotSupport: "正規表現によるバリデーションはtype:textのカラムだけサポートしてるで"
+ patternNotMatch: "この値は{pattern}のパターンに一致しいひんで"
+ notUnique: "この値は一意でなあかんで"
+_roleSelectDialog:
+ notSelected: "選択されとらんで"
+_customEmojisManager:
+ _gridCommon:
+ copySelectionRows: "選択行をコピーするで"
+ copySelectionRanges: "選択範囲をコピーするで"
+ deleteSelectionRows: "選択行を削除するで"
+ deleteSelectionRanges: "選択範囲の値をクリアするで"
+ searchSettings: "検索設定"
+ searchSettingCaption: "検索条件を詳しく設定するで。"
+ searchLimit: "表示件数"
+ sortOrder: "並び順"
+ registrationLogs: "登録ログ"
+ registrationLogsCaption: "絵文字更新・削除時のログが表示されるで。更新・削除操作をしたり、ページを遷移・リロードしたら消えるから気ぃつけてな。"
+ alertEmojisRegisterFailedDescription: "絵文字の更新・削除に失敗したで。詳細は登録ログを確認してな。"
+ _logs:
+ showSuccessLogSwitch: "成功ログを表示するで"
+ failureLogNothing: "失敗ログはあらへん。"
+ logNothing: "失敗ログはあらへん。"
+ _remote:
+ selectionRowDetail: "選択行の詳細やで"
+ importSelectionRows: "選択行をインポートするで"
+ importSelectionRangesRows: "選択範囲の行をインポートするで"
+ importEmojisButton: "チェックされた絵文字をインポートするで"
+ confirmImportEmojisTitle: "絵文字のインポートするで"
+ confirmImportEmojisDescription: "リモートから受信した{count}個の絵文字をインポートするで。絵文字のライセンスには十分気ぃつけてな。実行してもええか?"
+ _local:
+ tabTitleList: "登録済み絵文字一覧"
+ tabTitleRegister: "絵文字の登録"
+ _list:
+ emojisNothing: "登録された絵文字はないで。"
+ markAsDeleteTargetRows: "選択行を削除対象にするで"
+ markAsDeleteTargetRanges: "選択範囲の行を削除対象にするで"
+ alertUpdateEmojisNothingDescription: "変更された絵文字はないで。"
+ alertDeleteEmojisNothingDescription: "削除対象の絵文字はないで。"
+ confirmMovePage: "ページを移動してもええんか?"
+ confirmChangeView: "表示を変更してもええんか?"
+ confirmUpdateEmojisDescription: "{count}個の絵文字を更新するで。実行してもええか?"
+ confirmDeleteEmojisDescription: "チェックがつけられた{count}個の絵文字を削除するで。ほんまにええか?"
+ confirmResetDescription: "今までやった変更が全部リセットされるで。"
+ confirmMovePageDesciption: "このページの絵文字に変更が加えられてるで。\n保存せずページを移動してまうと、このページで加えた変更が全てパーになるで。"
+ dialogSelectRoleTitle: "絵文字に設定されたロールで検索"
+ _register:
+ uploadSettingTitle: "アップロード設定"
+ uploadSettingDescription: "この画面で絵文字アップロードするときの動きを設定できるで。"
+ directoryToCategoryLabel: "ディレクトリ名を\"category\"に入力する"
+ directoryToCategoryCaption: "ディレクトリをドラッグ・ドロップした時に、ディレクトリ名を\"category\"に入力します。"
+ emojiInputAreaCaption: "どれかの方法で登録する絵文字を選択して。"
+ emojiInputAreaList1: "この枠に画像ファイルかディレクトリをドラッグ&ドロップ"
+ emojiInputAreaList2: "このリンクをクリックしてPCから選択する"
+ emojiInputAreaList3: "このリンクをクリックしてドライブから選択する"
+ confirmRegisterEmojisDescription: "リストに表示されてる絵文字を新たなカスタム絵文字として登録するで。ほんまにええか? (サーバーがしんどくなるから、一回で登録できる絵文字は{count}件までやで)"
+ confirmClearEmojisDescription: "編集内容をほかして、リストに表示されている絵文字をクリアするで。ほんまにええか?"
+ confirmUploadEmojisDescription: "ドラッグ&ドロップされた{count}個のファイルをドライブにアップロードするで。ほんまにええか?"
_embedCodeGen:
title: "埋め込みコードをカスタム"
header: "ヘッダー出す"
@@ -2754,8 +2828,35 @@ _remoteLookupErrors:
_responseInvalid:
title: "レスポンスがおかしいで"
description: "このサーバーと通信することはできたけど、もらったデータがおかしかったで。"
- _responseInvalidIdHostNotMatch:
- description: "入力されたURIのドメインと最終的に得られたURIのドメインとが違うで。第三者のサーバーを介してリモートのコンテンツを照会してるんやったら、発信元のサーバーで取得できるURIを使って照会し直して。"
_noSuchObject:
title: "見つからへんね"
description: "求められたリソースが見つからんかったで。URIをもっかい確かめてや。"
+_captcha:
+ verify: "CAPTCHAしばいたって"
+ testSiteKeyMessage: "サイトキーとシークレットキーにテスト用の値を入力することでプレビューを確認できるで。\n詳細は下記ページを確認してな。"
+ _error:
+ _requestFailed:
+ title: "CAPTCHAのリクエストに失敗してもうた"
+ text: "しばらく後で実行するか、設定をもっかい確認してや。"
+ _verificationFailed:
+ title: "CAPTCHAのリクエストに失敗してもうた"
+ text: "設定がほんまに合ってるかもっかい確認してや。"
+ _unknown:
+ title: "CAPTCHAエラー"
+ text: "思いもせんかったエラーが起きたわ。"
+_bootErrors:
+ title: "読み込みに失敗したで"
+ serverError: "少し待ってからリロードしてもまだ問題が解決されんのやったら、以下のError IDを添えてサーバー管理者に連絡して。"
+ solution: "以下のことやったら解決するかもやで。"
+ solution1: "ブラウザとかOSを最新バージョンに更新する"
+ solution2: "アドブロッカーを無効にする"
+ solution3: "ブラウザのキャッシュをクリアする"
+ solution4: "(Tor Browser) dom.webaudio.enabledをtrueに設定する"
+ otherOption: "ほかのオプション"
+ otherOption1: "クライアント設定とキャッシュをほかす"
+ otherOption2: "簡易クライアントを起動"
+ otherOption3: "修復ツールを起動"
+_search:
+ searchScopeAll: "みんな"
+ searchScopeLocal: "ローカル"
+ searchScopeUser: "ユーザー指定"
diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml
index 4b9650b636..8ff11d35d9 100644
--- a/locales/ko-GS.yml
+++ b/locales/ko-GS.yml
@@ -655,6 +655,7 @@ replies: "답하기"
renotes: "리노트"
attach: "옇기"
surrender: "아이예"
+information: "정보"
_delivery:
stop: "고만 보내예"
_type:
@@ -843,3 +844,6 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "몬 찾앗십니다"
+_search:
+ searchScopeAll: "말캉"
+ searchScopeUser: "사용자 지정"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 36b818c117..4a02b0c64a 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -1309,6 +1309,50 @@ availableRoles: "사용 가능한 역할"
acknowledgeNotesAndEnable: "활성화 하기 전에 주의 사항을 확인했습니다."
federationSpecified: "이 서버는 화이트 리스트 제도로 운영 중 입니다. 정해진 리모트 서버가 아닌 경우 연합되지 않습니다."
federationDisabled: "이 서버는 연합을 하지 않고 있습니다. 리모트 서버 유저와 통신을 할 수 없습니다."
+confirmOnReact: "리액션할 때 확인"
+reactAreYouSure: "\" {emoji} \"로 리액션하시겠습니까?"
+markAsSensitiveConfirm: "이 미디어를 민감한 미디어로 설정하시겠습니까?"
+unmarkAsSensitiveConfirm: "이 미디어의 민감한 미디어 지정을 해제하시겠습니까?"
+preferences: "환경설정"
+accessibility: "접근성"
+preferencesProfile: "설정 프로필"
+copyPreferenceId: "설정한 ID를 복사"
+resetToDefaultValue: "기본값으로 되돌리기"
+overrideByAccount: "계정으로 덮어쓰기"
+untitled: "제목 없음"
+noName: "이름이 없습니다."
+skip: "건너뛰기"
+restore: "복원"
+syncBetweenDevices: "장치간 동기화"
+preferenceSyncConflictTitle: "서버에 설정값이 존재합니다."
+preferenceSyncConflictChoiceServer: "서버 설정값"
+preferenceSyncConflictChoiceDevice: "장치 설정값"
+paste: "붙여넣기"
+emojiPalette: "이모지 팔레트"
+postForm: "글 입력란"
+information: "정보"
+_emojiPalette:
+ palettes: "팔레트"
+ paletteForMain: "메인으로 사용할 팔레트"
+ paletteForReaction: "리액션으로 사용할 팔레트"
+_settings:
+ api: "API"
+ webhook: "Webhook"
+ serviceConnection: "서비스 연동"
+ accountData: "계정 데이터"
+_preferencesProfile:
+ profileName: "프로필 이름"
+ profileNameDescription: "이 디바이스를 식별할 이름을 설정해 주세요."
+ profileNameDescription2: "예: '메인PC', '스마트폰' 등"
+_preferencesBackup:
+ autoBackup: "자동 백업"
+ restoreFromBackup: "백업으로 복구"
+ noBackupsFoundTitle: "백업을 찾을 수 없습니다"
+ noBackupsFoundDescription: "자동으로 생성된 백업은 찾을 수 없었지만, 수동으로 백업 파일을 저장한 경우 해당 파일을 가져와 복원할 수 있습니다."
+ selectBackupToRestore: "복원할 백업을 선택하세요"
+ youNeedToNameYourProfileToEnableAutoBackup: "자동 백업을 활성화하려면 프로필 이름을 설정해야 합니다."
+ autoPreferencesBackupIsNotEnabledForThisDevice: "이 장치에서 설정 자동 백업이 활성화되어 있지 않습니다."
+ backupFound: "설정 백업이 발견되었습니다"
_accountSettings:
requireSigninToViewContents: "콘텐츠 열람을 위해 로그인을 필수로 설정하기"
requireSigninToViewContentsDescription1: "자신이 작성한 모든 노트 등의 콘텐츠를 보기 위해 로그인을 필수로 설정합니다. 크롤러가 정보 수집하는 것을 방지하는 효과를 기대할 수 있습니다."
@@ -1967,6 +2011,7 @@ _theme:
installed: "{name} 테마가 설치되었습니다"
installedThemes: "설치된 테마"
builtinThemes: "표준 테마"
+ instanceTheme: "서버 테마"
alreadyInstalled: "이미 설치된 테마입니다"
invalid: "테마 형식이 올바르지 않습니다"
make: "테마 만들기"
@@ -2440,6 +2485,8 @@ _notification:
flushNotification: "알림 이력을 초기화"
exportOfXCompleted: "{x} 추출에 성공했습니다."
login: "로그인 알림이 있습니다"
+ createToken: "액세스 토큰이 생성되었습니다"
+ createTokenDescription: "만약 기억이 나지 않는다면 '{text}'를 통해 액세스 토큰을 삭제해 주세요."
_types:
all: "전부"
note: "사용자의 새 글"
@@ -2590,6 +2637,7 @@ _moderationLogTypes:
deletePage: "페이지를 삭제"
deleteFlash: "Play를 삭제"
deleteGalleryPost: "갤러리 포스트를 삭제"
+ updateProxyAccountDescription: "프록시 계정의 설명 업데이트"
_fileViewer:
title: "파일 상세"
type: "파일 유형"
@@ -2603,10 +2651,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오."
_plugin:
title: "이 플러그인을 설치하시겠습니까?"
- metaTitle: "플러그인 정보"
_theme:
title: "이 테마를 설치하시겠습니까?"
- metaTitle: "테마 정보"
_meta:
base: "기본 컬러 스키마"
_vendorInfo:
@@ -2822,8 +2868,6 @@ _remoteLookupErrors:
_responseInvalid:
title: "유효하지 않은 반응입니다."
description: "이 서버와 통신할 수 있지만, 데이터가 올바르지 않습니다."
- _responseInvalidIdHostNotMatch:
- description: "입력된 URI과 실제 URI가 다릅니다. 제 3자 서버를 통한 리모트 컨텐츠를 조회하는 경우, 원래 서버 측에서 받아올 수 있는 URI를 사용하여 조회하시길 바랍니다."
_noSuchObject:
title: "찾을 수 없습니다"
description: "요구된 리소스를 찾을 수 없습니다. URI를 다시 한 번 확인해보세요."
@@ -2840,3 +2884,23 @@ _captcha:
_unknown:
title: "CAPTCHA 에러"
text: "알 수 없는 에러가 발생했습니다."
+_bootErrors:
+ title: "로딩이 실패함"
+ serverError: "잠시 기다렸다가 다시 로드해도 여전히 문제가 해결되지 않으면 아래 Error ID와 함께 서버 관리자에게 연락해 주세요."
+ solution: "다음과 같은 방법으로 해결할 수 있습니다."
+ solution1: "브라우저 및 OS를 최신 버전으로 업데이트하기"
+ solution2: "광고 차단 비활성화하기"
+ solution3: "브라우저 캐시 지우기"
+ solution4: "(Tor Browser) dom.webaudio.enabled를 true로 설정하세요"
+ otherOption: "기타 옵션"
+ otherOption1: "클라이언트 설정 및 캐시 삭제"
+ otherOption2: "간편 클라이언트 실행"
+ otherOption3: "복구 툴 실행"
+_search:
+ searchScopeAll: "전체"
+ searchScopeLocal: "로컬"
+ searchScopeServer: "서버 지정"
+ searchScopeUser: "사용자 지정"
+ pleaseEnterServerHost: "서버의 호스트를 입력해 주세요."
+ pleaseSelectUser: "유저를 선택해주세요"
+ serverHostPlaceholder: "예: misskey.example.com"
diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml
index 2d55c289aa..52b5a28fb2 100644
--- a/locales/lo-LA.yml
+++ b/locales/lo-LA.yml
@@ -394,6 +394,7 @@ searchByGoogle: "ຄົ້ນຫາ"
file: "ໄຟລ໌"
replies: "ຕອບ​ກັບ"
renotes: "Renote"
+information: "ກ່ຽວກັບ"
_delivery:
stop: "ໂຈະ"
_type:
@@ -477,3 +478,5 @@ _moderationLogTypes:
_remoteLookupErrors:
_noSuchObject:
title: "ບໍ່ພົບ"
+_search:
+ searchScopeAll: "ທັງໝົດ"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index 685094b4a5..e2f6017f20 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -462,6 +462,7 @@ loggedInAsBot: "Momenteel als bot ingelogd"
icon: "Avatar"
replies: "Antwoord"
renotes: "Herdelen"
+information: "Over"
_delivery:
stop: "Opgeschort"
_type:
@@ -540,3 +541,5 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Niet gevonden"
+_search:
+ searchScopeAll: "Alle"
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index 474e05ba67..7981360c41 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -463,6 +463,7 @@ icon: "Avatar"
replies: "Svar"
renotes: "Renote"
surrender: "Avbryt"
+information: "Informasjon"
_delivery:
stop: "Suspendert"
_initialAccountSetting:
@@ -730,3 +731,5 @@ _moderationLogTypes:
_remoteLookupErrors:
_noSuchObject:
title: "Ikke funnet"
+_search:
+ searchScopeAll: "Alle"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 9bd585de86..21b715714d 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -1044,6 +1044,8 @@ flip: "Odwróć"
lastNDays: "W ciągu ostatnich {n} dni"
surrender: "Odrzuć"
gameRetry: "Spróbuj ponownie"
+postForm: "Formularz tworzenia wpisu"
+information: "Informacje"
_delivery:
stop: "Zawieszono"
_type:
@@ -1583,3 +1585,6 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Nie znaleziono"
+_search:
+ searchScopeAll: "Wszystkie"
+ searchScopeLocal: "Lokalne"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index d691022d75..2f46eda784 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -1301,6 +1301,10 @@ lockdown: "Lockdown"
pleaseSelectAccount: "Selecione uma conta"
availableRoles: "Cargos disponíveis"
acknowledgeNotesAndEnable: "Ative após compreender as precauções."
+postForm: "Campo de postagem"
+information: "Informações"
+_settings:
+ webhook: "Webhook"
_accountSettings:
requireSigninToViewContents: "Exigir cadastro para ver o conteúdo"
requireSigninToViewContentsDescription1: "Exigir cadastro para ver todas as notas e outro conteúdo que você criou. Isso previne 'crawlers' de coletar os seus dados."
@@ -2595,10 +2599,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Tenha certeza de que o distribuidor desse recurso é confiável antes da instalação."
_plugin:
title: "Deseja instalar esse plugin?"
- metaTitle: "Informações do plugin"
_theme:
title: "Deseja instalar esse tema?"
- metaTitle: "Informações do tema"
_meta:
base: "Paleta de cores base"
_vendorInfo:
@@ -2754,8 +2756,10 @@ _remoteLookupErrors:
_responseInvalid:
title: "Resposta inválida"
description: "Foi possível comunicar com o servidor, porém os dados obtidos foram incorretos."
- _responseInvalidIdHostNotMatch:
- description: "O domínio do endereço inserido difere do domínio do endereço final. Se você estiver pesquisando por um servidor de terceiros, tente buscar novamente com um endereço que pode ser obtido através do servidor original."
_noSuchObject:
title: "Não encontrado"
description: "O recurso solicitado não foi encontrado, confira o endereço."
+_search:
+ searchScopeAll: "Todos"
+ searchScopeLocal: "Local"
+ searchScopeUser: "Usuário específico"
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 07f4c98d96..5d1459480d 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -646,6 +646,7 @@ show: "Arată"
icon: "Avatar"
replies: "Răspunde"
renotes: "Re-notează"
+information: "Despre"
_delivery:
stop: "Suspendat"
_type:
@@ -736,3 +737,5 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Nu a fost găsit"
+_search:
+ searchScopeAll: "Tot"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 7ed41a9c47..33a09d198a 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -1181,6 +1181,10 @@ keepOriginalFilenameDescription: "Если вы выключите данную
alwaysConfirmFollow: "Всегда подтверждать подписку"
inquiry: "Связаться"
messageToFollower: "Сообщение подписчикам"
+postForm: "Форма отправки"
+information: "Описание"
+_settings:
+ webhook: "Вебхук"
_delivery:
stop: "Заморожено"
_type:
@@ -2147,3 +2151,7 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Не найдено"
+_search:
+ searchScopeAll: "Все"
+ searchScopeLocal: "Местная"
+ searchScopeUser: "Указанный пользователь"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index 521d172671..cc8ef9298a 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -917,6 +917,8 @@ renotes: "Preposlať"
sourceCode: "Zdrojový kód"
flip: "Preklopiť"
lastNDays: "Posledných {n} dní"
+postForm: "Napísať poznámku"
+information: "Informácie"
_delivery:
stop: "Zmrazené"
_type:
@@ -1449,3 +1451,6 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Nenájdené"
+_search:
+ searchScopeAll: "Všetko"
+ searchScopeLocal: "Lokálne"
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index 5961605645..c740ab1c0c 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -562,6 +562,7 @@ inquiry: "Kontakt"
tryAgain: "Försök igen senare"
signinWithPasskey: "Logga in med nyckel"
unknownWebAuthnKey: "Okänd nyckel"
+information: "Om"
_delivery:
stop: "Suspenderad"
_type:
@@ -707,3 +708,5 @@ _reversi:
white: "Vit"
_selfXssPrevention:
warning: "VARNING"
+_search:
+ searchScopeAll: "Allt"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index ec83ba888c..784e9049dd 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -1292,6 +1292,10 @@ prohibitedWordsForNameOfUser: "คำนี้ไม่สามารถใช
prohibitedWordsForNameOfUserDescription: "หากมีสตริงใดๆ ในรายการนี้ปรากฏอยู่ในชื่อของผู้ใช้ ชื่อนั้นจะถูกปฏิเสธ ผู้ใช้ที่มีสิทธิ์แต่ผู้ดูแลระบบนั้นจะไม่ได้รับผลกระทบใดๆจากข้อจำกัดนี้ค่ะ"
yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม"
yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
+postForm: "แบบฟอร์มการโพสต์"
+information: "เกี่ยวกับ"
+_settings:
+ webhook: "Webhook"
_abuseUserReport:
forward: "ส่ง​ต่อ"
forwardDescription: "ส่งรายงานไปยังเซิร์ฟเวอร์ระยะไกลโดยใช้บัญชีระบบที่ไม่ระบุตัวตน"
@@ -2569,10 +2573,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "โปรดตรวจสอบให้แน่ใจว่าแหล่งแจกหน่ายมีความน่าเชื่อถือก่อนทำการติดตั้ง"
_plugin:
title: "ต้องการติดตั้งปลั๊กอินนี้ใช่ไหม?"
- metaTitle: "ข้อมูลส่วนเสริม"
_theme:
title: "ต้องการติดตั้งธีมนี้ใช่ไหม?"
- metaTitle: "ข้อมูลธีม"
_meta:
base: "โทนสีพื้นฐาน"
_vendorInfo:
@@ -2709,3 +2711,7 @@ _embedCodeGen:
_remoteLookupErrors:
_noSuchObject:
title: "ไม่พบหน้าที่ต้องการ"
+_search:
+ searchScopeAll: "ทั้งหมด"
+ searchScopeLocal: "ท้องถิ่น"
+ searchScopeUser: "ผู้ใช้เฉพาะ"
diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml
index 2c63f15aa2..e99aa64254 100644
--- a/locales/tr-TR.yml
+++ b/locales/tr-TR.yml
@@ -460,3 +460,5 @@ _deck:
_moderationLogTypes:
suspend: "askıya al"
resetPassword: "Şifre sıfırlama"
+_search:
+ searchScopeAll: "Tümü"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index a83ad80683..a63e39eff4 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -909,6 +909,8 @@ renotes: "Поширити"
sourceCode: "Вихідний код"
flip: "Перевернути"
lastNDays: "Останні {n} днів"
+postForm: "Створення нотатки"
+information: "Інформація"
_delivery:
stop: "Призупинено"
_type:
@@ -1624,3 +1626,6 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Не знайдено"
+_search:
+ searchScopeAll: "Всі"
+ searchScopeLocal: "Локальна"
diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml
index 6015492b92..d5b7d4b770 100644
--- a/locales/uz-UZ.yml
+++ b/locales/uz-UZ.yml
@@ -841,6 +841,7 @@ icon: "Avatar"
replies: "Javob berish"
renotes: "Qayta qayd etish"
flip: "Teskari"
+information: "Haqida"
_delivery:
stop: "To'xtatilgan"
_type:
@@ -1094,3 +1095,6 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Topilmadi"
+_search:
+ searchScopeAll: "Barcha"
+ searchScopeLocal: "Mahalliy"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index e6a9418126..8edcebcd08 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -1119,6 +1119,8 @@ pullDownToRefresh: "Kéo xuống để làm mới"
cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích."
lastNDays: "{n} ngày trước"
surrender: "Từ chối"
+postForm: "Mẫu đăng"
+information: "Giới thiệu"
_delivery:
stop: "Đã vô hiệu hóa"
_type:
@@ -1930,3 +1932,7 @@ _reversi:
_remoteLookupErrors:
_noSuchObject:
title: "Không tìm thấy"
+_search:
+ searchScopeAll: "Tất cả"
+ searchScopeLocal: "Máy chủ này"
+ searchScopeUser: "Người dùng chỉ định"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index f4df425af4..ad3eebfebd 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 说了关于「{word}」的什么"
makeActive: "启用"
display: "显示"
copy: "复制"
+copiedToClipboard: "已复制到剪贴板"
metrics: "指标"
overview: "概览"
logs: "日志"
@@ -746,7 +747,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。
public: "公开"
private: "私密"
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
-manageAccessTokens: "管理 Access Tokens"
+manageAccessTokens: "管理访问令牌"
accountInfo: "账户信息"
notesCount: "帖子数量"
repliesCount: "回复数量"
@@ -1309,6 +1310,69 @@ availableRoles: "可用角色"
acknowledgeNotesAndEnable: "理解注意事项后再开启。"
federationSpecified: "此服务器已开启联合白名单。只能与管理员指定的服务器通信。"
federationDisabled: "此服务器已禁用联合。无法与其它服务器上的用户通信。"
+confirmOnReact: "发送回应前需要确认"
+reactAreYouSure: "要用「{emoji}」进行回应吗?"
+markAsSensitiveConfirm: "要将此媒体标记为敏感吗?"
+unmarkAsSensitiveConfirm: "要将此媒体解除敏感标记吗?"
+preferences: "设置"
+accessibility: "辅助功能"
+preferencesProfile: "设置的配置"
+copyPreferenceId: "复制设置 ID"
+resetToDefaultValue: "重置为默认值"
+overrideByAccount: "用账户覆盖"
+untitled: "未命名"
+noName: "没有名字"
+skip: "跳过"
+restore: "恢复"
+syncBetweenDevices: "设备间同步"
+preferenceSyncConflictTitle: "服务器上已存在设定值"
+preferenceSyncConflictText: "服务器上已有此设置的设定值。要覆盖哪个设定值?"
+preferenceSyncConflictChoiceServer: "服务器上的设定值"
+preferenceSyncConflictChoiceDevice: "设备上的设定值"
+preferenceSyncConflictChoiceCancel: "取消同步"
+paste: "粘贴"
+emojiPalette: "表情符号调色板"
+postForm: "投稿窗口"
+textCount: "字数"
+information: "关于"
+_emojiPalette:
+ palettes: "调色板"
+ enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"
+ paletteForMain: "主调色板"
+ paletteForReaction: "回应用调色板"
+_settings:
+ driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。"
+ pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。"
+ notificationsBanner: "可在此设置从服务器接收的通知的种类和范围,以及推送通知的设置。"
+ api: "API"
+ webhook: "Webhook"
+ serviceConnection: "连接服务"
+ serviceConnectionBanner: "可在此管理用于连接外部应用或服务的访问令牌及 Webhook。"
+ accountData: "账户数据"
+ accountDataBanner: "可在此导入或导出帐户数据的存档。"
+ muteAndBlockBanner: "可在此设置隐藏内容,或限制指定用户能进行的操作。"
+ accessibilityBanner: "可在此设置客户端的显示及动态效果等辅助设置。"
+ privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。"
+ securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。"
+ preferencesBanner: "可在此设置客户端的整体运作行为。"
+ appearanceBanner: "可在此设置客户端的外观及显示方式。"
+ soundsBanner: "可在此设置客户端播放的声音。"
+ timelineAndNote: "时间线和帖子"
+ makeEveryTextElementsSelectable: "使所有的文字均可选择"
+ makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。"
+_preferencesProfile:
+ profileName: "配置名"
+ profileNameDescription: "请指定用于识别此设备的名称"
+ profileNameDescription2: "如「PC」、「手机」等"
+_preferencesBackup:
+ autoBackup: "自动备份"
+ restoreFromBackup: "从备份恢复"
+ noBackupsFoundTitle: "没有找到备份"
+ noBackupsFoundDescription: "没有找到自动备份。若有手动保存备份文件,可将其导入来恢复。"
+ selectBackupToRestore: "请选择要恢复的备份"
+ youNeedToNameYourProfileToEnableAutoBackup: "需指定配置名以开启自动备份。"
+ autoPreferencesBackupIsNotEnabledForThisDevice: "此设备未开启自动备份"
+ backupFound: "已找到备份"
_accountSettings:
requireSigninToViewContents: "需要登录才能显示内容"
requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。"
@@ -1319,6 +1383,7 @@ _accountSettings:
makeNotesHiddenBefore: "将过去的帖子设为私密"
makeNotesHiddenBeforeDescription: "开启此设定时,超过设定的时间或日期后,帖子将变为仅自己可见。关闭后帖子的公开状态将恢复成原本的设定。"
mayNotEffectForFederatedNotes: "与远程服务器联合的帖子在远端可能会没有效果。"
+ mayNotEffectSomeSituations: "此限制功能非常简单,在与远程服务器联合等情形时可能不适用。"
notesHavePassedSpecifiedPeriod: "超过指定时间的帖子"
notesOlderThanSpecifiedDateAndTime: "指定日期前的帖子"
_abuseUserReport:
@@ -1967,6 +2032,7 @@ _theme:
installed: "{name} 已安装"
installedThemes: "已安装的主题"
builtinThemes: "标准主题"
+ instanceTheme: "服务器主题"
alreadyInstalled: "此主题已经安装"
invalid: "主题格式错误"
make: "制作主题"
@@ -2440,6 +2506,8 @@ _notification:
flushNotification: "重置通知历史"
exportOfXCompleted: "已完成 {x} 的导出"
login: "有新的登录"
+ createToken: "访问令牌已创建"
+ createTokenDescription: "如果不明白其用途,请遵循「{text}」的指示删除访问令牌。"
_types:
all: "全部"
note: "用户的新帖子"
@@ -2456,6 +2524,7 @@ _notification:
achievementEarned: "取得的成就"
exportCompleted: "已完成导出"
login: "登录"
+ createToken: "创建访问令牌"
test: "测试通知"
app: "关联应用的通知"
_actions:
@@ -2483,6 +2552,7 @@ _deck:
useSimpleUiForNonRootPages: "用简易UI表示非根页面"
usedAsMinWidthWhenFlexible: "「自适应宽度」被启用的时候,这就是最小的宽度"
flexible: "自适应宽度"
+ enableSyncBetweenDevicesForProfiles: "启用个人资料信息跨设备同步"
_columns:
main: "主列"
widgets: "小工具"
@@ -2590,6 +2660,7 @@ _moderationLogTypes:
deletePage: "删除了页面"
deleteFlash: "删除了 Play"
deleteGalleryPost: "删除了图库稿件"
+ updateProxyAccountDescription: "更新代理账户的简介"
_fileViewer:
title: "文件信息"
type: "文件类型"
@@ -2603,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "请在安装前确保来源可靠"
_plugin:
title: "要安装此插件吗?"
- metaTitle: "插件信息"
_theme:
title: "要安装此主题吗?"
- metaTitle: "主题信息"
_meta:
base: "基本配色方案"
_vendorInfo:
@@ -2777,8 +2846,8 @@ _customEmojisManager:
_register:
uploadSettingTitle: "上传设置"
uploadSettingDescription: "可以在此页面设置上传表情符号时的行为。"
- directoryToCategoryLabel: "目录名请输入「category」"
- directoryToCategoryCaption: "拖放目录时,目录名请输入「category」"
+ directoryToCategoryLabel: "将目录名设为「category」"
+ directoryToCategoryCaption: "拖放目录时,将目录名设置为「category」"
emojiInputAreaCaption: "请使用其中一种方法选择要注册的表情符号。"
emojiInputAreaList1: "在此区域内拖放图像文件或者目录"
emojiInputAreaList2: "单击此链接以从电脑中选择"
@@ -2822,8 +2891,6 @@ _remoteLookupErrors:
_responseInvalid:
title: "响应无效"
description: "成功与此服务器通信,但返回的数据无效。"
- _responseInvalidIdHostNotMatch:
- description: "输入 URI 的域名和最终取得的 URI 的域名不同。如果是通过第三方服务器获取远程内容,请使用可以从原始服务器获取内容的 URI 再试一次。"
_noSuchObject:
title: "未找到"
description: "未找到请求的资源。请再次检查 URI。"
@@ -2840,3 +2907,23 @@ _captcha:
_unknown:
title: "CAPTCHA 错误"
text: "发生意外错误。"
+_bootErrors:
+ title: "加载失败"
+ serverError: "请稍等片刻再重试。若问题仍无法解决,请将以下 Error ID 一起发送给管理员。"
+ solution: "以下方法或许可以解决问题:"
+ solution1: "将浏览器及操作系统更新到最新版本"
+ solution2: "禁用广告屏蔽插件"
+ solution3: "清除浏览器缓存"
+ solution4: "(Tor Browser)将 dom.webaudio.enabled 设定为 true"
+ otherOption: "其它选项"
+ otherOption1: "清除客户端设定与缓存"
+ otherOption2: "使用简易客户端"
+ otherOption3: "启动修复工具"
+_search:
+ searchScopeAll: "全部"
+ searchScopeLocal: "本地"
+ searchScopeServer: "指定服务器"
+ searchScopeUser: "指定用户"
+ pleaseEnterServerHost: "请填写服务器主机名"
+ pleaseSelectUser: "请选择用户"
+ serverHostPlaceholder: "如:misskey.example.com"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 466e3cc1d8..0de267688c 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -103,7 +103,7 @@ serverIsDead: "伺服器沒有回應。請稍等片刻再試。"
youShouldUpgradeClient: "請重新載入以使用新版客戶端顯示此頁面。"
enterListName: "輸入清單名稱"
privacy: "隱私"
-makeFollowManuallyApprove: "手動審核追隨請求"
+makeFollowManuallyApprove: "追隨需要核准"
defaultNoteVisibility: "預設可見性"
follow: "追隨"
followRequest: "追隨請求"
@@ -368,7 +368,7 @@ normal: "正常"
instanceName: "伺服器名稱"
instanceDescription: "伺服器介紹"
maintainerName: "管理員名稱"
-maintainerEmail: "管理員郵箱"
+maintainerEmail: "管理員信箱"
tosUrl: "服務條款 URL"
thisYear: "本年"
thisMonth: "本月"
@@ -459,13 +459,13 @@ moderationNoteDescription: "您可以編寫僅在審查員之間共用的註解
addModerationNote: "新增管理筆記"
moderationLogs: "管理日誌"
nUsersMentioned: "被 {n} 個人提及"
-securityKeyAndPasskey: "安全金鑰、Passkey"
+securityKeyAndPasskey: "安全金鑰、通行金鑰"
securityKey: "安全金鑰"
lastUsed: "上次使用"
lastUsedAt: "上次使用:{t}"
unregister: "註銷"
-passwordLessLogin: "設置無密碼登入"
-passwordLessLoginDescription: "不使用密碼,以安全金鑰或 Passkey 登入"
+passwordLessLogin: "無密碼登入"
+passwordLessLoginDescription: "不使用密碼,以安全金鑰或通行金鑰登入"
resetPassword: "重設密碼"
newPasswordIs: "新密碼為「{password}」"
reduceUiAnimation: "減少介面的動態視覺"
@@ -521,7 +521,7 @@ menuStyle: "選單風格"
style: "風格"
drawer: "側邊欄"
popup: "彈出式視窗"
-showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的"
+showNoteActionsOnlyHover: "僅於游標懸停時顯示貼文選項"
showReactionsCount: "顯示貼文的反應數目"
noHistory: "沒有歷史紀錄"
signinHistory: "登入歷史"
@@ -558,12 +558,12 @@ useObjectStorage: "使用物件儲存"
objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "用於引用的 URL。如果您使用的是 CDN 或反向代理,請指定其 URL,例如 S3(https://<bucket>.s3.amazonaws.com)、GCS(https://storage.googleapis.com/<bucket>)。"
objectStorageBucket: "儲存空間(Bucket)"
-objectStorageBucketDesc: "請填寫所用服務的儲存空間(Bucket)名稱。 "
+objectStorageBucketDesc: "請填寫所用服務的儲存桶(Bucket)名稱。 "
objectStoragePrefix: "前綴"
objectStoragePrefixDesc: "它儲存在此前綴目錄下。"
objectStorageEndpoint: "端點(Endpoint)"
objectStorageEndpointDesc: "如使用 AWS S3,請留空。如使用其他服務,請按照其說明文件以「<host>」或「<host>:<port>」的形式設定端點(Endpoint)。"
-objectStorageRegion: "地域(Region)"
+objectStorageRegion: "區域(Region)"
objectStorageRegionDesc: "請填寫一個分區,例如「xx-east-1」。 如果您使用的服務不設分區,請留空或填寫「us-east-1」。"
objectStorageUseSSL: "使用 SSL"
objectStorageUseSSLDesc: "請在不使用 https 連接 API 時關閉"
@@ -586,7 +586,7 @@ popout: "彈出式視窗"
volume: "音量"
masterVolume: "主音量"
notUseSound: "關閉音效"
-useSoundOnlyWhenActive: "瀏覽器在前景運作時,Misskey 才會發出音效"
+useSoundOnlyWhenActive: "僅在 Misskey 於前景運作時發出音效"
details: "詳細資訊"
renoteDetails: "轉發貼文的細節"
chooseEmoji: "選擇您的表情符號"
@@ -681,7 +681,7 @@ smtpHost: "主機"
smtpPort: "埠"
smtpUser: "使用者名稱"
smtpPass: "密碼"
-emptyToDisableSmtpAuth: "留空使用者名稱和密碼以關閉SMTP驗證。"
+emptyToDisableSmtpAuth: "將使用者名稱和密碼留空以關閉 SMTP 驗證。"
smtpSecure: "在 SMTP 連接中使用隱式 SSL/TLS"
smtpSecureInfo: "使用 STARTTLS 時關閉。"
testEmail: "測試郵件發送"
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 說了一些關於「{word}」的話"
makeActive: "啟用"
display: "檢視"
copy: "複製"
+copiedToClipboard: "已複製到剪貼簿"
metrics: "指標"
overview: "概覽"
logs: "日誌"
@@ -711,7 +712,7 @@ useGlobalSetting: "使用全域設定"
useGlobalSettingDesc: "啟用時,將使用帳戶通知設定。停用時,則可以單獨設定。"
other: "其他"
regenerateLoginToken: "重新產生登入權杖"
-regenerateLoginTokenDescription: "重新產生用於登入的內部權杖。一般情況下是不需要這樣做的。重新產生後,所有裝置將會被登出。"
+regenerateLoginTokenDescription: "重新產生用於登入的內部權杖。通常不需要使用此功能。重新產生後,所有裝置都將被登出。"
theKeywordWhenSearchingForCustomEmoji: "這是搜尋自訂表情符號時的關鍵字"
setMultipleBySeparatingWithSpace: "您可以使用空格分隔多個項目。"
fileIdOrUrl: "檔案 ID 或 URL"
@@ -745,7 +746,7 @@ unclip: "解除摘錄"
confirmToUnclipAlreadyClippedNote: "此貼文已包含在摘錄「{name}」中。 你想將貼文從這個摘錄中排除嗎?"
public: "公開"
private: "私密"
-i18nInfo: "Misskey 已被志願者們翻譯成各種語言版本。您可以瀏覽 {link} 幫助翻譯。"
+i18nInfo: "Misskey 已被志願者們翻譯成各種語言版本。您可以前往 {link} 以協助翻譯。"
manageAccessTokens: "管理存取權杖"
accountInfo: "帳戶資訊"
notesCount: "貼文數量"
@@ -765,7 +766,7 @@ driveFilesCount: "雲端硬碟檔案數量"
driveUsage: "雲端硬碟使用量"
noCrawle: "拒絕搜尋引擎索引"
noCrawleDescription: "要求網路搜尋引擎不要索引你的個人資料頁、貼文及頁面等。"
-lockedAccountInfo: "即使你通過了追隨者請求,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。"
+lockedAccountInfo: "即使追隨需要核准,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。"
alwaysMarkSensitive: "預設標記檔案為敏感內容"
loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
disableShowingAnimatedImages: "不播放動態圖檔"
@@ -781,7 +782,7 @@ useSystemFont: "使用系統預設的字型"
clips: "摘錄"
experimentalFeatures: "實驗中的功能"
experimental: "實驗性"
-thisIsExperimentalFeature: "這是實驗性的功能。可能會有變更規格和不能正常動作的可能性。"
+thisIsExperimentalFeature: "這是一項實驗性功能,其行為會隨需要進行調整,也可能無法正常運作。"
developer: "開發者"
makeExplorable: "使自己的帳戶更容易被找到"
makeExplorableDescription: "如果關閉,帳戶將不會被顯示在「探索」頁面中。"
@@ -1188,7 +1189,7 @@ forYou: "給您"
currentAnnouncements: "最新公告"
pastAnnouncements: "歷史公告"
youHaveUnreadAnnouncements: "有未讀的公告。"
-useSecurityKey: "請按照瀏覽器或裝置上的說明來使用安全金鑰或 Passkey。"
+useSecurityKey: "請按照瀏覽器或裝置上的說明來使用安全金鑰或通行金鑰。"
replies: "回覆"
renotes: "轉發"
loadReplies: "閱覽回覆"
@@ -1205,9 +1206,9 @@ showRenotes: "顯示其他人的轉發貼文"
edited: "已編輯"
notificationRecieveConfig: "接受通知的設定"
mutualFollow: "互相追隨"
-followingOrFollower: "追隨中或者追隨者"
+followingOrFollower: "追隨中或追隨者"
fileAttachedOnly: "只顯示包含附件的貼文"
-showRepliesToOthersInTimeline: "顯示給其他人的回覆"
+showRepliesToOthersInTimeline: "在時間軸上顯示給其他人的回覆"
hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆"
showRepliesToOthersInTimelineAll: "在時間軸包含追隨中所有人的回覆"
hideRepliesToOthersInTimelineAll: "在時間軸不包含追隨中所有人的回覆"
@@ -1247,7 +1248,7 @@ reloadRequiredToApplySettings: "需要重新載入頁面設定才能生效。"
remainingN: "剩餘:{n}"
overwriteContentConfirm: "確定要覆蓋目前的內容嗎?"
seasonalScreenEffect: "隨季節變換畫面的呈現"
-decorate: "設置頭像裝飾"
+decorate: "裝飾"
addMfmFunction: "插入 MFM 功能語法"
enableQuickAddMfmFunction: "顯示進階 MFM 選擇器"
bubbleGame: "氣泡遊戲"
@@ -1274,7 +1275,7 @@ useBackupCode: "使用備用驗證碼"
launchApp: "啟動 APP"
useNativeUIForVideoAudioPlayer: "使用瀏覽器的 UI 播放影片與音訊"
keepOriginalFilename: "保留原始檔名"
-keepOriginalFilenameDescription: "如果關閉此設置,上傳時檔案名稱會自動替換為隨機字串。"
+keepOriginalFilenameDescription: "如果關閉此設定,上傳時檔案名稱會自動替換為隨機字串。"
noDescription: "沒有說明文字"
alwaysConfirmFollow: "追隨時總是確認"
inquiry: "聯絡我們"
@@ -1291,10 +1292,10 @@ performance: "性能"
modified: "已變更"
discard: "取消"
thereAreNChanges: "有 {n} 處的變更"
-signinWithPasskey: "使用密碼金鑰登入"
-unknownWebAuthnKey: "未註冊的金鑰。"
-passkeyVerificationFailed: "驗證金鑰失敗。"
-passkeyVerificationSucceededButPasswordlessLoginDisabled: "雖然驗證金鑰成功,但是無密碼登入的方式是停用的。"
+signinWithPasskey: "使用通行金鑰登入"
+unknownWebAuthnKey: "未註冊的通行金鑰。"
+passkeyVerificationFailed: "驗證通行金鑰失敗。"
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "雖然驗證通行金鑰成功,但是無密碼登入的方式是停用的。"
messageToFollower: "給追隨者的訊息"
target: "目標 "
testCaptchaWarning: "此功能用於 CAPTCHA 的測試。<strong>請勿在正式環境中使用。</strong>"
@@ -1309,6 +1310,69 @@ availableRoles: "可用角色"
acknowledgeNotesAndEnable: "了解注意事項後再開啟。"
federationSpecified: "此伺服器以白名單聯邦的方式運作。除了管理員指定的伺服器外,它無法與其他伺服器互動。"
federationDisabled: "此伺服器未開啟站台聯邦。無法與其他伺服器上的使用者互動。"
+confirmOnReact: "反應時確認"
+reactAreYouSure: "用「 {emoji} 」反應嗎?"
+markAsSensitiveConfirm: "要將這個媒體設定為敏感嗎?"
+unmarkAsSensitiveConfirm: "要解除這個媒體的敏感設定嗎?"
+preferences: "環境設定"
+accessibility: "輔助工具"
+preferencesProfile: "設定檔案"
+copyPreferenceId: "複製設定 ID"
+resetToDefaultValue: "還原成預設值"
+overrideByAccount: "覆寫帳號"
+untitled: "無標題"
+noName: "沒有名稱"
+skip: "跳過"
+restore: "還原"
+syncBetweenDevices: "裝置之間的同步化"
+preferenceSyncConflictTitle: "伺服器上存在設定值"
+preferenceSyncConflictText: "已啟用同步的設定項目會將設定值儲存至伺服器,並已找到該設定項目在伺服器上儲存的設定值。請選擇要使用哪個設定值進行覆寫。"
+preferenceSyncConflictChoiceServer: "伺服器設定值"
+preferenceSyncConflictChoiceDevice: "裝置的設定值"
+preferenceSyncConflictChoiceCancel: "取消啟用同步"
+paste: "貼上"
+emojiPalette: "表情符號調色盤"
+postForm: "發文視窗"
+textCount: "字數"
+information: "關於"
+_emojiPalette:
+ palettes: "調色盤"
+ enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化"
+ paletteForMain: "主要使用的調色盤"
+ paletteForReaction: "反應用的調色盤"
+_settings:
+ driveBanner: "您可以管理和設定雲端硬碟、確認使用量,以及調整上傳檔案時的設定。"
+ pluginBanner: "可使用外掛擴充用戶端的功能。您可以安裝外掛,實施個別的設定與管理。"
+ notificationsBanner: "您可以設定從伺服器接收通知的類型和範圍,以及推送通知。"
+ api: "API"
+ webhook: "Webhook"
+ serviceConnection: "服務整合"
+ serviceConnectionBanner: "您可以管理和設定存取權杖與 Webhooks,以便與外部應用程式和服務整合。"
+ accountData: "帳戶資料"
+ accountDataBanner: "您可以管理帳戶資料的匯出 / 匯入。"
+ muteAndBlockBanner: "您可以設定和管理要隱藏的內容,並限制特定使用者的行動。"
+ accessibilityBanner: "可針對客戶端的視覺和行為進行個人化設定,以達到更佳的使用效果。"
+ privacyBanner: "您可以調整帳戶的隱私設定,例如內容的可見性、尋找內容的容易程度,以及追隨是否需要核准。"
+ securityBanner: "您可以設定與帳戶安全性相關的設定,例如密碼、登入方式、驗證應用程式和通行金鑰。"
+ preferencesBanner: "您可以根據喜好設定用戶端的整體行為。"
+ appearanceBanner: "您可以根據喜好設定與用戶端外觀和顯示方式相關的設定。"
+ soundsBanner: "您可以調整用戶端播放的聲音設定。"
+ timelineAndNote: "時間軸及貼文"
+ makeEveryTextElementsSelectable: "允許選取所有文字"
+ makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。"
+_preferencesProfile:
+ profileName: "設定檔案名稱"
+ profileNameDescription: "設定一個名稱來識別此裝置。"
+ profileNameDescription2: "例如:「主要個人電腦」、「智慧型手機」等"
+_preferencesBackup:
+ autoBackup: "自動備份"
+ restoreFromBackup: "從備份還原"
+ noBackupsFoundTitle: "找不到備份檔"
+ noBackupsFoundDescription: "沒有找到自動建立的備份,但如果您手動儲存了備份檔案,則可以匯入並還原。"
+ selectBackupToRestore: "選擇要還原的備份"
+ youNeedToNameYourProfileToEnableAutoBackup: "要啟用自動備份,必須設定檔案名稱。"
+ autoPreferencesBackupIsNotEnabledForThisDevice: "此裝置未啟用自動備份設定。"
+ backupFound: "找到設定的備份"
_accountSettings:
requireSigninToViewContents: "須登入以顯示內容"
requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。"
@@ -1319,6 +1383,7 @@ _accountSettings:
makeNotesHiddenBefore: "隱藏過去的貼文"
makeNotesHiddenBeforeDescription: "啟用此功能後,超過設定的日期和時間或超過設定時間的貼文將僅對自己顯示(私密化)。 如果您再次停用它,貼文的公開狀態也會恢復原狀。"
mayNotEffectForFederatedNotes: "聯邦發送至遠端伺服器的貼文可能會不受影響。"
+ mayNotEffectSomeSituations: "這些限制已經簡化。它們可能不適用於某些情況,例如在遠端伺服器上檢視或管理時。"
notesHavePassedSpecifiedPeriod: "早於指定時間的貼文"
notesOlderThanSpecifiedDateAndTime: "指定時間和日期之前的貼文"
_abuseUserReport:
@@ -1433,7 +1498,7 @@ _initialTutorial:
useCases: "伺服器的服務條款可能會規範特定的貼文需要使用隱藏內容,除此之外也會用在隱藏劇情洩漏與敏感內容的貼文。"
_howToMakeAttachmentsSensitive:
title: "如何標記上傳附件為敏感內容?"
- description: "如果伺服器服務條款有規範,又或者不希望上傳附件直接被看見,可以設置為「敏感內容」"
+ description: "如果伺服器的服務條款有規範,又或者不適合直接展示的附件,請記得加上「敏感」標記。"
tryThisFile: "試試看!把附加在發文表單的圖像檔案標記為敏感內容。"
_exampleNote:
note: "打開納豆的包裝失敗了…"
@@ -1467,7 +1532,7 @@ _serverSettings:
inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址,或包含運營者聯絡資訊網頁的網址。"
openRegistration: "允許建立帳戶"
openRegistrationWarning: "開放註冊伴隨著風險。 建議只有在伺服器受到持續監控,並準備好在出現問題時能立即處理的情況下才開放註冊。"
- thisSettingWillAutomaticallyOffWhenModeratorsInactive: "為了防止 spam,如果一段期間內沒有偵測到審查員的活動,此設定將自動關閉。"
+ thisSettingWillAutomaticallyOffWhenModeratorsInactive: "如果在一段期間內沒有偵測到任何審查員活動,此設定將自動關閉,以防止垃圾內容。"
_accountMigration:
moveFrom: "從其他帳戶遷移到這個帳戶"
moveFromSub: "為另一個帳戶建立別名"
@@ -1481,7 +1546,7 @@ _accountMigration:
startMigration: "遷移"
migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外,請確認在要遷移到的帳戶已經建立了一個別名。"
movedAndCannotBeUndone: "帳戶已遷移。\n遷移無法撤消。"
- postMigrationNote: "將在完成遷移後的 24 小時取消追隨所有帳號。\n此帳戶的追隨中/追隨者人數將歸零。由於不會解除粉絲對您的追隨,因此他們仍然可以繼續閱覽此帳戶僅對追隨者公開的貼文。"
+ postMigrationNote: "將在完成遷移的 24 小時後取消追隨所有帳號。\n此帳戶的追隨中/追隨者人數將歸零。由於不會解除粉絲對您的追隨,因此他們仍然可以繼續閱覽此帳戶內僅對追隨者公開的貼文。"
movedTo: "要遷移到的帳戶:"
_achievements:
earnedAt: "獲得日期"
@@ -1798,7 +1863,7 @@ _role:
canHideAds: "不顯示廣告"
canSearchNotes: "可否搜尋貼文"
canUseTranslator: "使用翻譯功能"
- avatarDecorationLimit: "頭像裝飾的最大設置量"
+ avatarDecorationLimit: "頭像可掛上的最大裝飾數量"
canImportAntennas: "允許匯入天線"
canImportBlocking: "允許匯入封鎖名單"
canImportFollowing: "允許匯入追隨名單"
@@ -1957,7 +2022,7 @@ _instanceMute:
instanceMuteDescription: "包括對被靜音伺服器上的使用者的回覆,被設定的伺服器上所有貼文及轉發都會被靜音。"
instanceMuteDescription2: "設定時以換行進行分隔"
title: "將隱藏被設定的伺服器貼文。"
- heading: "將伺服器靜音"
+ heading: "要靜音的伺服器"
_theme:
explore: "探索佈景主題"
install: "安裝佈景主題"
@@ -1967,6 +2032,7 @@ _theme:
installed: "{name}已安裝"
installedThemes: "已經安裝的佈景主題"
builtinThemes: "標準佈景主題"
+ instanceTheme: "伺服器的主題"
alreadyInstalled: "已安裝此佈景主題"
invalid: "佈景主題格式錯誤"
make: "製作佈景主題"
@@ -2076,11 +2142,11 @@ _2fa:
setupCompleted: "設定完成"
step4: "從現在開始,任何登入操作都將要求您提供權杖。"
securityKeyNotSupported: "您的瀏覽器不支援安全金鑰。"
- registerTOTPBeforeKey: "如要註冊安全金鑰或 Passkey,請先設定驗證應用程式。"
- securityKeyInfo: "您可以設定使用支援 FIDO2 的硬體安全金鑰,以及裝置上的生物辨識、PIN 碼和密碼等來登入。"
- registerSecurityKey: "註冊安全金鑰或 Passkey"
+ registerTOTPBeforeKey: "如要註冊安全金鑰或通行金鑰,請先設定驗證應用程式。"
+ securityKeyInfo: "註冊 WebAuthn 衍生的金鑰,例如支援 FIDO2 的硬體安全金鑰、裝置生物識別、PIN 鎖和通行金鑰。"
+ registerSecurityKey: "註冊安全金鑰或通行金鑰"
securityKeyName: "輸入金鑰名稱"
- tapSecurityKey: "按照瀏覽器的說明註冊安全金鑰或 Passkey。"
+ tapSecurityKey: "按照瀏覽器的說明註冊安全金鑰或通行金鑰。"
removeKey: "刪除安全金鑰"
removeKeyConfirm: "要刪除{name}嗎?"
whyTOTPOnlyRenew: "如果註冊了安全金鑰,則無法解除驗證應用程式的設定。"
@@ -2302,7 +2368,7 @@ _profile:
avatarDecorationMax: "最多可以設置 {max} 個裝飾。"
followedMessage: "被追隨時的訊息"
followedMessageDescription: "可以設定被追隨時顯示給對方的訊息。"
- followedMessageDescriptionForLockedAccount: "如果追隨是需要審核的話,在允許追隨請求之後顯示。"
+ followedMessageDescriptionForLockedAccount: "如果追隨需要核准的話,將在通過追隨請求之後顯示。"
_exportOrImport:
allNotes: "所有貼文"
favoritedNotes: "「我的最愛」貼文"
@@ -2408,7 +2474,7 @@ _pages:
note: "嵌式貼文"
_note:
id: "貼文ID"
- idDescription: "您也可以粘貼筆記 URL 並進行設置。 "
+ idDescription: "您也可以貼上貼文 URL 來進行設定。 "
detailed: "顯示詳細內容"
_relayStatus:
requesting: "等待核准"
@@ -2422,7 +2488,7 @@ _notification:
youRenoted: "{name} 轉發了你的貼文"
youWereFollowed: "您有新的追隨者"
youReceivedFollowRequest: "您有新的追隨請求"
- yourFollowRequestAccepted: "您的追隨請求已通過"
+ yourFollowRequestAccepted: "您的追隨請求已被核准"
pollEnded: "問卷調查已產生結果"
newNote: "新的貼文"
unreadAntennaNote: "天線 {name}"
@@ -2440,6 +2506,8 @@ _notification:
flushNotification: "重置通知歷史紀錄"
exportOfXCompleted: "{x} 的匯出已完成。"
login: "已登入"
+ createToken: "已產生存取權杖"
+ createTokenDescription: "如果您不知道,請透過「{text}」刪除存取權杖。"
_types:
all: "全部 "
note: "使用者的最新貼文"
@@ -2456,6 +2524,7 @@ _notification:
achievementEarned: "獲得成就"
exportCompleted: "已完成匯出。"
login: "登入"
+ createToken: "建立存取權杖"
test: "通知測試"
app: "應用程式通知"
_actions:
@@ -2483,6 +2552,7 @@ _deck:
useSimpleUiForNonRootPages: "用簡易介面顯示非根頁面"
usedAsMinWidthWhenFlexible: "如果啟用「自動調整寬度」,此為最小寬度"
flexible: "自動調整寬度"
+ enableSyncBetweenDevicesForProfiles: "啟用裝置與裝置之間的設定檔資料同步化"
_columns:
main: "主列"
widgets: "小工具"
@@ -2590,6 +2660,7 @@ _moderationLogTypes:
deletePage: "刪除頁面"
deleteFlash: "刪除 Play"
deleteGalleryPost: "刪除相簿的貼文"
+ updateProxyAccountDescription: "更新代理帳戶的說明"
_fileViewer:
title: "檔案詳細資訊"
type: "檔案類型 "
@@ -2603,10 +2674,8 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "安裝前請確認提供者是可信賴的。"
_plugin:
title: "要安裝此外掛嘛?"
- metaTitle: "外掛資訊"
_theme:
title: "要安裝此佈景主題嗎?"
- metaTitle: "佈景主題資訊"
_meta:
base: "基本配色方案"
_vendorInfo:
@@ -2655,7 +2724,7 @@ _dataSaver:
_hemisphere:
N: "北半球"
S: "南半球"
- caption: "在某些客戶端的設定中,用於判斷季節。"
+ caption: "某些客戶端的設定會用此來判斷季節。"
_reversi:
reversi: "黑白棋"
gameSettings: "對弈設定"
@@ -2822,8 +2891,6 @@ _remoteLookupErrors:
_responseInvalid:
title: "回應不正確"
description: "雖然能夠與這個伺服器通訊,但是取得的資料不正確。"
- _responseInvalidIdHostNotMatch:
- description: "輸入的 URI 的網域與最終取得的 URI 的網域不同。 如果您是透過第三方伺服器查詢遠端內容,請使用可在原始伺服器上取得的 URI 再次查詢。"
_noSuchObject:
title: "查無項目"
description: "無法找到所要求的資源,請再次檢查 URI。"
@@ -2840,3 +2907,23 @@ _captcha:
_unknown:
title: "CAPTCHA 錯誤"
text: "發生了意外的錯誤。"
+_bootErrors:
+ title: "載入失敗"
+ serverError: "如果稍等片刻並重新載入後問題仍然存在,請聯絡您的伺服器管理員並提供以下的錯誤 ID。"
+ solution: "執行以下操作或許可以解決問題。"
+ solution1: "將瀏覽器和作業系統更新至最新版本"
+ solution2: "停用廣告攔截器"
+ solution3: "清除瀏覽器的快取"
+ solution4: "(Tor 瀏覽器)將 dom.webaudio.enabled 設為 true"
+ otherOption: "其他選項"
+ otherOption1: "刪除用戶端設定和快取"
+ otherOption2: "啟動簡易用戶端"
+ otherOption3: "啟動修復工具"
+_search:
+ searchScopeAll: "全部"
+ searchScopeLocal: "本地"
+ searchScopeServer: "指定伺服器"
+ searchScopeUser: "指定使用者"
+ pleaseEnterServerHost: "請輸入伺服器的主機名稱"
+ pleaseSelectUser: "請選擇使用者"
+ serverHostPlaceholder: "例:misskey.example.com"
diff --git a/package.json b/package.json
index a02afb7434..84adbd790c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "sharkey",
- "version": "2025.3.0-dev",
+ "version": "2025.3.2-beta.10",
"codename": "shonk",
"repository": {
"type": "git",
@@ -24,6 +24,7 @@
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"build-storybook": "pnpm --filter frontend build-storybook",
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
+ "build-frontend-search-index": "pnpm --filter frontend build-search-index",
"start": "pnpm check:connect && cd packages/backend && MK_WARNED_ABOUT_CONFIG=true node ./built/boot/entry.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
"init": "pnpm migrate",
@@ -40,7 +41,7 @@
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run",
"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
- "e2e-dev-container": "cp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
+ "e2e-dev-container": "ncp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
"jest": "cd packages/backend && pnpm jest",
"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
"test": "pnpm -r test",
@@ -50,34 +51,35 @@
"cleanall": "pnpm clean-all"
},
"resolutions": {
- "chokidar": "3.5.3",
+ "chokidar": "4.0.3",
"lodash": "4.17.21"
},
"dependencies": {
- "cssnano": "6.1.2",
- "execa": "8.0.1",
- "fast-glob": "3.3.2",
- "ignore-walk": "6.0.5",
+ "cssnano": "7.0.6",
+ "execa": "9.5.2",
+ "fast-glob": "3.3.3",
+ "ignore-walk": "7.0.0",
"js-yaml": "4.1.0",
- "postcss": "8.4.49",
- "tar": "6.2.1",
- "terser": "5.36.0",
- "typescript": "5.6.3",
- "esbuild": "0.24.0",
- "glob": "11.0.0"
+ "postcss": "8.5.3",
+ "tar": "7.4.3",
+ "terser": "5.39.0",
+ "typescript": "5.8.2",
+ "esbuild": "0.25.0",
+ "glob": "11.0.1"
},
"optionalDependencies": {
"cypress": "13.15.2"
},
"devDependencies": {
- "@misskey-dev/eslint-plugin": "2.0.3",
- "@types/node": "22.9.0",
- "@typescript-eslint/eslint-plugin": "7.17.0",
- "@typescript-eslint/parser": "7.17.0",
+ "@misskey-dev/eslint-plugin": "2.1.0",
+ "@types/node": "22.13.10",
+ "@typescript-eslint/eslint-plugin": "8.26.0",
+ "@typescript-eslint/parser": "8.26.0",
"cross-env": "7.0.3",
- "eslint": "9.14.0",
- "globals": "15.12.0",
+ "eslint": "9.22.0",
+ "globals": "16.0.0",
"ncp": "2.0.0",
- "start-server-and-test": "2.0.8"
+ "pnpm": "10.6.1",
+ "start-server-and-test": "2.0.10"
}
}
diff --git a/packages/backend/.swcrc b/packages/backend/.swcrc
index 845190b5f4..f4bf7a4d2a 100644
--- a/packages/backend/.swcrc
+++ b/packages/backend/.swcrc
@@ -1,5 +1,5 @@
{
- "$schema": "https://json.schemastore.org/swcrc",
+ "$schema": "https://swc.rs/schema.json",
"jsc": {
"parser": {
"syntax": "typescript",
diff --git a/packages/backend/migration/1739006797620-GoogleAnalytics.js b/packages/backend/migration/1739006797620-GoogleAnalytics.js
new file mode 100644
index 0000000000..5871bf098a
--- /dev/null
+++ b/packages/backend/migration/1739006797620-GoogleAnalytics.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class GoogleAnalytics1739006797620 {
+ name = 'GoogleAnalytics1739006797620'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "googleAnalyticsMeasurementId" character varying(64)`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "googleAnalyticsMeasurementId"`);
+ }
+}
diff --git a/packages/backend/migration/1740121393164-system-accounts.js b/packages/backend/migration/1740121393164-system-accounts.js
new file mode 100644
index 0000000000..9490cb2b64
--- /dev/null
+++ b/packages/backend/migration/1740121393164-system-accounts.js
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SystemAccounts1740121393164 {
+ name = 'SystemAccounts1740121393164'
+
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "system_account" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "type" character varying(256) NOT NULL, CONSTRAINT "PK_edb56f4aaf9ddd50ee556da97ba" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_41a3c87a37aea616ee459369e1" ON "system_account" ("userId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_c362033aee0ea51011386a5a7e" ON "system_account" ("type") `);
+ await queryRunner.query(`ALTER TABLE "system_account" ADD CONSTRAINT "FK_41a3c87a37aea616ee459369e12" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+
+ const instanceActor = await queryRunner.query(`SELECT "id" FROM "user" WHERE "username" = 'instance.actor'`);
+ if (instanceActor.length > 0) {
+ await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${instanceActor[0].id}', '${instanceActor[0].id}', 'actor')`);
+ }
+
+ const relayActor = await queryRunner.query(`SELECT "id" FROM "user" WHERE "username" = 'relay.actor'`);
+ if (relayActor.length > 0) {
+ await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${relayActor[0].id}', '${relayActor[0].id}', 'relay')`);
+ }
+
+ const meta = await queryRunner.query(`SELECT "proxyAccountId" FROM "meta" ORDER BY "id" DESC LIMIT 1`);
+ if (!meta && meta.length >= 1 && meta[0].proxyAccountId) {
+ await queryRunner.query(`INSERT INTO "system_account" ("id", "userId", "type") VALUES ('${meta[0].proxyAccountId}', '${meta[0].proxyAccountId}', 'proxy')`);
+ }
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "system_account" DROP CONSTRAINT "FK_41a3c87a37aea616ee459369e12"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_c362033aee0ea51011386a5a7e"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_41a3c87a37aea616ee459369e1"`);
+ await queryRunner.query(`DROP TABLE "system_account"`);
+ }
+}
diff --git a/packages/backend/migration/1740129169650-system-accounts-2.js b/packages/backend/migration/1740129169650-system-accounts-2.js
new file mode 100644
index 0000000000..c03f0337ab
--- /dev/null
+++ b/packages/backend/migration/1740129169650-system-accounts-2.js
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SystemAccounts21740129169650 {
+ name = 'SystemAccounts21740129169650'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "proxyAccountId"`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "proxyAccountId" character varying(32)`);
+ const proxyAccountId = await queryRunner.query(`SELECT "userId" FROM "system_account" WHERE "type" = 'proxy' ORDER BY "id" DESC LIMIT 1`);
+ if (proxyAccountId && proxyAccountId.length >= 1) {
+ await queryRunner.query(`UPDATE "meta" SET "proxyAccountId" = '${proxyAccountId[0].userId}'`);
+ }
+ await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_ab1bc0c1e209daa77b8e8d212ad" FOREIGN KEY ("proxyAccountId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ }
+}
diff --git a/packages/backend/migration/1740133121105-system-accounts-3.js b/packages/backend/migration/1740133121105-system-accounts-3.js
new file mode 100644
index 0000000000..a1f8c996f5
--- /dev/null
+++ b/packages/backend/migration/1740133121105-system-accounts-3.js
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SystemAccounts31740133121105 {
+ name = 'SystemAccounts31740133121105'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "rootUserId" character varying(32)`);
+ await queryRunner.query(`ALTER TABLE "meta" ADD CONSTRAINT "FK_c80e4079d632f95eac06a9d28cc" FOREIGN KEY ("rootUserId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+
+ const users = await queryRunner.query(`SELECT "id" FROM "user" WHERE "isRoot" = true LIMIT 1`);
+ if (users.length > 0) {
+ await queryRunner.query(`UPDATE "meta" SET "rootUserId" = $1`, [users[0].id]);
+ }
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "meta" DROP CONSTRAINT "FK_c80e4079d632f95eac06a9d28cc"`);
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "rootUserId"`);
+ }
+}
diff --git a/packages/backend/migration/1740993126937-system-accounts-4.js b/packages/backend/migration/1740993126937-system-accounts-4.js
new file mode 100644
index 0000000000..83654aca80
--- /dev/null
+++ b/packages/backend/migration/1740993126937-system-accounts-4.js
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SystemAccounts41740993126937 {
+ name = 'SystemAccounts41740993126937'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isRoot"`);
+ }
+
+ async down(queryRunner) {
+ // down 実行時は isRoot = true のユーザーが存在しなくなるため手動で対応する必要あり
+ await queryRunner.query(`ALTER TABLE "user" ADD "isRoot" boolean NOT NULL DEFAULT false`);
+ }
+}
diff --git a/packages/backend/migration/1741279404074-system-accounts-fixup.js b/packages/backend/migration/1741279404074-system-accounts-fixup.js
new file mode 100644
index 0000000000..31cab7f5ae
--- /dev/null
+++ b/packages/backend/migration/1741279404074-system-accounts-fixup.js
@@ -0,0 +1,26 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SystemAccounts1741279404074 {
+ name = 'SystemAccounts1741279404074'
+
+ async up(queryRunner) {
+ const instanceActor = await queryRunner.query(`SELECT "id" FROM "user" WHERE "username" = 'instance.actor' AND "host" IS NULL AND "id" NOT IN (SELECT "userId" FROM "system_account" WHERE "type" = 'actor')`);
+ if (instanceActor.length > 0) {
+ console.warn('instance.actor was incorrect, updating...');
+ await queryRunner.query(`UPDATE "system_account" SET "id" = '${instanceActor[0].id}', "userId" = '${instanceActor[0].id}' WHERE "type" = 'actor'`);
+ }
+
+ const relayActor = await queryRunner.query(`SELECT "id" FROM "user" WHERE "username" = 'relay.actor' AND "host" IS NULL AND "id" NOT IN (SELECT "userId" FROM "system_account" WHERE "type" = 'relay')`);
+ if (relayActor.length > 0) {
+ console.warn('relay.actor was incorrect, updating...');
+ await queryRunner.query(`UPDATE "system_account" SET "id" = '${relayActor[0].id}', "userId" = '${relayActor[0].id}' WHERE "type" = 'relay'`);
+ }
+ }
+
+ async down(queryRunner) {
+ // fixup migration, no down migration
+ }
+}
diff --git a/packages/backend/migration/1741424411879-user-featured-fixup.js b/packages/backend/migration/1741424411879-user-featured-fixup.js
new file mode 100644
index 0000000000..5643a328f0
--- /dev/null
+++ b/packages/backend/migration/1741424411879-user-featured-fixup.js
@@ -0,0 +1,26 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class UserFeaturedFixup1741424411879 {
+ name = 'UserFeaturedFixup1741424411879'
+
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE OR REPLACE FUNCTION pg_temp.extract_ap_id(text) RETURNS text AS $$
+ SELECT
+ CASE
+ WHEN $1 ~ '^https?://' THEN $1
+ WHEN $1 LIKE '{%' THEN COALESCE(jsonb_extract_path_text($1::jsonb, 'id'), null)
+ ELSE null
+ END;
+ $$ LANGUAGE sql IMMUTABLE;`);
+
+ // "host" is NOT NULL is not needed but just in case add it to prevent overwriting irreplaceable data
+ await queryRunner.query(`UPDATE "user" SET "featured" = pg_temp.extract_ap_id("featured") WHERE "host" IS NOT NULL`);
+ }
+
+ async down(queryRunner) {
+ // fixup migration, no down migration
+ }
+}
diff --git a/packages/backend/migration/1742203321812-chat.js b/packages/backend/migration/1742203321812-chat.js
new file mode 100644
index 0000000000..3d8f7276b5
--- /dev/null
+++ b/packages/backend/migration/1742203321812-chat.js
@@ -0,0 +1,46 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Chat1742203321812 {
+ name = 'Chat1742203321812'
+
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "chat_room" ("id" character varying(32) NOT NULL, "name" character varying(256) NOT NULL, "ownerId" character varying(32) NOT NULL, CONSTRAINT "PK_8aa3a52cf74c96469f0ef9fbe3e" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_f0d8ad64243fa2ca2800da0dfd" ON "chat_room" ("ownerId") `);
+ await queryRunner.query(`CREATE TABLE "chat_message" ("id" character varying(32) NOT NULL, "fromUserId" character varying(32) NOT NULL, "toUserId" character varying(32), "toRoomId" character varying(32), "text" character varying(4096), "uri" character varying(512), "reads" character varying(32) array NOT NULL DEFAULT '{}', "fileId" character varying(32), "reactions" character varying(1024) array NOT NULL DEFAULT '{}', CONSTRAINT "PK_3cc0d85193aade457d3077dd06b" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_79a26e7a4d9afa5e4fc05f134e" ON "chat_message" ("fromUserId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_25e097b51d7622c249452c6f75" ON "chat_message" ("toUserId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_f006b8a76efd1abf9f221c175c" ON "chat_message" ("toRoomId") `);
+ await queryRunner.query(`CREATE TABLE "chat_room_membership" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "roomId" character varying(32) NOT NULL, CONSTRAINT "PK_2bd59c741e571b283c048beb69a" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_d99c5279460fb77ef58c596ce5" ON "chat_room_membership" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_c25143ebab714e930aeca1c0e8" ON "chat_room_membership" ("roomId") `);
+ await queryRunner.query(`ALTER TABLE "chat_room" ADD CONSTRAINT "FK_f0d8ad64243fa2ca2800da0dfd6" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "chat_message" ADD CONSTRAINT "FK_79a26e7a4d9afa5e4fc05f134ed" FOREIGN KEY ("fromUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "chat_message" ADD CONSTRAINT "FK_25e097b51d7622c249452c6f757" FOREIGN KEY ("toUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "chat_message" ADD CONSTRAINT "FK_f006b8a76efd1abf9f221c175ce" FOREIGN KEY ("toRoomId") REFERENCES "chat_room"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "chat_message" ADD CONSTRAINT "FK_fd0f9a4879430239715ad4f8e2a" FOREIGN KEY ("fileId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "chat_room_membership" ADD CONSTRAINT "FK_d99c5279460fb77ef58c596ce51" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "chat_room_membership" ADD CONSTRAINT "FK_c25143ebab714e930aeca1c0e8d" FOREIGN KEY ("roomId") REFERENCES "chat_room"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "chat_room_membership" DROP CONSTRAINT "FK_c25143ebab714e930aeca1c0e8d"`);
+ await queryRunner.query(`ALTER TABLE "chat_room_membership" DROP CONSTRAINT "FK_d99c5279460fb77ef58c596ce51"`);
+ await queryRunner.query(`ALTER TABLE "chat_message" DROP CONSTRAINT "FK_fd0f9a4879430239715ad4f8e2a"`);
+ await queryRunner.query(`ALTER TABLE "chat_message" DROP CONSTRAINT "FK_f006b8a76efd1abf9f221c175ce"`);
+ await queryRunner.query(`ALTER TABLE "chat_message" DROP CONSTRAINT "FK_25e097b51d7622c249452c6f757"`);
+ await queryRunner.query(`ALTER TABLE "chat_message" DROP CONSTRAINT "FK_79a26e7a4d9afa5e4fc05f134ed"`);
+ await queryRunner.query(`ALTER TABLE "chat_room" DROP CONSTRAINT "FK_f0d8ad64243fa2ca2800da0dfd6"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_c25143ebab714e930aeca1c0e8"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_d99c5279460fb77ef58c596ce5"`);
+ await queryRunner.query(`DROP TABLE "chat_room_membership"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_f006b8a76efd1abf9f221c175c"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_25e097b51d7622c249452c6f75"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_79a26e7a4d9afa5e4fc05f134e"`);
+ await queryRunner.query(`DROP TABLE "chat_message"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_f0d8ad64243fa2ca2800da0dfd"`);
+ await queryRunner.query(`DROP TABLE "chat_room"`);
+ }
+}
diff --git a/packages/backend/migration/1742608337548-chat-2.js b/packages/backend/migration/1742608337548-chat-2.js
new file mode 100644
index 0000000000..9f74a263d6
--- /dev/null
+++ b/packages/backend/migration/1742608337548-chat-2.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Chat21742608337548 {
+ name = 'Chat21742608337548'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "user" ADD "chatScope" character varying(128) NOT NULL DEFAULT 'mutual'`);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_185b6b5afa707b5d36d1ce3144" ON "chat_room_membership" ("userId", "roomId") `);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "public"."IDX_185b6b5afa707b5d36d1ce3144"`);
+ await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "chatScope"`);
+ }
+}
diff --git a/packages/backend/migration/1742617546147-chat-3.js b/packages/backend/migration/1742617546147-chat-3.js
new file mode 100644
index 0000000000..116b9a738b
--- /dev/null
+++ b/packages/backend/migration/1742617546147-chat-3.js
@@ -0,0 +1,26 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Chat31742617546147 {
+ name = 'Chat31742617546147'
+
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "chat_approval" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "otherId" character varying(32) NOT NULL, CONSTRAINT "PK_fbbb95d60acf5c85388345b5f5d" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_530257863e1381a7f2f1d3282f" ON "chat_approval" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_b1d46037f23d170da5c05fdf75" ON "chat_approval" ("otherId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_12c4768a2f706fc267f2078903" ON "chat_approval" ("userId", "otherId") `);
+ await queryRunner.query(`ALTER TABLE "chat_approval" ADD CONSTRAINT "FK_530257863e1381a7f2f1d3282fe" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "chat_approval" ADD CONSTRAINT "FK_b1d46037f23d170da5c05fdf755" FOREIGN KEY ("otherId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "chat_approval" DROP CONSTRAINT "FK_b1d46037f23d170da5c05fdf755"`);
+ await queryRunner.query(`ALTER TABLE "chat_approval" DROP CONSTRAINT "FK_530257863e1381a7f2f1d3282fe"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_12c4768a2f706fc267f2078903"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_b1d46037f23d170da5c05fdf75"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_530257863e1381a7f2f1d3282f"`);
+ await queryRunner.query(`DROP TABLE "chat_approval"`);
+ }
+}
diff --git a/packages/backend/migration/1742707840715-chat-4.js b/packages/backend/migration/1742707840715-chat-4.js
new file mode 100644
index 0000000000..953a53d880
--- /dev/null
+++ b/packages/backend/migration/1742707840715-chat-4.js
@@ -0,0 +1,26 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Chat41742707840715 {
+ name = 'Chat41742707840715'
+
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE TABLE "chat_room_invitation" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "roomId" character varying(32) NOT NULL, CONSTRAINT "PK_9d489521a312dd28225672de2dc" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_8552bb38e7ed038c5bdd398a38" ON "chat_room_invitation" ("userId") `);
+ await queryRunner.query(`CREATE INDEX "IDX_5f265075b215fc390a57523b12" ON "chat_room_invitation" ("roomId") `);
+ await queryRunner.query(`CREATE UNIQUE INDEX "IDX_044f2a7962b8ee5bbfaa02e8a3" ON "chat_room_invitation" ("userId", "roomId") `);
+ await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD CONSTRAINT "FK_8552bb38e7ed038c5bdd398a384" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD CONSTRAINT "FK_5f265075b215fc390a57523b12a" FOREIGN KEY ("roomId") REFERENCES "chat_room"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP CONSTRAINT "FK_5f265075b215fc390a57523b12a"`);
+ await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP CONSTRAINT "FK_8552bb38e7ed038c5bdd398a384"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_044f2a7962b8ee5bbfaa02e8a3"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_5f265075b215fc390a57523b12"`);
+ await queryRunner.query(`DROP INDEX "public"."IDX_8552bb38e7ed038c5bdd398a38"`);
+ await queryRunner.query(`DROP TABLE "chat_room_invitation"`);
+ }
+}
diff --git a/packages/backend/migration/1742721896936-chat-5.js b/packages/backend/migration/1742721896936-chat-5.js
new file mode 100644
index 0000000000..00db787cb7
--- /dev/null
+++ b/packages/backend/migration/1742721896936-chat-5.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Chat51742721896936 {
+ name = 'Chat51742721896936'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "chat_room_invitation" ADD "ignored" boolean NOT NULL DEFAULT false`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "chat_room_invitation" DROP COLUMN "ignored"`);
+ }
+}
diff --git a/packages/backend/migration/1742795111958-chat-6.js b/packages/backend/migration/1742795111958-chat-6.js
new file mode 100644
index 0000000000..9a5dc3e32f
--- /dev/null
+++ b/packages/backend/migration/1742795111958-chat-6.js
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Chat61742795111958 {
+ name = 'Chat61742795111958'
+
+ async up(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "chat_room" ADD "description" character varying(2048) NOT NULL DEFAULT ''`);
+ await queryRunner.query(`ALTER TABLE "chat_room" ADD "isArchived" boolean NOT NULL DEFAULT false`);
+ await queryRunner.query(`ALTER TABLE "chat_room_membership" ADD "isMuted" boolean NOT NULL DEFAULT false`);
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`ALTER TABLE "chat_room_membership" DROP COLUMN "isMuted"`);
+ await queryRunner.query(`ALTER TABLE "chat_room" DROP COLUMN "isArchived"`);
+ await queryRunner.query(`ALTER TABLE "chat_room" DROP COLUMN "description"`);
+ }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 9093bf34c5..ff4f2d7cf3 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -37,18 +37,18 @@
},
"optionalDependencies": {
"@swc/core-android-arm64": "1.3.11",
- "@swc/core-darwin-arm64": "1.3.56",
- "@swc/core-darwin-x64": "1.3.56",
+ "@swc/core-darwin-arm64": "1.11.11",
+ "@swc/core-darwin-x64": "1.11.11",
"@swc/core-freebsd-x64": "1.3.11",
- "@swc/core-linux-arm-gnueabihf": "1.3.56",
- "@swc/core-linux-arm64-gnu": "1.3.56",
- "@swc/core-linux-arm64-musl": "1.3.56",
- "@swc/core-linux-x64-gnu": "1.3.56",
- "@swc/core-linux-x64-musl": "1.3.56",
- "@swc/core-win32-arm64-msvc": "1.3.56",
- "@swc/core-win32-ia32-msvc": "1.3.56",
- "@swc/core-win32-x64-msvc": "1.3.56",
- "bufferutil": "4.0.7",
+ "@swc/core-linux-arm-gnueabihf": "1.11.11",
+ "@swc/core-linux-arm64-gnu": "1.11.11",
+ "@swc/core-linux-arm64-musl": "1.11.11",
+ "@swc/core-linux-x64-gnu": "1.11.11",
+ "@swc/core-linux-x64-musl": "1.11.11",
+ "@swc/core-win32-arm64-msvc": "1.11.11",
+ "@swc/core-win32-ia32-msvc": "1.11.11",
+ "@swc/core-win32-x64-msvc": "1.11.11",
+ "bufferutil": "4.0.9",
"slacc-android-arm-eabi": "0.0.10",
"slacc-android-arm64": "0.0.10",
"slacc-darwin-arm64": "0.0.10",
@@ -62,36 +62,33 @@
"slacc-linux-x64-musl": "0.0.10",
"slacc-win32-arm64-msvc": "0.0.10",
"slacc-win32-x64-msvc": "0.0.10",
- "utf-8-validate": "6.0.3"
+ "utf-8-validate": "6.0.5"
},
"dependencies": {
- "@aws-sdk/client-s3": "3.620.0",
- "@aws-sdk/lib-storage": "3.620.0",
- "@bull-board/api": "6.5.0",
- "@bull-board/fastify": "6.5.0",
- "@bull-board/ui": "6.5.0",
+ "@aws-sdk/client-s3": "3.772.0",
+ "@aws-sdk/lib-storage": "3.772.0",
"@discordapp/twemoji": "15.1.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",
+ "@fastify/accepts": "5.0.2",
+ "@fastify/cookie": "11.0.2",
+ "@fastify/cors": "10.1.0",
+ "@fastify/express": "4.0.2",
+ "@fastify/http-proxy": "10.0.2",
+ "@fastify/multipart": "9.0.3",
+ "@fastify/static": "8.1.1",
+ "@fastify/view": "10.0.2",
"@misskey-dev/sharp-read-bmp": "1.2.0",
- "@misskey-dev/summaly": "5.1.0",
- "@nestjs/common": "10.4.7",
- "@nestjs/core": "10.4.7",
- "@nestjs/testing": "10.4.7",
+ "@misskey-dev/summaly": "5.2.0",
+ "@nestjs/common": "11.0.12",
+ "@nestjs/core": "11.0.12",
+ "@nestjs/testing": "11.0.12",
"@peertube/http-signature": "1.7.0",
- "@sentry/node": "8.38.0",
- "@sentry/profiling-node": "8.38.0",
- "@simplewebauthn/server": "10.0.1",
- "@sinonjs/fake-timers": "11.2.2",
+ "@sentry/node": "8.55.0",
+ "@sentry/profiling-node": "8.55.0",
+ "@simplewebauthn/server": "12.0.0",
+ "@sinonjs/fake-timers": "11.3.1",
"@smithy/node-http-handler": "2.5.0",
- "@swc/cli": "0.3.12",
- "@swc/core": "1.9.2",
+ "@swc/cli": "0.6.0",
+ "@swc/core": "1.11.11",
"@transfem-org/sfm-js": "0.24.6",
"@twemoji/parser": "15.1.1",
"@types/psl": "^1.1.3",
@@ -103,11 +100,11 @@
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.3",
- "bullmq": "5.26.1",
+ "bullmq": "5.44.1",
"cacheable-lookup": "7.0.0",
"canvas": "^3.1.0",
"cbor": "9.0.2",
- "chalk": "5.3.0",
+ "chalk": "5.4.1",
"chalk-template": "1.1.0",
"chokidar": "3.6.0",
"cli-highlight": "2.1.11",
@@ -116,48 +113,48 @@
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
"fast-xml-parser": "4.4.1",
- "fastify": "5.0.0",
+ "fastify": "5.2.1",
"fastify-multer": "^2.0.3",
"fastify-raw-body": "5.0.0",
"feed": "4.2.2",
"file-type": "19.6.0",
"fluent-ffmpeg": "2.1.3",
- "form-data": "4.0.1",
+ "form-data": "4.0.2",
"glob": "11.0.0",
- "got": "14.4.4",
- "happy-dom": "15.11.4",
+ "got": "14.4.6",
+ "happy-dom": "16.8.1",
"hpagent": "1.2.0",
"htmlescape": "1.1.1",
"http-link-header": "1.1.3",
- "ioredis": "5.4.1",
+ "ioredis": "5.6.0",
"ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0",
"is-svg": "5.1.0",
"js-yaml": "4.1.0",
- "jsdom": "24.1.1",
+ "jsdom": "26.0.0",
"json5": "2.2.3",
- "jsonld": "8.3.2",
+ "jsonld": "8.3.3",
"jsrsasign": "11.1.0",
- "juice": "11.0.0",
+ "juice": "11.0.1",
"megalodon": "workspace:*",
- "meilisearch": "0.45.0",
+ "meilisearch": "0.49.0",
"microformats-parser": "2.0.2",
"mime-types": "2.1.35",
"misskey-js": "workspace:*",
"misskey-reversi": "workspace:*",
"moment": "^2.30.1",
"ms": "3.0.0-canary.1",
- "nanoid": "5.0.8",
+ "nanoid": "5.1.5",
"nested-property": "4.0.0",
"node-fetch": "3.3.2",
- "nodemailer": "6.9.16",
- "oauth": "0.10.0",
+ "nodemailer": "6.10.0",
+ "oauth": "0.10.2",
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
- "otpauth": "9.3.4",
+ "otpauth": "9.3.6",
"parse5": "7.2.1",
- "pg": "8.13.1",
+ "pg": "8.14.1",
"pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3",
"promise-limit": "2.7.0",
@@ -172,31 +169,31 @@
"reflect-metadata": "0.2.2",
"rename": "1.0.4",
"rss-parser": "3.13.0",
- "rxjs": "7.8.1",
- "sanitize-html": "2.13.1",
- "secure-json-parse": "2.7.0",
+ "rxjs": "7.8.2",
+ "sanitize-html": "2.15.0",
+ "secure-json-parse": "3.0.2",
"sharp": "0.33.5",
"slacc": "0.0.10",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
- "systeminformation": "5.23.5",
+ "systeminformation": "5.25.11",
"tinycolor2": "1.6.0",
"tmp": "0.2.3",
- "tsc-alias": "1.8.10",
+ "tsc-alias": "1.8.11",
"tsconfig-paths": "4.2.0",
- "typeorm": "0.3.20",
- "typescript": "5.6.3",
- "ulid": "2.3.0",
+ "typeorm": "0.3.21",
+ "typescript": "5.8.2",
+ "ulid": "2.4.0",
"uuid": "^9.0.1",
"vary": "1.1.2",
"web-push": "3.6.7",
- "ws": "8.18.0",
+ "ws": "8.18.1",
"xev": "3.0.2"
},
"devDependencies": {
"@jest/globals": "29.7.0",
- "@nestjs/platform-express": "10.4.7",
- "@simplewebauthn/types": "10.0.0",
+ "@nestjs/platform-express": "10.4.15",
+ "@simplewebauthn/types": "12.0.0",
"@swc/jest": "0.2.37",
"@types/accepts": "1.3.7",
"@types/archiver": "6.0.3",
@@ -211,15 +208,15 @@
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.7",
"@types/jsonld": "1.5.15",
- "@types/jsrsasign": "10.5.14",
+ "@types/jsrsasign": "10.5.15",
"@types/mime-types": "2.1.4",
"@types/ms": "0.7.34",
- "@types/node": "22.9.0",
- "@types/nodemailer": "6.4.16",
+ "@types/node": "22.13.10",
+ "@types/nodemailer": "6.4.17",
"@types/oauth": "0.9.6",
"@types/oauth2orize": "1.11.5",
"@types/oauth2orize-pkce": "0.1.2",
- "@types/pg": "8.11.10",
+ "@types/pg": "8.11.11",
"@types/proxy-addr": "^2.0.3",
"@types/pug": "2.0.10",
"@types/qrcode": "1.5.5",
@@ -235,18 +232,18 @@
"@types/uuid": "^9.0.4",
"@types/vary": "1.1.3",
"@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",
+ "@types/ws": "8.18.0",
+ "@typescript-eslint/eslint-plugin": "8.27.0",
+ "@typescript-eslint/parser": "8.27.0",
+ "aws-sdk-client-mock": "4.1.0",
"cross-env": "7.0.3",
- "eslint-plugin-import": "2.30.0",
+ "eslint-plugin-import": "2.31.0",
"execa": "8.0.1",
"fkill": "9.0.0",
"jest": "29.7.0",
"jest-mock": "29.7.0",
- "nodemon": "3.1.7",
- "pid-port": "1.0.0",
+ "nodemon": "3.1.9",
+ "pid-port": "1.0.2",
"simple-oauth2": "5.1.0"
}
}
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 7ca566477d..92d1bf20fa 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -141,7 +141,7 @@ const $meta: Provider = {
for (const key in body.after) {
(meta as any)[key] = (body.after as any)[key];
}
- meta.proxyAccount = null; // joinなカラムは通常取ってこないので
+ meta.rootUser = null; // joinなカラムは通常取ってこないので
break;
}
default:
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index 067672fd79..2542cce778 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -77,6 +77,7 @@ type Source = {
proxyBypassHosts?: string[];
allowedPrivateNetworks?: string[];
+ disallowExternalApRedirect?: boolean;
maxFileSize?: number;
maxNoteLength?: number;
@@ -126,8 +127,8 @@ type Source = {
logging?: {
sql?: {
- disableQueryTruncation? : boolean,
- enableQueryParamLogging? : boolean,
+ disableQueryTruncation?: boolean,
+ enableQueryParamLogging?: boolean,
}
}
@@ -179,6 +180,7 @@ export type Config = {
proxySmtp: string | undefined;
proxyBypassHosts: string[] | undefined;
allowedPrivateNetworks: string[] | undefined;
+ disallowExternalApRedirect: boolean;
maxFileSize: number;
maxNoteLength: number;
maxRemoteNoteLength: number;
@@ -206,8 +208,8 @@ export type Config = {
checkActivityPubGetSignature: boolean | undefined;
logging?: {
sql?: {
- disableQueryTruncation? : boolean,
- enableQueryParamLogging? : boolean,
+ disableQueryTruncation?: boolean,
+ enableQueryParamLogging?: boolean,
}
}
@@ -360,6 +362,7 @@ export function loadConfig(): Config {
proxySmtp: config.proxySmtp,
proxyBypassHosts: config.proxyBypassHosts,
allowedPrivateNetworks: config.allowedPrivateNetworks,
+ disallowExternalApRedirect: config.disallowExternalApRedirect ?? false,
maxFileSize: config.maxFileSize ?? 262144000,
maxNoteLength: config.maxNoteLength ?? 3000,
maxRemoteNoteLength: config.maxRemoteNoteLength ?? 100000,
diff --git a/packages/backend/src/core/AbuseReportService.ts b/packages/backend/src/core/AbuseReportService.ts
index 0b022d3b08..846d2c8ebd 100644
--- a/packages/backend/src/core/AbuseReportService.ts
+++ b/packages/backend/src/core/AbuseReportService.ts
@@ -10,9 +10,9 @@ import { bindThis } from '@/decorators.js';
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiUser, UsersRepository } from '@/models/_.js';
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
import { QueueService } from '@/core/QueueService.js';
-import { InstanceActorService } from '@/core/InstanceActorService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
import { IdService } from './IdService.js';
@Injectable()
@@ -27,7 +27,7 @@ export class AbuseReportService {
private idService: IdService,
private abuseReportNotificationService: AbuseReportNotificationService,
private queueService: QueueService,
- private instanceActorService: InstanceActorService,
+ private systemAccountService: SystemAccountService,
private apRendererService: ApRendererService,
private moderationLogService: ModerationLogService,
) {
@@ -136,7 +136,7 @@ export class AbuseReportService {
forwarded: true,
});
- const actor = await this.instanceActorService.getInstanceActor();
+ const actor = await this.systemAccountService.fetch('actor');
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index e24fefb4b5..2dbd16b5fe 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -20,10 +20,10 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { ProxyAccountService } from '@/core/ProxyAccountService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import InstanceChart from '@/core/chart/charts/instance.js';
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
@Injectable()
export class AccountMoveService {
@@ -58,12 +58,12 @@ export class AccountMoveService {
private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService,
private globalEventService: GlobalEventService,
- private proxyAccountService: ProxyAccountService,
private perUserFollowingChart: PerUserFollowingChart,
private federatedInstanceService: FederatedInstanceService,
private instanceChart: InstanceChart,
private relayService: RelayService,
private queueService: QueueService,
+ private systemAccountService: SystemAccountService,
) {
}
@@ -130,11 +130,11 @@ export class AccountMoveService {
}
// follow the new account
- const proxy = await this.proxyAccountService.fetch();
+ const proxy = await this.systemAccountService.fetch('proxy');
const followings = await this.followingsRepository.findBy({
followeeId: src.id,
followerHost: IsNull(), // follower is local
- followerId: proxy ? Not(proxy.id) : undefined,
+ followerId: Not(proxy.id),
});
const followJobs = followings.map(following => ({
from: { id: following.followerId },
@@ -269,10 +269,8 @@ export class AccountMoveService {
// Have the proxy account follow the new account in the same way as UserListService.push
if (this.userEntityService.isRemoteUser(dst)) {
- const proxy = await this.proxyAccountService.fetch();
- if (proxy) {
- this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
- }
+ const proxy = await this.systemAccountService.fetch('proxy');
+ this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
}
}
diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts
index d17101ac97..5bf0f82163 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -47,7 +47,7 @@ export type CaptchaSetting = {
siteKey: string | null;
secretKey: string | null;
}
-}
+};
export class CaptchaError extends Error {
public readonly code: CaptchaErrorCode;
@@ -63,11 +63,11 @@ export class CaptchaError extends Error {
export type CaptchaSaveSuccess = {
success: true;
-}
+};
export type CaptchaSaveFailure = {
success: false;
error: CaptchaError;
-}
+};
export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure;
type CaptchaResponse = {
diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts
new file mode 100644
index 0000000000..57e33af107
--- /dev/null
+++ b/packages/backend/src/core/ChatService.ts
@@ -0,0 +1,780 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import { Brackets } from 'typeorm';
+import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
+import { QueueService } from '@/core/QueueService.js';
+import { IdService } from '@/core/IdService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
+import { PushNotificationService } from '@/core/PushNotificationService.js';
+import { bindThis } from '@/decorators.js';
+import type { ChatApprovalsRepository, ChatMessagesRepository, ChatRoomInvitationsRepository, ChatRoomMembershipsRepository, ChatRoomsRepository, MiChatMessage, MiChatRoom, MiChatRoomMembership, MiDriveFile, MiUser, MutingsRepository, UsersRepository } from '@/models/_.js';
+import { UserBlockingService } from '@/core/UserBlockingService.js';
+import { QueryService } from '@/core/QueryService.js';
+import { RoleService } from '@/core/RoleService.js';
+import { UserFollowingService } from '@/core/UserFollowingService.js';
+import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
+import { Packed } from '@/misc/json-schema.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { emojiRegex } from '@/misc/emoji-regex.js';
+
+const MAX_ROOM_MEMBERS = 30;
+const MAX_REACTIONS_PER_MESSAGE = 100;
+const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/;
+
+@Injectable()
+export class ChatService {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.chatMessagesRepository)
+ private chatMessagesRepository: ChatMessagesRepository,
+
+ @Inject(DI.chatApprovalsRepository)
+ private chatApprovalsRepository: ChatApprovalsRepository,
+
+ @Inject(DI.chatRoomsRepository)
+ private chatRoomsRepository: ChatRoomsRepository,
+
+ @Inject(DI.chatRoomInvitationsRepository)
+ private chatRoomInvitationsRepository: ChatRoomInvitationsRepository,
+
+ @Inject(DI.chatRoomMembershipsRepository)
+ private chatRoomMembershipsRepository: ChatRoomMembershipsRepository,
+
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
+
+ private userEntityService: UserEntityService,
+ private chatEntityService: ChatEntityService,
+ private idService: IdService,
+ private globalEventService: GlobalEventService,
+ private apRendererService: ApRendererService,
+ private queueService: QueueService,
+ private pushNotificationService: PushNotificationService,
+ private userBlockingService: UserBlockingService,
+ private queryService: QueryService,
+ private roleService: RoleService,
+ private userFollowingService: UserFollowingService,
+ private customEmojiService: CustomEmojiService,
+ ) {
+ }
+
+ @bindThis
+ public async createMessageToUser(fromUser: { id: MiUser['id']; host: MiUser['host']; }, toUser: MiUser, params: {
+ text?: string | null;
+ file?: MiDriveFile | null;
+ uri?: string | null;
+ }): Promise<Packed<'ChatMessageLite'>> {
+ if (fromUser.id === toUser.id) {
+ throw new Error('yourself');
+ }
+
+ const approvals = await this.chatApprovalsRepository.createQueryBuilder('approval')
+ .where(new Brackets(qb => { // 自分が相手を許可しているか
+ qb.where('approval.userId = :fromUserId', { fromUserId: fromUser.id })
+ .andWhere('approval.otherId = :toUserId', { toUserId: toUser.id });
+ }))
+ .orWhere(new Brackets(qb => { // 相手が自分を許可しているか
+ qb.where('approval.userId = :toUserId', { toUserId: toUser.id })
+ .andWhere('approval.otherId = :fromUserId', { fromUserId: fromUser.id });
+ }))
+ .take(2)
+ .getMany();
+
+ const otherApprovedMe = approvals.some(approval => approval.userId === toUser.id);
+ const iApprovedOther = approvals.some(approval => approval.userId === fromUser.id);
+
+ if (!otherApprovedMe) {
+ if (toUser.chatScope === 'none') {
+ throw new Error('recipient is cannot chat (none)');
+ } else if (toUser.chatScope === 'followers') {
+ const isFollower = await this.userFollowingService.isFollowing(fromUser.id, toUser.id);
+ if (!isFollower) {
+ throw new Error('recipient is cannot chat (followers)');
+ }
+ } else if (toUser.chatScope === 'following') {
+ const isFollowing = await this.userFollowingService.isFollowing(toUser.id, fromUser.id);
+ if (!isFollowing) {
+ throw new Error('recipient is cannot chat (following)');
+ }
+ } else if (toUser.chatScope === 'mutual') {
+ const isMutual = await this.userFollowingService.isMutual(fromUser.id, toUser.id);
+ if (!isMutual) {
+ throw new Error('recipient is cannot chat (mutual)');
+ }
+ }
+ }
+
+ if (!(await this.roleService.getUserPolicies(toUser.id)).canChat) {
+ throw new Error('recipient is cannot chat (policy)');
+ }
+
+ const blocked = await this.userBlockingService.checkBlocked(toUser.id, fromUser.id);
+ if (blocked) {
+ throw new Error('blocked');
+ }
+
+ const message = {
+ id: this.idService.gen(),
+ fromUserId: fromUser.id,
+ toUserId: toUser.id,
+ text: params.text ? params.text.trim() : null,
+ fileId: params.file ? params.file.id : null,
+ reads: [],
+ uri: params.uri ?? null,
+ } satisfies Partial<MiChatMessage>;
+
+ const inserted = await this.chatMessagesRepository.insertOne(message);
+
+ // 相手を許可しておく
+ if (!iApprovedOther) {
+ this.chatApprovalsRepository.insertOne({
+ id: this.idService.gen(),
+ userId: fromUser.id,
+ otherId: toUser.id,
+ });
+ }
+
+ const packedMessage = await this.chatEntityService.packMessageLiteFor1on1(inserted);
+
+ if (this.userEntityService.isLocalUser(toUser)) {
+ const redisPipeline = this.redisClient.pipeline();
+ redisPipeline.set(`newUserChatMessageExists:${toUser.id}:${fromUser.id}`, message.id);
+ redisPipeline.sadd(`newChatMessagesExists:${toUser.id}`, `user:${fromUser.id}`);
+ redisPipeline.exec();
+ }
+
+ if (this.userEntityService.isLocalUser(fromUser)) {
+ // 自分のストリーム
+ this.globalEventService.publishChatUserStream(fromUser.id, toUser.id, 'message', packedMessage);
+ }
+
+ if (this.userEntityService.isLocalUser(toUser)) {
+ // 相手のストリーム
+ this.globalEventService.publishChatUserStream(toUser.id, fromUser.id, 'message', packedMessage);
+ }
+
+ // 3秒経っても既読にならなかったらイベント発行
+ if (this.userEntityService.isLocalUser(toUser)) {
+ setTimeout(async () => {
+ const marker = await this.redisClient.get(`newUserChatMessageExists:${toUser.id}:${fromUser.id}`);
+
+ if (marker == null) return; // 既読
+
+ const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted, toUser);
+ this.globalEventService.publishMainStream(toUser.id, 'newChatMessage', packedMessageForTo);
+ //this.pushNotificationService.pushNotification(toUser.id, 'newChatMessage', packedMessageForTo);
+ }, 3000);
+ }
+
+ return packedMessage;
+ }
+
+ @bindThis
+ public async createMessageToRoom(fromUser: { id: MiUser['id']; host: MiUser['host']; }, toRoom: MiChatRoom, params: {
+ text?: string | null;
+ file?: MiDriveFile | null;
+ uri?: string | null;
+ }): Promise<Packed<'ChatMessageLite'>> {
+ const memberships = await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id });
+
+ if (toRoom.ownerId !== fromUser.id && !memberships.some(member => member.userId === fromUser.id)) {
+ throw new Error('you are not a member of the room');
+ }
+
+ const message = {
+ id: this.idService.gen(),
+ fromUserId: fromUser.id,
+ toRoomId: toRoom.id,
+ text: params.text ? params.text.trim() : null,
+ fileId: params.file ? params.file.id : null,
+ reads: [],
+ uri: params.uri ?? null,
+ } satisfies Partial<MiChatMessage>;
+
+ const inserted = await this.chatMessagesRepository.insertOne(message);
+
+ const packedMessage = await this.chatEntityService.packMessageLiteForRoom(inserted);
+
+ this.globalEventService.publishChatRoomStream(toRoom.id, 'message', packedMessage);
+
+ const redisPipeline = this.redisClient.pipeline();
+ for (const membership of memberships) {
+ if (membership.isMuted) continue;
+
+ redisPipeline.set(`newRoomChatMessageExists:${membership.userId}:${toRoom.id}`, message.id);
+ redisPipeline.sadd(`newChatMessagesExists:${membership.userId}`, `room:${toRoom.id}`);
+ }
+ redisPipeline.exec();
+
+ // 3秒経っても既読にならなかったらイベント発行
+ setTimeout(async () => {
+ const redisPipeline = this.redisClient.pipeline();
+ for (const membership of memberships) {
+ redisPipeline.get(`newRoomChatMessageExists:${membership.userId}:${toRoom.id}`);
+ }
+ const markers = await redisPipeline.exec();
+ if (markers == null) throw new Error('redis error');
+
+ if (markers.every(marker => marker[1] == null)) return;
+
+ const packedMessageForTo = await this.chatEntityService.packMessageDetailed(inserted);
+
+ for (let i = 0; i < memberships.length; i++) {
+ const marker = markers[i][1];
+ if (marker == null) continue;
+
+ this.globalEventService.publishMainStream(memberships[i].userId, 'newChatMessage', packedMessageForTo);
+ //this.pushNotificationService.pushNotification(memberships[i].userId, 'newChatMessage', packedMessageForTo);
+ }
+ }, 3000);
+
+ return packedMessage;
+ }
+
+ @bindThis
+ public async readUserChatMessage(
+ readerId: MiUser['id'],
+ senderId: MiUser['id'],
+ ): Promise<void> {
+ const redisPipeline = this.redisClient.pipeline();
+ redisPipeline.del(`newUserChatMessageExists:${readerId}:${senderId}`);
+ redisPipeline.srem(`newChatMessagesExists:${readerId}`, `user:${senderId}`);
+ await redisPipeline.exec();
+ }
+
+ @bindThis
+ public async readRoomChatMessage(
+ readerId: MiUser['id'],
+ roomId: MiChatRoom['id'],
+ ): Promise<void> {
+ const redisPipeline = this.redisClient.pipeline();
+ redisPipeline.del(`newRoomChatMessageExists:${readerId}:${roomId}`);
+ redisPipeline.srem(`newChatMessagesExists:${readerId}`, `room:${roomId}`);
+ await redisPipeline.exec();
+ }
+
+ @bindThis
+ public findMessageById(messageId: MiChatMessage['id']) {
+ return this.chatMessagesRepository.findOneBy({ id: messageId });
+ }
+
+ @bindThis
+ public findMyMessageById(userId: MiUser['id'], messageId: MiChatMessage['id']) {
+ return this.chatMessagesRepository.findOneBy({ id: messageId, fromUserId: userId });
+ }
+
+ @bindThis
+ public async deleteMessage(message: MiChatMessage) {
+ await this.chatMessagesRepository.delete(message.id);
+
+ if (message.toUserId) {
+ const [fromUser, toUser] = await Promise.all([
+ this.usersRepository.findOneByOrFail({ id: message.fromUserId }),
+ this.usersRepository.findOneByOrFail({ id: message.toUserId }),
+ ]);
+
+ if (this.userEntityService.isLocalUser(fromUser)) this.globalEventService.publishChatUserStream(message.fromUserId, message.toUserId, 'deleted', message.id);
+ if (this.userEntityService.isLocalUser(toUser)) this.globalEventService.publishChatUserStream(message.toUserId, message.fromUserId, 'deleted', message.id);
+
+ if (this.userEntityService.isLocalUser(fromUser) && this.userEntityService.isRemoteUser(toUser)) {
+ //const activity = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${message.id}`), fromUser));
+ //this.queueService.deliver(fromUser, activity, toUser.inbox);
+ }
+ } else if (message.toRoomId) {
+ this.globalEventService.publishChatRoomStream(message.toRoomId, 'deleted', message.id);
+ }
+ }
+
+ @bindThis
+ public async userTimeline(meId: MiUser['id'], otherId: MiUser['id'], limit: number, sinceId?: MiChatMessage['id'] | null, untilId?: MiChatMessage['id'] | null) {
+ const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), sinceId, untilId)
+ .andWhere(new Brackets(qb => {
+ qb
+ .where(new Brackets(qb => {
+ qb
+ .where('message.fromUserId = :meId')
+ .andWhere('message.toUserId = :otherId');
+ }))
+ .orWhere(new Brackets(qb => {
+ qb
+ .where('message.fromUserId = :otherId')
+ .andWhere('message.toUserId = :meId');
+ }));
+ }))
+ .setParameter('meId', meId)
+ .setParameter('otherId', otherId);
+
+ const messages = await query.take(limit).getMany();
+
+ return messages;
+ }
+
+ @bindThis
+ public async roomTimeline(roomId: MiChatRoom['id'], limit: number, sinceId?: MiChatMessage['id'] | null, untilId?: MiChatMessage['id'] | null) {
+ const query = this.queryService.makePaginationQuery(this.chatMessagesRepository.createQueryBuilder('message'), sinceId, untilId)
+ .where('message.toRoomId = :roomId', { roomId })
+ .leftJoinAndSelect('message.file', 'file')
+ .leftJoinAndSelect('message.fromUser', 'fromUser');
+
+ const messages = await query.take(limit).getMany();
+
+ return messages;
+ }
+
+ @bindThis
+ public async userHistory(meId: MiUser['id'], limit: number): Promise<MiChatMessage[]> {
+ const history: MiChatMessage[] = [];
+
+ const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
+ .select('muting.muteeId')
+ .where('muting.muterId = :muterId', { muterId: meId });
+
+ for (let i = 0; i < limit; i++) {
+ const found = history.map(m => (m.fromUserId === meId) ? m.toUserId! : m.fromUserId!);
+
+ const query = this.chatMessagesRepository.createQueryBuilder('message')
+ .orderBy('message.id', 'DESC')
+ .where(new Brackets(qb => {
+ qb
+ .where('message.fromUserId = :meId', { meId: meId })
+ .orWhere('message.toUserId = :meId', { meId: meId });
+ }))
+ .andWhere('message.toRoomId IS NULL')
+ .andWhere(`message.fromUserId NOT IN (${ mutingQuery.getQuery() })`)
+ .andWhere(`message.toUserId NOT IN (${ mutingQuery.getQuery() })`);
+
+ if (found.length > 0) {
+ query.andWhere('message.fromUserId NOT IN (:...found)', { found: found });
+ query.andWhere('message.toUserId NOT IN (:...found)', { found: found });
+ }
+
+ query.setParameters(mutingQuery.getParameters());
+
+ const message = await query.getOne();
+
+ if (message) {
+ history.push(message);
+ } else {
+ break;
+ }
+ }
+
+ return history;
+ }
+
+ @bindThis
+ public async roomHistory(meId: MiUser['id'], limit: number): Promise<MiChatMessage[]> {
+ // TODO: 一回のクエリにまとめられるかも
+ const [memberRoomIds, ownedRoomIds] = await Promise.all([
+ this.chatRoomMembershipsRepository.findBy({
+ userId: meId,
+ }).then(xs => xs.map(x => x.roomId)),
+ this.chatRoomsRepository.findBy({
+ ownerId: meId,
+ }).then(xs => xs.map(x => x.id)),
+ ]);
+
+ const roomIds = memberRoomIds.concat(ownedRoomIds);
+
+ if (memberRoomIds.length === 0 && ownedRoomIds.length === 0) {
+ return [];
+ }
+
+ const history: MiChatMessage[] = [];
+
+ for (let i = 0; i < limit; i++) {
+ const found = history.map(m => m.toRoomId!);
+
+ const query = this.chatMessagesRepository.createQueryBuilder('message')
+ .orderBy('message.id', 'DESC')
+ .where('message.toRoomId IN (:...roomIds)', { roomIds });
+
+ if (found.length > 0) {
+ query.andWhere('message.toRoomId NOT IN (:...found)', { found: found });
+ }
+
+ const message = await query.getOne();
+
+ if (message) {
+ history.push(message);
+ } else {
+ break;
+ }
+ }
+
+ return history;
+ }
+
+ @bindThis
+ public async getUserReadStateMap(userId: MiUser['id'], otherIds: MiUser['id'][]) {
+ const readStateMap: Record<MiUser['id'], boolean> = {};
+
+ const redisPipeline = this.redisClient.pipeline();
+
+ for (const otherId of otherIds) {
+ redisPipeline.get(`newUserChatMessageExists:${userId}:${otherId}`);
+ }
+
+ const markers = await redisPipeline.exec();
+ if (markers == null) throw new Error('redis error');
+
+ for (let i = 0; i < otherIds.length; i++) {
+ const marker = markers[i][1];
+ readStateMap[otherIds[i]] = marker == null;
+ }
+
+ return readStateMap;
+ }
+
+ @bindThis
+ public async getRoomReadStateMap(userId: MiUser['id'], roomIds: MiChatRoom['id'][]) {
+ const readStateMap: Record<MiChatRoom['id'], boolean> = {};
+
+ const redisPipeline = this.redisClient.pipeline();
+
+ for (const roomId of roomIds) {
+ redisPipeline.get(`newRoomChatMessageExists:${userId}:${roomId}`);
+ }
+
+ const markers = await redisPipeline.exec();
+ if (markers == null) throw new Error('redis error');
+
+ for (let i = 0; i < roomIds.length; i++) {
+ const marker = markers[i][1];
+ readStateMap[roomIds[i]] = marker == null;
+ }
+
+ return readStateMap;
+ }
+
+ @bindThis
+ public async hasUnreadMessages(userId: MiUser['id']) {
+ const card = await this.redisClient.scard(`newChatMessagesExists:${userId}`);
+ return card > 0;
+ }
+
+ @bindThis
+ public async createRoom(owner: MiUser, params: Partial<{
+ name: string;
+ description: string;
+ }>) {
+ const room = {
+ id: this.idService.gen(),
+ name: params.name,
+ description: params.description,
+ ownerId: owner.id,
+ } satisfies Partial<MiChatRoom>;
+
+ const created = await this.chatRoomsRepository.insertOne(room);
+
+ return created;
+ }
+
+ @bindThis
+ public async deleteRoom(room: MiChatRoom) {
+ await this.chatRoomsRepository.delete(room.id);
+ }
+
+ @bindThis
+ public async findMyRoomById(ownerId: MiUser['id'], roomId: MiChatRoom['id']) {
+ return this.chatRoomsRepository.findOneBy({ id: roomId, ownerId: ownerId });
+ }
+
+ @bindThis
+ public async findRoomById(roomId: MiChatRoom['id']) {
+ return this.chatRoomsRepository.findOne({ where: { id: roomId }, relations: ['owner'] });
+ }
+
+ @bindThis
+ public async isRoomMember(room: MiChatRoom, userId: MiUser['id']) {
+ if (room.ownerId === userId) return true;
+ const membership = await this.chatRoomMembershipsRepository.findOneBy({ roomId: room.id, userId });
+ return membership != null;
+ }
+
+ @bindThis
+ public async createRoomInvitation(inviterId: MiUser['id'], roomId: MiChatRoom['id'], inviteeId: MiUser['id']) {
+ if (inviterId === inviteeId) {
+ throw new Error('yourself');
+ }
+
+ const room = await this.chatRoomsRepository.findOneByOrFail({ id: roomId, ownerId: inviterId });
+
+ if (await this.isRoomMember(room, inviteeId)) {
+ throw new Error('already member');
+ }
+
+ const existingInvitation = await this.chatRoomInvitationsRepository.findOneBy({ roomId, userId: inviteeId });
+ if (existingInvitation) {
+ throw new Error('already invited');
+ }
+
+ const membershipsCount = await this.chatRoomMembershipsRepository.countBy({ roomId });
+ if (membershipsCount >= MAX_ROOM_MEMBERS) {
+ throw new Error('room is full');
+ }
+
+ // TODO: cehck block
+
+ const invitation = {
+ id: this.idService.gen(),
+ roomId: room.id,
+ userId: inviteeId,
+ } satisfies Partial<MiChatRoomInvitation>;
+
+ const created = await this.chatRoomInvitationsRepository.insertOne(invitation);
+
+ return created;
+ }
+
+ @bindThis
+ public async getOwnedRoomsWithPagination(ownerId: MiUser['id'], limit: number, sinceId?: MiChatRoom['id'] | null, untilId?: MiChatRoom['id'] | null) {
+ const query = this.queryService.makePaginationQuery(this.chatRoomsRepository.createQueryBuilder('room'), sinceId, untilId)
+ .where('room.ownerId = :ownerId', { ownerId });
+
+ const rooms = await query.take(limit).getMany();
+
+ return rooms;
+ }
+
+ @bindThis
+ public async getReceivedRoomInvitationsWithPagination(userId: MiUser['id'], limit: number, sinceId?: MiChatRoomInvitation['id'] | null, untilId?: MiChatRoomInvitation['id'] | null) {
+ const query = this.queryService.makePaginationQuery(this.chatRoomInvitationsRepository.createQueryBuilder('invitation'), sinceId, untilId)
+ .where('invitation.userId = :userId', { userId })
+ .andWhere('invitation.ignored = FALSE');
+
+ const invitations = await query.take(limit).getMany();
+
+ return invitations;
+ }
+
+ @bindThis
+ public async joinToRoom(userId: MiUser['id'], roomId: MiChatRoom['id']) {
+ const invitation = await this.chatRoomInvitationsRepository.findOneByOrFail({ roomId, userId });
+
+ const membershipsCount = await this.chatRoomMembershipsRepository.countBy({ roomId });
+ if (membershipsCount >= MAX_ROOM_MEMBERS) {
+ throw new Error('room is full');
+ }
+
+ const membership = {
+ id: this.idService.gen(),
+ roomId: roomId,
+ userId: userId,
+ } satisfies Partial<MiChatRoomMembership>;
+
+ // TODO: transaction
+ await this.chatRoomMembershipsRepository.insertOne(membership);
+ await this.chatRoomInvitationsRepository.delete(invitation.id);
+ }
+
+ @bindThis
+ public async ignoreRoomInvitation(userId: MiUser['id'], roomId: MiChatRoom['id']) {
+ const invitation = await this.chatRoomInvitationsRepository.findOneByOrFail({ roomId, userId });
+ await this.chatRoomInvitationsRepository.update(invitation.id, { ignored: true });
+ }
+
+ @bindThis
+ public async leaveRoom(userId: MiUser['id'], roomId: MiChatRoom['id']) {
+ const membership = await this.chatRoomMembershipsRepository.findOneByOrFail({ roomId, userId });
+ await this.chatRoomMembershipsRepository.delete(membership.id);
+ }
+
+ @bindThis
+ public async muteRoom(userId: MiUser['id'], roomId: MiChatRoom['id'], mute: boolean) {
+ const membership = await this.chatRoomMembershipsRepository.findOneByOrFail({ roomId, userId });
+ await this.chatRoomMembershipsRepository.update(membership.id, { isMuted: mute });
+ }
+
+ @bindThis
+ public async updateRoom(room: MiChatRoom, params: {
+ name?: string;
+ description?: string;
+ }): Promise<MiChatRoom> {
+ return this.chatRoomsRepository.createQueryBuilder().update()
+ .set(params)
+ .where('id = :id', { id: room.id })
+ .returning('*')
+ .execute()
+ .then((response) => {
+ return response.raw[0];
+ });
+ }
+
+ @bindThis
+ public async getRoomMembershipsWithPagination(roomId: MiChatRoom['id'], limit: number, sinceId?: MiChatRoomMembership['id'] | null, untilId?: MiChatRoomMembership['id'] | null) {
+ const query = this.queryService.makePaginationQuery(this.chatRoomMembershipsRepository.createQueryBuilder('membership'), sinceId, untilId)
+ .where('membership.roomId = :roomId', { roomId });
+
+ const memberships = await query.take(limit).getMany();
+
+ return memberships;
+ }
+
+ @bindThis
+ public async searchMessages(meId: MiUser['id'], query: string, limit: number, params: {
+ userId?: MiUser['id'] | null;
+ roomId?: MiChatRoom['id'] | null;
+ }) {
+ const q = this.chatMessagesRepository.createQueryBuilder('message');
+
+ if (params.userId) {
+ q.andWhere(new Brackets(qb => {
+ qb
+ .where(new Brackets(qb => {
+ qb
+ .where('message.fromUserId = :meId')
+ .andWhere('message.toUserId = :otherId');
+ }))
+ .orWhere(new Brackets(qb => {
+ qb
+ .where('message.fromUserId = :otherId')
+ .andWhere('message.toUserId = :meId');
+ }));
+ }))
+ .setParameter('meId', meId)
+ .setParameter('otherId', params.userId);
+ } else if (params.roomId) {
+ q.where('message.toRoomId = :roomId', { roomId: params.roomId });
+ } else {
+ const membershipsQuery = this.chatRoomMembershipsRepository.createQueryBuilder('membership')
+ .select('membership.roomId')
+ .where('membership.userId = :meId', { meId: meId });
+
+ const ownedRoomsQuery = this.chatRoomsRepository.createQueryBuilder('room')
+ .select('room.id')
+ .where('room.ownerId = :meId', { meId });
+
+ q.andWhere(new Brackets(qb => {
+ qb
+ .where('message.fromUserId = :meId')
+ .orWhere('message.toUserId = :meId')
+ .orWhere(`message.toRoomId IN (${membershipsQuery.getQuery()})`)
+ .orWhere(`message.toRoomId IN (${ownedRoomsQuery.getQuery()})`);
+ }));
+
+ q.setParameters(membershipsQuery.getParameters());
+ q.setParameters(ownedRoomsQuery.getParameters());
+ }
+
+ q.andWhere('LOWER(message.text) LIKE :q', { q: `%${ sqlLikeEscape(query.toLowerCase()) }%` });
+
+ q.leftJoinAndSelect('message.file', 'file');
+ q.leftJoinAndSelect('message.fromUser', 'fromUser');
+ q.leftJoinAndSelect('message.toUser', 'toUser');
+ q.leftJoinAndSelect('message.toRoom', 'toRoom');
+ q.leftJoinAndSelect('toRoom.owner', 'toRoomOwner');
+
+ const messages = await q.orderBy('message.id', 'DESC').take(limit).getMany();
+
+ return messages;
+ }
+
+ @bindThis
+ public async react(messageId: MiChatMessage['id'], userId: MiUser['id'], reaction_: string) {
+ let reaction;
+
+ // TODO: ReactionServiceのやつと共通化
+ function normalize(x: string) {
+ const match = emojiRegex.exec(x);
+ if (match) {
+ // 合字を含む1つの絵文字
+ const unicode = match[0];
+
+ // 異体字セレクタ除去
+ return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
+ } else {
+ throw new Error('invalid emoji');
+ }
+ }
+
+ const custom = reaction_.match(isCustomEmojiRegexp);
+
+ if (custom == null) {
+ reaction = normalize(reaction_);
+ } else {
+ const name = custom[1];
+ const emoji = (await this.customEmojiService.localEmojisCache.fetch()).get(name);
+
+ if (emoji == null) {
+ throw new Error('no such emoji');
+ } else {
+ reaction = `:${name}:`;
+ }
+ }
+
+ const message = await this.chatMessagesRepository.findOneByOrFail({ id: messageId });
+
+ if (message.fromUserId === userId) {
+ throw new Error('cannot react to own message');
+ }
+
+ if (message.toRoomId === null && message.toUserId !== userId) {
+ throw new Error('cannot react to others message');
+ }
+
+ if (message.reactions.length >= MAX_REACTIONS_PER_MESSAGE) {
+ throw new Error('too many reactions');
+ }
+
+ const room = message.toRoomId ? await this.chatRoomsRepository.findOneByOrFail({ id: message.toRoomId }) : null;
+
+ if (room) {
+ if (!await this.isRoomMember(room, userId)) {
+ throw new Error('cannot react to others message');
+ }
+ }
+
+ await this.chatMessagesRepository.createQueryBuilder().update()
+ .set({
+ reactions: () => `array_append("reactions", '${userId}/${reaction}')`,
+ })
+ .where('id = :id', { id: message.id })
+ .execute();
+
+ if (room) {
+ this.globalEventService.publishChatRoomStream(room.id, 'react', {
+ messageId: message.id,
+ user: await this.userEntityService.pack(userId),
+ reaction,
+ });
+ } else {
+ this.globalEventService.publishChatUserStream(message.fromUserId, message.toUserId!, 'react', {
+ messageId: message.id,
+ reaction,
+ });
+ this.globalEventService.publishChatUserStream(message.toUserId!, message.fromUserId, 'react', {
+ messageId: message.id,
+ reaction,
+ });
+ }
+ }
+
+ @bindThis
+ public async getMyMemberships(userId: MiUser['id'], limit: number, sinceId?: MiChatRoomMembership['id'] | null, untilId?: MiChatRoomMembership['id'] | null) {
+ const query = this.queryService.makePaginationQuery(this.chatRoomMembershipsRepository.createQueryBuilder('membership'), sinceId, untilId)
+ .where('membership.userId = :userId', { userId });
+
+ const memberships = await query.take(limit).getMany();
+
+ return memberships;
+ }
+}
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 997d81facc..fcae7d70dc 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -28,7 +28,6 @@ import { AppLockService } from './AppLockService.js';
import { AchievementService } from './AchievementService.js';
import { AvatarDecorationService } from './AvatarDecorationService.js';
import { CaptchaService } from './CaptchaService.js';
-import { CreateSystemUserService } from './CreateSystemUserService.js';
import { CustomEmojiService } from './CustomEmojiService.js';
import { DeleteAccountService } from './DeleteAccountService.js';
import { DownloadService } from './DownloadService.js';
@@ -41,7 +40,7 @@ import { HashtagService } from './HashtagService.js';
import { HttpRequestService } from './HttpRequestService.js';
import { IdService } from './IdService.js';
import { ImageProcessingService } from './ImageProcessingService.js';
-import { InstanceActorService } from './InstanceActorService.js';
+import { SystemAccountService } from './SystemAccountService.js';
import { InternalStorageService } from './InternalStorageService.js';
import { MetaService } from './MetaService.js';
import { MfmService } from './MfmService.js';
@@ -51,7 +50,6 @@ import { NoteEditService } from './NoteEditService.js';
import { NoteDeleteService } from './NoteDeleteService.js';
import { LatestNoteService } from './LatestNoteService.js';
import { NotePiningService } from './NotePiningService.js';
-import { NoteReadService } from './NoteReadService.js';
import { NotificationService } from './NotificationService.js';
import { PollService } from './PollService.js';
import { PushNotificationService } from './PushNotificationService.js';
@@ -75,7 +73,6 @@ import { UserSuspendService } from './UserSuspendService.js';
import { UserAuthService } from './UserAuthService.js';
import { VideoProcessingService } from './VideoProcessingService.js';
import { UserWebhookService } from './UserWebhookService.js';
-import { ProxyAccountService } from './ProxyAccountService.js';
import { UtilityService } from './UtilityService.js';
import { FileInfoService } from './FileInfoService.js';
import { SearchService } from './SearchService.js';
@@ -83,6 +80,7 @@ import { ClipService } from './ClipService.js';
import { FeaturedService } from './FeaturedService.js';
import { FanoutTimelineService } from './FanoutTimelineService.js';
import { ChannelFollowingService } from './ChannelFollowingService.js';
+import { ChatService } from './ChatService.js';
import { RegistryApiService } from './RegistryApiService.js';
import { ReversiService } from './ReversiService.js';
@@ -108,6 +106,7 @@ import { AppEntityService } from './entities/AppEntityService.js';
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
import { BlockingEntityService } from './entities/BlockingEntityService.js';
import { ChannelEntityService } from './entities/ChannelEntityService.js';
+import { ChatEntityService } from './entities/ChatEntityService.js';
import { ClipEntityService } from './entities/ClipEntityService.js';
import { DriveFileEntityService } from './entities/DriveFileEntityService.js';
import { DriveFolderEntityService } from './entities/DriveFolderEntityService.js';
@@ -174,7 +173,6 @@ const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppL
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
-const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
const $DeleteAccountService: Provider = { provide: 'DeleteAccountService', useExisting: DeleteAccountService };
const $DownloadService: Provider = { provide: 'DownloadService', useExisting: DownloadService };
@@ -187,7 +185,6 @@ const $HashtagService: Provider = { provide: 'HashtagService', useExisting: Hash
const $HttpRequestService: Provider = { provide: 'HttpRequestService', useExisting: HttpRequestService };
const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
-const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService };
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
@@ -197,10 +194,9 @@ const $NoteEditService: Provider = { provide: 'NoteEditService', useExisting: No
const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService };
const $LatestNoteService: Provider = { provide: 'LatestNoteService', useExisting: LatestNoteService };
const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService };
-const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
const $PollService: Provider = { provide: 'PollService', useExisting: PollService };
-const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExisting: ProxyAccountService };
+const $SystemAccountService: Provider = { provide: 'SystemAccountService', useExisting: SystemAccountService };
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
@@ -235,6 +231,7 @@ const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: Fe
const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', useExisting: FanoutTimelineService };
const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
+const $ChatService: Provider = { provide: 'ChatService', useExisting: ChatService };
const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
const $TimeService: Provider = { provide: 'TimeService', useExisting: TimeService };
@@ -263,6 +260,7 @@ const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting:
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
const $BlockingEntityService: Provider = { provide: 'BlockingEntityService', useExisting: BlockingEntityService };
const $ChannelEntityService: Provider = { provide: 'ChannelEntityService', useExisting: ChannelEntityService };
+const $ChatEntityService: Provider = { provide: 'ChatEntityService', useExisting: ChatEntityService };
const $ClipEntityService: Provider = { provide: 'ClipEntityService', useExisting: ClipEntityService };
const $DriveFileEntityService: Provider = { provide: 'DriveFileEntityService', useExisting: DriveFileEntityService };
const $DriveFolderEntityService: Provider = { provide: 'DriveFolderEntityService', useExisting: DriveFolderEntityService };
@@ -333,7 +331,6 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
AchievementService,
AvatarDecorationService,
CaptchaService,
- CreateSystemUserService,
CustomEmojiService,
DeleteAccountService,
DownloadService,
@@ -346,7 +343,6 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
HttpRequestService,
IdService,
ImageProcessingService,
- InstanceActorService,
InternalStorageService,
MetaService,
MfmService,
@@ -356,10 +352,9 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
NoteDeleteService,
LatestNoteService,
NotePiningService,
- NoteReadService,
NotificationService,
PollService,
- ProxyAccountService,
+ SystemAccountService,
PushNotificationService,
QueryService,
ReactionService,
@@ -394,6 +389,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
FanoutTimelineService,
FanoutTimelineEndpointService,
ChannelFollowingService,
+ ChatService,
RegistryApiService,
ReversiService,
TimeService,
@@ -422,6 +418,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
AuthSessionEntityService,
BlockingEntityService,
ChannelEntityService,
+ ChatEntityService,
ClipEntityService,
DriveFileEntityService,
DriveFolderEntityService,
@@ -488,7 +485,6 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$AchievementService,
$AvatarDecorationService,
$CaptchaService,
- $CreateSystemUserService,
$CustomEmojiService,
$DeleteAccountService,
$DownloadService,
@@ -501,7 +497,6 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$HttpRequestService,
$IdService,
$ImageProcessingService,
- $InstanceActorService,
$InternalStorageService,
$MetaService,
$MfmService,
@@ -511,10 +506,9 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$NoteDeleteService,
$LatestNoteService,
$NotePiningService,
- $NoteReadService,
$NotificationService,
$PollService,
- $ProxyAccountService,
+ $SystemAccountService,
$PushNotificationService,
$QueryService,
$ReactionService,
@@ -549,6 +543,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$FanoutTimelineService,
$FanoutTimelineEndpointService,
$ChannelFollowingService,
+ $ChatService,
$RegistryApiService,
$ReversiService,
$TimeService,
@@ -577,6 +572,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$AuthSessionEntityService,
$BlockingEntityService,
$ChannelEntityService,
+ $ChatEntityService,
$ClipEntityService,
$DriveFileEntityService,
$DriveFolderEntityService,
@@ -644,7 +640,6 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
AchievementService,
AvatarDecorationService,
CaptchaService,
- CreateSystemUserService,
CustomEmojiService,
DeleteAccountService,
DownloadService,
@@ -657,7 +652,6 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
HttpRequestService,
IdService,
ImageProcessingService,
- InstanceActorService,
InternalStorageService,
MetaService,
MfmService,
@@ -667,10 +661,9 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
NoteDeleteService,
LatestNoteService,
NotePiningService,
- NoteReadService,
NotificationService,
PollService,
- ProxyAccountService,
+ SystemAccountService,
PushNotificationService,
QueryService,
ReactionService,
@@ -705,6 +698,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
FanoutTimelineService,
FanoutTimelineEndpointService,
ChannelFollowingService,
+ ChatService,
RegistryApiService,
ReversiService,
TimeService,
@@ -732,6 +726,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
AuthSessionEntityService,
BlockingEntityService,
ChannelEntityService,
+ ChatEntityService,
ClipEntityService,
DriveFileEntityService,
DriveFolderEntityService,
@@ -798,7 +793,6 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$AchievementService,
$AvatarDecorationService,
$CaptchaService,
- $CreateSystemUserService,
$CustomEmojiService,
$DeleteAccountService,
$DownloadService,
@@ -811,7 +805,6 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$HttpRequestService,
$IdService,
$ImageProcessingService,
- $InstanceActorService,
$InternalStorageService,
$MetaService,
$MfmService,
@@ -821,10 +814,9 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$NoteDeleteService,
$LatestNoteService,
$NotePiningService,
- $NoteReadService,
$NotificationService,
$PollService,
- $ProxyAccountService,
+ $SystemAccountService,
$PushNotificationService,
$QueryService,
$ReactionService,
@@ -858,6 +850,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$FanoutTimelineService,
$FanoutTimelineEndpointService,
$ChannelFollowingService,
+ $ChatService,
$RegistryApiService,
$ReversiService,
$TimeService,
@@ -885,6 +878,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp
$AuthSessionEntityService,
$BlockingEntityService,
$ChannelEntityService,
+ $ChatEntityService,
$ClipEntityService,
$DriveFileEntityService,
$DriveFolderEntityService,
diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts
deleted file mode 100644
index a0aa6bad06..0000000000
--- a/packages/backend/src/core/CreateSystemUserService.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { randomUUID } from 'node:crypto';
-import { Inject, Injectable } from '@nestjs/common';
-import * as argon2 from 'argon2';
-//import bcrypt from 'bcryptjs';
-import { IsNull, DataSource } from 'typeorm';
-import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
-import { MiUser } from '@/models/User.js';
-import { MiUserProfile } from '@/models/UserProfile.js';
-import { IdService } from '@/core/IdService.js';
-import { MiUserKeypair } from '@/models/UserKeypair.js';
-import { MiUsedUsername } from '@/models/UsedUsername.js';
-import { DI } from '@/di-symbols.js';
-import generateNativeUserToken from '@/misc/generate-native-user-token.js';
-import { bindThis } from '@/decorators.js';
-
-@Injectable()
-export class CreateSystemUserService {
- constructor(
- @Inject(DI.db)
- private db: DataSource,
-
- private idService: IdService,
- ) {
- }
-
- @bindThis
- public async createSystemUser(username: string): Promise<MiUser> {
- const password = randomUUID();
-
- // Generate hash of password
- //const salt = await bcrypt.genSalt(8);
- const hash = await argon2.hash(password);
-
- // Generate secret
- const secret = generateNativeUserToken();
-
- const keyPair = await genRsaKeyPair();
-
- let account!: MiUser;
-
- // Start transaction
- await this.db.transaction(async transactionalEntityManager => {
- const exist = await transactionalEntityManager.findOneBy(MiUser, {
- usernameLower: username.toLowerCase(),
- host: IsNull(),
- });
-
- if (exist) throw new Error('the user is already exists');
-
- account = await transactionalEntityManager.insert(MiUser, {
- id: this.idService.gen(),
- username: username,
- usernameLower: username.toLowerCase(),
- host: null,
- token: secret,
- isRoot: false,
- isLocked: true,
- isExplorable: false,
- approved: true,
- isBot: true,
- /* we always allow requests about our instance actor, because when
- a remote instance needs to check our signature on a request we
- sent, it will need to fetch information about the user that
- signed it (which is our instance actor), and if we try to check
- their signature on *that* request, we'll fetch *their* instance
- actor... leading to an infinite recursion */
- allowUnsignedFetch: 'always',
- }).then(x => transactionalEntityManager.findOneByOrFail(MiUser, x.identifiers[0]));
-
- await transactionalEntityManager.insert(MiUserKeypair, {
- publicKey: keyPair.publicKey,
- privateKey: keyPair.privateKey,
- userId: account.id,
- });
-
- await transactionalEntityManager.insert(MiUserProfile, {
- userId: account.id,
- autoAcceptFollowed: false,
- password: hash,
- });
-
- await transactionalEntityManager.insert(MiUsedUsername, {
- createdAt: new Date(),
- username: username.toLowerCase(),
- });
- });
-
- return account;
- }
-}
diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts
index 8408e95863..48f27d558e 100644
--- a/packages/backend/src/core/DeleteAccountService.ts
+++ b/packages/backend/src/core/DeleteAccountService.ts
@@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Not, IsNull } from 'typeorm';
-import type { FollowingsRepository, MiUser, UsersRepository } from '@/models/_.js';
+import type { FollowingsRepository, MiMeta, MiUser, UsersRepository } from '@/models/_.js';
import { QueueService } from '@/core/QueueService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -13,11 +13,15 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
import { isSystemAccount } from '@/misc/is-system-account.js';
@Injectable()
export class DeleteAccountService {
constructor(
+ @Inject(DI.meta)
+ private meta: MiMeta,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -29,6 +33,7 @@ export class DeleteAccountService {
private queueService: QueueService,
private globalEventService: GlobalEventService,
private moderationLogService: ModerationLogService,
+ private systemAccountService: SystemAccountService,
) {
}
@@ -37,9 +42,13 @@ export class DeleteAccountService {
id: string;
host: string | null;
}, moderator?: MiUser): Promise<void> {
+ if (this.meta.rootUserId === user.id) throw new Error('cannot delete a root account');
+
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
- if (_user.isRoot) throw new Error('cannot delete a root account');
- if (isSystemAccount(_user)) throw new Error('cannot delete a system account');
+
+ if (isSystemAccount(_user)) {
+ throw new Error('cannot delete a system account');
+ }
if (moderator != null) {
this.moderationLogService.log(moderator, 'deleteAccount', {
diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts
index 05b9e64a37..a1a6914130 100644
--- a/packages/backend/src/core/DownloadService.ts
+++ b/packages/backend/src/core/DownloadService.ts
@@ -60,8 +60,8 @@ export class DownloadService {
request: operationTimeout, // whole operation timeout
},
agent: {
- http: this.httpRequestService.httpAgent,
- https: this.httpRequestService.httpsAgent,
+ http: this.httpRequestService.getAgentForHttp(urlObj, true),
+ https: this.httpRequestService.getAgentForHttps(urlObj, true),
},
http2: false, // default
retry: {
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index a65059b417..4be1b0e41b 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -177,7 +177,8 @@ export class DriveService {
?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
// for original
- const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`;
+ const prefix = this.meta.objectStoragePrefix ? `${this.meta.objectStoragePrefix}/` : '';
+ const key = `${prefix}${randomUUID()}${ext}`;
const url = `${ baseUrl }/${ key }`;
// for alts
@@ -194,7 +195,7 @@ export class DriveService {
];
if (alts.webpublic) {
- webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
+ webpublicKey = `${prefix}webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
@@ -202,7 +203,7 @@ export class DriveService {
}
if (alts.thumbnail) {
- thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
+ thumbnailKey = `${prefix}thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts
index da198d0e42..45d7ea11e4 100644
--- a/packages/backend/src/core/EmailService.ts
+++ b/packages/backend/src/core/EmailService.ts
@@ -164,6 +164,13 @@ export class EmailService {
available: boolean;
reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
}> {
+ if (!this.utilityService.validateEmailFormat(emailAddress)) {
+ return {
+ available: false,
+ reason: 'format',
+ };
+ }
+
const exist = await this.userProfilesRepository.countBy({
emailVerified: true,
email: emailAddress,
diff --git a/packages/backend/src/core/FanoutTimelineService.ts b/packages/backend/src/core/FanoutTimelineService.ts
index f6dabfadcd..24999bf4da 100644
--- a/packages/backend/src/core/FanoutTimelineService.ts
+++ b/packages/backend/src/core/FanoutTimelineService.ts
@@ -9,7 +9,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
-export type FanoutTimelineName =
+export type FanoutTimelineName = (
// home timeline
| `homeTimeline:${string}`
| `homeTimelineWithFiles:${string}` // only notes with files are included
@@ -37,6 +37,7 @@ export type FanoutTimelineName =
// role timelines
| `roleTimeline:${string}` // any notes are included
+);
@Injectable()
export class FanoutTimelineService {
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 211c22bfaf..94d6311e0d 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -20,7 +20,7 @@ import type { MiPage } from '@/models/Page.js';
import type { MiWebhook } from '@/models/Webhook.js';
import type { MiSystemWebhook } from '@/models/SystemWebhook.js';
import type { MiMeta } from '@/models/Meta.js';
-import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
+import { MiAvatarDecoration, MiChatMessage, MiChatRoom, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@@ -72,12 +72,8 @@ export interface MainEventTypes {
readAllNotifications: undefined;
notificationFlushed: undefined;
unreadNotification: Packed<'Notification'>;
- unreadMention: MiNote['id'];
- readAllUnreadMentions: undefined;
- unreadSpecifiedNote: MiNote['id'];
- readAllUnreadSpecifiedNotes: undefined;
- readAllAntennas: undefined;
unreadAntenna: MiAntenna;
+ newChatMessage: Packed<'ChatMessage'>;
readAllAnnouncements: undefined;
myTokenRegenerated: undefined;
signin: {
@@ -168,6 +164,16 @@ export interface AdminEventTypes {
};
}
+export interface ChatEventTypes {
+ message: Packed<'ChatMessageLite'>;
+ deleted: Packed<'ChatMessageLite'>['id'];
+ react: {
+ reaction: string;
+ user?: Packed<'UserLite'>;
+ messageId: MiChatMessage['id'];
+ };
+}
+
export interface ReversiEventTypes {
matched: {
game: Packed<'ReversiGameDetailed'>;
@@ -207,7 +213,7 @@ export interface ReversiGameEventTypes {
type Events<T extends object> = { [K in keyof T]: { type: K; body: T[K]; } };
type EventUnionFromDictionary<
T extends object,
- U = Events<T>
+ U = Events<T>,
> = U[keyof U];
type SerializedAll<T> = {
@@ -216,7 +222,7 @@ type SerializedAll<T> = {
type UndefinedAsNullAll<T> = {
[K in keyof T]: T[K] extends undefined ? null : T[K];
-}
+};
export interface InternalEventTypes {
userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
@@ -300,6 +306,14 @@ export type GlobalEvents = {
name: 'notesStream';
payload: Serialized<Packed<'Note'>>;
};
+ chat: {
+ name: `chatUserStream:${MiUser['id']}-${MiUser['id']}`;
+ payload: EventTypesToEventPayload<ChatEventTypes>;
+ };
+ chatRoom: {
+ name: `chatRoomStream:${MiChatRoom['id']}`;
+ payload: EventTypesToEventPayload<ChatEventTypes>;
+ };
reversi: {
name: `reversiStream:${MiUser['id']}`;
payload: EventTypesToEventPayload<ReversiEventTypes>;
@@ -399,6 +413,16 @@ export class GlobalEventService {
}
@bindThis
+ public publishChatUserStream<K extends keyof ChatEventTypes>(fromUserId: MiUser['id'], toUserId: MiUser['id'], type: K, value?: ChatEventTypes[K]): void {
+ this.publish(`chatUserStream:${fromUserId}-${toUserId}`, type, typeof value === 'undefined' ? null : value);
+ }
+
+ @bindThis
+ public publishChatRoomStream<K extends keyof ChatEventTypes>(toRoomId: MiChatRoom['id'], type: K, value?: ChatEventTypes[K]): void {
+ this.publish(`chatRoomStream:${toRoomId}`, type, typeof value === 'undefined' ? null : value);
+ }
+
+ @bindThis
public publishReversiStream<K extends keyof ReversiEventTypes>(userId: MiUser['id'], type: K, value?: ReversiEventTypes[K]): void {
this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts
index 1aa62a9879..e8b0326e66 100644
--- a/packages/backend/src/core/HttpRequestService.ts
+++ b/packages/backend/src/core/HttpRequestService.ts
@@ -16,6 +16,7 @@ import type { Config } from '@/config.js';
import { StatusError } from '@/misc/status-error.js';
import { bindThis } from '@/decorators.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
+import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject, IObjectWithId } from '@/core/activitypub/type.js';
import { ApUtilityService } from './activitypub/ApUtilityService.js';
import type { Response } from 'node-fetch';
@@ -115,32 +116,32 @@ export class HttpRequestService {
/**
* Get http non-proxy agent (without local address filtering)
*/
- private httpNative: http.Agent;
+ private readonly httpNative: http.Agent;
/**
* Get https non-proxy agent (without local address filtering)
*/
- private httpsNative: https.Agent;
+ private readonly httpsNative: https.Agent;
/**
* Get http non-proxy agent
*/
- private http: http.Agent;
+ private readonly http: http.Agent;
/**
* Get https non-proxy agent
*/
- private https: https.Agent;
+ private readonly https: https.Agent;
/**
* Get http proxy or non-proxy agent
*/
- public httpAgent: http.Agent;
+ public readonly httpAgent: http.Agent;
/**
* Get https proxy or non-proxy agent
*/
- public httpsAgent: https.Agent;
+ public readonly httpsAgent: https.Agent;
constructor(
@Inject(DI.config)
@@ -198,7 +199,7 @@ export class HttpRequestService {
/**
* Get agent by URL
* @param url URL
- * @param bypassProxy Allways bypass proxy
+ * @param bypassProxy Always bypass proxy
* @param isLocalAddressAllowed
*/
@bindThis
@@ -216,8 +217,40 @@ export class HttpRequestService {
}
}
+ /**
+ * Get agent for http by URL
+ * @param url URL
+ * @param isLocalAddressAllowed
+ */
+ @bindThis
+ public getAgentForHttp(url: URL, isLocalAddressAllowed = false): http.Agent {
+ if ((this.config.proxyBypassHosts ?? []).includes(url.hostname)) {
+ return isLocalAddressAllowed
+ ? this.httpNative
+ : this.http;
+ } else {
+ return this.httpAgent;
+ }
+ }
+
+ /**
+ * Get agent for https by URL
+ * @param url URL
+ * @param isLocalAddressAllowed
+ */
+ @bindThis
+ public getAgentForHttps(url: URL, isLocalAddressAllowed = false): https.Agent {
+ if ((this.config.proxyBypassHosts ?? []).includes(url.hostname)) {
+ return isLocalAddressAllowed
+ ? this.httpsNative
+ : this.https;
+ } else {
+ return this.httpsAgent;
+ }
+ }
+
@bindThis
- public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise<IObjectWithId> {
+ public async getActivityJson(url: string, isLocalAddressAllowed = false, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IObjectWithId> {
const res = await this.send(url, {
method: 'GET',
headers: {
@@ -235,7 +268,7 @@ export class HttpRequestService {
// Make sure the object ID matches the final URL (which is where it actually exists).
// The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match.
- this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url);
+ this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url, allowSoftfail);
return activity as IObjectWithId;
}
diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
deleted file mode 100644
index 22c47297a3..0000000000
--- a/packages/backend/src/core/InstanceActorService.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { Inject, Injectable } from '@nestjs/common';
-import { IsNull, Not } from 'typeorm';
-import type { MiLocalUser } from '@/models/User.js';
-import type { UsersRepository } from '@/models/_.js';
-import { MemorySingleCache } from '@/misc/cache.js';
-import { DI } from '@/di-symbols.js';
-import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
-import { bindThis } from '@/decorators.js';
-
-const ACTOR_USERNAME = 'instance.actor' as const;
-
-@Injectable()
-export class InstanceActorService {
- private cache: MemorySingleCache<MiLocalUser>;
-
- constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
- private createSystemUserService: CreateSystemUserService,
- ) {
- this.cache = new MemorySingleCache<MiLocalUser>(Infinity);
- }
-
- @bindThis
- public async realLocalUsersPresent(): Promise<boolean> {
- return await this.usersRepository.existsBy({
- host: IsNull(),
- username: Not(ACTOR_USERNAME),
- });
- }
-
- @bindThis
- public async getInstanceActor(): Promise<MiLocalUser> {
- const cached = this.cache.get();
- if (cached) return cached;
-
- const user = await this.usersRepository.findOneBy({
- host: IsNull(),
- username: ACTOR_USERNAME,
- }) as MiLocalUser | undefined;
-
- if (user) {
- this.cache.set(user);
- return user;
- } else {
- const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as MiLocalUser;
- this.cache.set(created);
- return created;
- }
- }
-}
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index 3d88d0aefe..40e7439f5f 100644
--- a/packages/backend/src/core/MetaService.ts
+++ b/packages/backend/src/core/MetaService.ts
@@ -53,7 +53,7 @@ export class MetaService implements OnApplicationShutdown {
case 'metaUpdated': {
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...(body.after),
- proxyAccount: null, // joinなカラムは通常取ってこないので
+ rootUser: null, // joinなカラムは通常取ってこないので
};
break;
}
@@ -113,17 +113,20 @@ export class MetaService implements OnApplicationShutdown {
if (before) {
await transactionalEntityManager.update(MiMeta, before.id, data);
-
- const metas = await transactionalEntityManager.find(MiMeta, {
- order: {
- id: 'DESC',
- },
- });
-
- return metas[0];
} else {
- return await transactionalEntityManager.save(MiMeta, data);
+ await transactionalEntityManager.save(MiMeta, {
+ ...data,
+ id: 'x',
+ });
}
+
+ const afters = await transactionalEntityManager.find(MiMeta, {
+ order: {
+ id: 'DESC',
+ },
+ });
+
+ return afters[0];
});
if (data.hiddenTags) {
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 6c2f673217..ad646d3ff4 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -576,7 +576,8 @@ export class MfmService {
appendChildren(nodes, body);
- const serialized = new XMLSerializer().serializeToString(body);
+ // Remove the unnecessary namespace
+ const serialized = new XMLSerializer().serializeToString(body).replace(/^\s*<p xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">/, '<p>');
happyDOM.close().catch(err => {});
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index df31cb4247..b75b4ecd21 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -42,7 +42,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
-import { NoteReadService } from '@/core/NoteReadService.js';
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
@@ -203,7 +202,6 @@ export class NoteCreateService implements OnApplicationShutdown {
private globalEventService: GlobalEventService,
private queueService: QueueService,
private fanoutTimelineService: FanoutTimelineService,
- private noteReadService: NoteReadService,
private notificationService: NotificationService,
private relayService: RelayService,
private federatedInstanceService: FederatedInstanceService,
@@ -651,31 +649,6 @@ export class NoteCreateService implements OnApplicationShutdown {
if (!silent) {
if (this.userEntityService.isLocalUser(user)) this.activeUsersChart.write(user);
- // 未読通知を作成
- if (data.visibility === 'specified') {
- if (data.visibleUsers == null) throw new Error('invalid param');
-
- for (const u of data.visibleUsers) {
- // ローカルユーザーのみ
- if (!this.userEntityService.isLocalUser(u)) continue;
-
- this.noteReadService.insertNoteUnread(u.id, note, {
- isSpecified: true,
- isMentioned: false,
- });
- }
- } else {
- for (const u of mentionedUsers) {
- // ローカルユーザーのみ
- if (!this.userEntityService.isLocalUser(u)) continue;
-
- this.noteReadService.insertNoteUnread(u.id, note, {
- isSpecified: false,
- isMentioned: true,
- });
- }
- }
-
// Pack the note
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 1f94e65809..8ec05c88dc 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Brackets, In } from 'typeorm';
+import { Brackets, In, IsNull, Not } from 'typeorm';
import { Injectable, Inject } from '@nestjs/common';
import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
@@ -232,12 +232,26 @@ export class NoteDeleteService {
}
@bindThis
+ private async getRenotedOrRepliedRemoteUsers(note: MiNote) {
+ const query = this.notesRepository.createQueryBuilder('note')
+ .leftJoinAndSelect('note.user', 'user')
+ .where(new Brackets(qb => {
+ qb.orWhere('note.renoteId = :renoteId', { renoteId: note.id });
+ qb.orWhere('note.replyId = :replyId', { replyId: note.id });
+ }))
+ .andWhere({ userHost: Not(IsNull()) });
+ const notes = await query.getMany() as (MiNote & { user: MiRemoteUser })[];
+ const remoteUsers = notes.map(({ user }) => user);
+ return remoteUsers;
+ }
+
+ @bindThis
private async deliverToConcerned(user: { id: MiLocalUser['id']; host: null; }, note: MiNote, content: any) {
this.apDeliverManagerService.deliverToFollowers(user, content);
this.relayService.deliverToRelays(user, content);
- const remoteUsers = await this.getMentionedRemoteUsers(note);
- for (const remoteUser of remoteUsers) {
- this.apDeliverManagerService.deliverToUser(user, content, remoteUser);
- }
+ this.apDeliverManagerService.deliverToUsers(user, content, [
+ ...await this.getMentionedRemoteUsers(note),
+ ...await this.getRenotedOrRepliedRemoteUsers(note),
+ ]);
}
}
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
deleted file mode 100644
index 320b23cc1a..0000000000
--- a/packages/backend/src/core/NoteReadService.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { setTimeout } from 'node:timers/promises';
-import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
-import { In } from 'typeorm';
-import { DI } from '@/di-symbols.js';
-import type { MiUser } from '@/models/User.js';
-import type { Packed } from '@/misc/json-schema.js';
-import type { MiNote } from '@/models/Note.js';
-import { IdService } from '@/core/IdService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
-import { bindThis } from '@/decorators.js';
-import { trackPromise } from '@/misc/promise-tracker.js';
-
-@Injectable()
-export class NoteReadService implements OnApplicationShutdown {
- #shutdownController = new AbortController();
-
- constructor(
- @Inject(DI.noteUnreadsRepository)
- private noteUnreadsRepository: NoteUnreadsRepository,
-
- @Inject(DI.mutingsRepository)
- private mutingsRepository: MutingsRepository,
-
- @Inject(DI.noteThreadMutingsRepository)
- private noteThreadMutingsRepository: NoteThreadMutingsRepository,
-
- private idService: IdService,
- private globalEventService: GlobalEventService,
- ) {
- }
-
- @bindThis
- public async insertNoteUnread(userId: MiUser['id'], note: MiNote, params: {
- // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse
- isSpecified: boolean;
- isMentioned: boolean;
- }): Promise<void> {
- //#region ミュートしているなら無視
- const mute = await this.mutingsRepository.findBy({
- muterId: userId,
- });
- if (mute.map(m => m.muteeId).includes(note.userId)) return;
- //#endregion
-
- // スレッドミュート
- const isThreadMuted = await this.noteThreadMutingsRepository.exists({
- where: {
- userId: userId,
- threadId: note.threadId ?? note.id,
- },
- });
- if (isThreadMuted) return;
-
- const unread = {
- id: this.idService.gen(),
- noteId: note.id,
- userId: userId,
- isSpecified: params.isSpecified,
- isMentioned: params.isMentioned,
- noteUserId: note.userId,
- };
-
- /* we may be called from NoteEditService, for a note that's
- already present in the `note_unread` table: `upsert` makes sure
- we don't throw a "duplicate key" error, while still updating
- the other columns if they've changed */
- await this.noteUnreadsRepository.upsert(unread, ['userId', 'noteId']);
-
- // 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
- setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
- const exist = await this.noteUnreadsRepository.exists({ where: { id: unread.id } });
-
- if (!exist) return;
-
- if (params.isMentioned) {
- this.globalEventService.publishMainStream(userId, 'unreadMention', note.id);
- }
- if (params.isSpecified) {
- this.globalEventService.publishMainStream(userId, 'unreadSpecifiedNote', note.id);
- }
- }, () => { /* aborted, ignore it */ });
- }
-
- @bindThis
- public async read(
- userId: MiUser['id'],
- notes: (MiNote | Packed<'Note'>)[],
- ): Promise<void> {
- if (notes.length === 0) return;
-
- const noteIds = new Set<MiNote['id']>();
-
- for (const note of notes) {
- if (note.mentions && note.mentions.includes(userId)) {
- noteIds.add(note.id);
- } else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
- noteIds.add(note.id);
- }
- }
-
- if (noteIds.size === 0) return;
-
- // Remove the record
- await this.noteUnreadsRepository.delete({
- userId: userId,
- noteId: In(Array.from(noteIds)),
- });
-
- // TODO: ↓まとめてクエリしたい
-
- trackPromise(this.noteUnreadsRepository.countBy({
- userId: userId,
- isMentioned: true,
- }).then(mentionsCount => {
- if (mentionsCount === 0) {
- // 全て既読になったイベントを発行
- this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
- }
- }));
-
- trackPromise(this.noteUnreadsRepository.countBy({
- userId: userId,
- isSpecified: true,
- }).then(specifiedCount => {
- if (specifiedCount === 0) {
- // 全て既読になったイベントを発行
- this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
- }
- }));
- }
-
- @bindThis
- public dispose(): void {
- this.#shutdownController.abort();
- }
-
- @bindThis
- public onApplicationShutdown(signal?: string | undefined): void {
- this.dispose();
- }
-}
diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts
deleted file mode 100644
index c3ff2a68d3..0000000000
--- a/packages/backend/src/core/ProxyAccountService.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { Inject, Injectable } from '@nestjs/common';
-import type { MiMeta, UsersRepository } from '@/models/_.js';
-import type { MiLocalUser } from '@/models/User.js';
-import { DI } from '@/di-symbols.js';
-import { bindThis } from '@/decorators.js';
-
-@Injectable()
-export class ProxyAccountService {
- constructor(
- @Inject(DI.meta)
- private meta: MiMeta,
-
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
- ) {
- }
-
- @bindThis
- public async fetch(): Promise<MiLocalUser | null> {
- if (this.meta.proxyAccountId == null) return null;
- return await this.usersRepository.findOneByOrFail({ id: this.meta.proxyAccountId }) as MiLocalUser;
- }
-}
diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts
index db32114346..9120de1f9f 100644
--- a/packages/backend/src/core/RelayService.ts
+++ b/packages/backend/src/core/RelayService.ts
@@ -4,54 +4,35 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import { IsNull } from 'typeorm';
-import type { MiLocalUser, MiUser } from '@/models/User.js';
-import type { RelaysRepository, UsersRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import type { RelaysRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { MemorySingleCache } from '@/misc/cache.js';
import type { MiRelay } from '@/models/Relay.js';
import { QueueService } from '@/core/QueueService.js';
-import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { DI } from '@/di-symbols.js';
import { deepClone } from '@/misc/clone.js';
import { bindThis } from '@/decorators.js';
-
-const ACTOR_USERNAME = 'relay.actor' as const;
+import { SystemAccountService } from '@/core/SystemAccountService.js';
@Injectable()
export class RelayService {
private relaysCache: MemorySingleCache<MiRelay[]>;
constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
@Inject(DI.relaysRepository)
private relaysRepository: RelaysRepository,
private idService: IdService,
private queueService: QueueService,
- private createSystemUserService: CreateSystemUserService,
+ private systemAccountService: SystemAccountService,
private apRendererService: ApRendererService,
) {
this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); // 10m
}
@bindThis
- private async getRelayActor(): Promise<MiLocalUser> {
- const user = await this.usersRepository.findOneBy({
- host: IsNull(),
- username: ACTOR_USERNAME,
- });
-
- if (user) return user as MiLocalUser;
-
- const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME);
- return created as MiLocalUser;
- }
-
- @bindThis
public async addRelay(inbox: string): Promise<MiRelay> {
const relay = await this.relaysRepository.insertOne({
id: this.idService.gen(),
@@ -59,8 +40,8 @@ export class RelayService {
status: 'requesting',
});
- const relayActor = await this.getRelayActor();
- const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
+ const relayActor = await this.systemAccountService.fetch('relay');
+ const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
const activity = this.apRendererService.addContext(follow);
this.queueService.deliver(relayActor, activity, relay.inbox, false);
@@ -77,7 +58,7 @@ export class RelayService {
throw new Error('relay not found');
}
- const relayActor = await this.getRelayActor();
+ const relayActor = await this.systemAccountService.fetch('relay');
const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
const undo = this.apRendererService.renderUndo(follow, relayActor);
const activity = this.apRendererService.addContext(undo);
diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts
index 098b5e1706..a2f1b73cdb 100644
--- a/packages/backend/src/core/RemoteUserResolveService.ts
+++ b/packages/backend/src/core/RemoteUserResolveService.ts
@@ -74,7 +74,7 @@ export class RemoteUserResolveService {
if (user == null) {
const self = await this.resolveSelf(acctLower);
- if (self.href.startsWith(this.config.url)) {
+ if (this.utilityService.isUriLocal(self.href)) {
const local = this.apDbResolverService.parseUri(self.href);
if (local.local && local.type === 'users') {
// the LR points to local
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 0bae3af385..6e4346e22d 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -66,6 +66,7 @@ export type RolePolicies = {
canImportFollowing: boolean;
canImportMuting: boolean;
canImportUserLists: boolean;
+ canChat: boolean;
};
export const DEFAULT_POLICIES: RolePolicies = {
@@ -103,11 +104,11 @@ export const DEFAULT_POLICIES: RolePolicies = {
canImportFollowing: true,
canImportMuting: true,
canImportUserLists: true,
+ canChat: true,
};
@Injectable()
export class RoleService implements OnApplicationShutdown, OnModuleInit {
- private rootUserIdCache: MemorySingleCache<MiUser['id']>;
private rolesCache: MemorySingleCache<MiRole[]>;
private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
private notificationService: NotificationService;
@@ -143,7 +144,6 @@ 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
@@ -411,19 +411,20 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)),
canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)),
canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)),
+ canChat: calc('canChat', vs => vs.some(v => v === true)),
};
}
@bindThis
- public async isModerator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise<boolean> {
+ public async isModerator(user: { id: MiUser['id'] } | null): Promise<boolean> {
if (user == null) return false;
- return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
+ return (this.meta.rootUserId === user.id) || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
}
@bindThis
- public async isAdministrator(user: { id: MiUser['id']; isRoot: MiUser['isRoot'] } | null): Promise<boolean> {
+ public async isAdministrator(user: { id: MiUser['id'] } | null): Promise<boolean> {
if (user == null) return false;
- return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
+ return (this.meta.rootUserId === user.id) || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
}
@bindThis
@@ -472,16 +473,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
.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);
+ if (includeRoot && this.meta.rootUserId) {
+ resultSet.add(this.meta.rootUserId);
}
return [...resultSet].sort((x, y) => x.localeCompare(y));
diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts
index 37721d2bf1..968a5dcc0b 100644
--- a/packages/backend/src/core/S3Service.ts
+++ b/packages/backend/src/core/S3Service.ts
@@ -46,6 +46,8 @@ export class S3Service {
tls: meta.objectStorageUseSSL,
forcePathStyle: meta.objectStorageEndpoint ? meta.objectStorageS3ForcePathStyle : false, // AWS with endPoint omitted
requestHandler: new NodeHttpHandler(handlerOption),
+ requestChecksumCalculation: 'WHEN_REQUIRED',
+ responseChecksumValidation: 'WHEN_REQUIRED',
});
}
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index 9fc0c2b34a..f1dd0f0503 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -15,13 +15,14 @@ import { MiUserProfile } from '@/models/UserProfile.js';
import { IdService } from '@/core/IdService.js';
import { MiUserKeypair } from '@/models/UserKeypair.js';
import { MiUsedUsername } from '@/models/UsedUsername.js';
-import generateUserToken from '@/misc/generate-native-user-token.js';
+import { generateNativeUserToken } from '@/misc/token.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { InstanceActorService } from '@/core/InstanceActorService.js';
import { bindThis } from '@/decorators.js';
import UsersChart from '@/core/chart/charts/users.js';
import { UtilityService } from '@/core/UtilityService.js';
import { UserService } from '@/core/UserService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
+import { MetaService } from '@/core/MetaService.js';
@Injectable()
export class SignupService {
@@ -42,7 +43,8 @@ export class SignupService {
private userService: UserService,
private userEntityService: UserEntityService,
private idService: IdService,
- private instanceActorService: InstanceActorService,
+ private systemAccountService: SystemAccountService,
+ private metaService: MetaService,
private usersChart: UsersChart,
) {
}
@@ -77,7 +79,7 @@ export class SignupService {
}
// Generate secret
- const secret = generateUserToken();
+ const secret = generateNativeUserToken();
// Check username duplication
if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
@@ -89,9 +91,7 @@ export class SignupService {
throw new Error('USED_USERNAME');
}
- const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
-
- if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
+ if (!opts.ignorePreservedUsernames && this.meta.rootUserId != null) {
const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
if (isPreserved) {
throw new Error('USED_USERNAME');
@@ -132,8 +132,7 @@ export class SignupService {
usernameLower: username.toLowerCase(),
host: this.utilityService.toPunyNullable(host),
token: secret,
- isRoot: isTheFirstUser,
- approved: isTheFirstUser || (opts.approved ?? !this.meta.approvalRequiredForSignup),
+ approved: opts.approved ?? !this.meta.approvalRequiredForSignup,
signupReason: reason,
enableRss: false,
}));
@@ -159,6 +158,10 @@ export class SignupService {
this.usersChart.update(account, true);
this.userService.notifySystemWebhook(account, 'userCreated');
+ if (this.meta.rootUserId == null) {
+ await this.metaService.update({ rootUserId: account.id });
+ }
+
return { account, secret };
}
}
diff --git a/packages/backend/src/core/SystemAccountService.ts b/packages/backend/src/core/SystemAccountService.ts
new file mode 100644
index 0000000000..1e050c3054
--- /dev/null
+++ b/packages/backend/src/core/SystemAccountService.ts
@@ -0,0 +1,172 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { randomUUID } from 'node:crypto';
+import { Inject, Injectable } from '@nestjs/common';
+import { DataSource, IsNull } from 'typeorm';
+import bcrypt from 'bcryptjs';
+import { MiLocalUser, MiUser } from '@/models/User.js';
+import { MiSystemAccount, MiUsedUsername, MiUserKeypair, MiUserProfile, type UsersRepository, type SystemAccountsRepository } from '@/models/_.js';
+import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
+import { MemoryKVCache } from '@/misc/cache.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { generateNativeUserToken } from '@/misc/token.js';
+import { IdService } from '@/core/IdService.js';
+import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
+
+export const SYSTEM_ACCOUNT_TYPES = ['actor', 'relay', 'proxy'] as const;
+
+@Injectable()
+export class SystemAccountService {
+ private cache: MemoryKVCache<MiLocalUser>;
+
+ constructor(
+ @Inject(DI.db)
+ private db: DataSource,
+
+ @Inject(DI.meta)
+ private meta: MiMeta,
+
+ @Inject(DI.systemAccountsRepository)
+ private systemAccountsRepository: SystemAccountsRepository,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+
+ private idService: IdService,
+ ) {
+ this.cache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 10); // 10m
+ }
+
+ @bindThis
+ public async list(): Promise<MiSystemAccount[]> {
+ const accounts = await this.systemAccountsRepository.findBy({});
+
+ return accounts;
+ }
+
+ @bindThis
+ public async fetch(type: typeof SYSTEM_ACCOUNT_TYPES[number]): Promise<MiLocalUser> {
+ const cached = this.cache.get(type);
+ if (cached) return cached;
+
+ const systemAccount = await this.systemAccountsRepository.findOne({
+ where: { type: type },
+ relations: ['user'],
+ });
+
+ if (systemAccount) {
+ this.cache.set(type, systemAccount.user as MiLocalUser);
+ return systemAccount.user as MiLocalUser;
+ } else {
+ const created = await this.createCorrespondingUser(type, {
+ username: `system.${type}`, // NOTE: (できれば避けたいが) . が含まれるかどうかでシステムアカウントかどうかを判定している処理もあるので変えないように
+ name: this.meta.name,
+ });
+ this.cache.set(type, created);
+ return created;
+ }
+ }
+
+ @bindThis
+ private async createCorrespondingUser(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
+ username: MiUser['username'];
+ name?: MiUser['name'];
+ }): Promise<MiLocalUser> {
+ const password = randomUUID();
+
+ // Generate hash of password
+ const salt = await bcrypt.genSalt(8);
+ const hash = await bcrypt.hash(password, salt);
+
+ // Generate secret
+ const secret = generateNativeUserToken();
+
+ const keyPair = await genRsaKeyPair();
+
+ let account!: MiUser;
+
+ // Start transaction
+ await this.db.transaction(async transactionalEntityManager => {
+ const exist = await transactionalEntityManager.findOneBy(MiUser, {
+ usernameLower: extra.username.toLowerCase(),
+ host: IsNull(),
+ });
+
+ if (exist) {
+ account = exist;
+ return;
+ }
+
+ account = await transactionalEntityManager.insert(MiUser, {
+ id: this.idService.gen(),
+ username: extra.username,
+ usernameLower: extra.username.toLowerCase(),
+ host: null,
+ token: secret,
+ isLocked: true,
+ isExplorable: false,
+ isBot: true,
+ name: extra.name,
+ }).then(x => transactionalEntityManager.findOneByOrFail(MiUser, x.identifiers[0]));
+
+ await transactionalEntityManager.insert(MiUserKeypair, {
+ publicKey: keyPair.publicKey,
+ privateKey: keyPair.privateKey,
+ userId: account.id,
+ });
+
+ await transactionalEntityManager.insert(MiUserProfile, {
+ userId: account.id,
+ autoAcceptFollowed: false,
+ password: hash,
+ });
+
+ await transactionalEntityManager.insert(MiUsedUsername, {
+ createdAt: new Date(),
+ username: extra.username.toLowerCase(),
+ });
+
+ await transactionalEntityManager.insert(MiSystemAccount, {
+ id: this.idService.gen(),
+ userId: account.id,
+ type: type,
+ });
+ });
+
+ return account as MiLocalUser;
+ }
+
+ @bindThis
+ public async updateCorrespondingUserProfile(type: typeof SYSTEM_ACCOUNT_TYPES[number], extra: {
+ name?: string;
+ description?: MiUserProfile['description'];
+ }): Promise<MiLocalUser> {
+ const user = await this.fetch(type);
+
+ const updates = {} as Partial<MiUser>;
+ if (extra.name !== undefined) updates.name = extra.name;
+
+ if (Object.keys(updates).length > 0) {
+ await this.usersRepository.update(user.id, updates);
+ }
+
+ const profileUpdates = {} as Partial<MiUserProfile>;
+ if (extra.description !== undefined) profileUpdates.description = extra.description;
+
+ if (Object.keys(profileUpdates).length > 0) {
+ await this.userProfilesRepository.update(user.id, profileUpdates);
+ }
+
+ const updated = await this.usersRepository.findOneByOrFail({ id: user.id }) as MiLocalUser;
+ this.cache.set(type, updated);
+
+ return updated;
+ }
+}
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index b98ca97ec9..e7a6be99fb 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -5,7 +5,7 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
-import { IsNull } from 'typeorm';
+import { Brackets, IsNull } from 'typeorm';
import type { MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { QueueService } from '@/core/QueueService.js';
@@ -736,4 +736,30 @@ export class UserFollowingService implements OnModuleInit {
.where('following.followerId = :followerId', { followerId: userId })
.getMany();
}
+
+ @bindThis
+ public isFollowing(followerId: MiUser['id'], followeeId: MiUser['id']) {
+ return this.followingsRepository.exists({
+ where: {
+ followerId,
+ followeeId,
+ },
+ });
+ }
+
+ @bindThis
+ public async isMutual(aUserId: MiUser['id'], bUserId: MiUser['id']) {
+ const count = await this.followingsRepository.createQueryBuilder('following')
+ .where(new Brackets(qb => {
+ qb.where('following.followerId = :aUserId', { aUserId })
+ .andWhere('following.followeeId = :bUserId', { bUserId });
+ }))
+ .orWhere(new Brackets(qb => {
+ qb.where('following.followerId = :bUserId', { bUserId })
+ .andWhere('following.followeeId = :aUserId', { aUserId });
+ }))
+ .getCount();
+
+ return count === 2;
+ }
}
diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts
index 4f4d59a02c..a4c5eb5416 100644
--- a/packages/backend/src/core/UserListService.ts
+++ b/packages/backend/src/core/UserListService.ts
@@ -15,11 +15,11 @@ import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { ProxyAccountService } from '@/core/ProxyAccountService.js';
import { bindThis } from '@/decorators.js';
import { QueueService } from '@/core/QueueService.js';
import { RedisKVCache } from '@/misc/cache.js';
import { RoleService } from '@/core/RoleService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
@Injectable()
export class UserListService implements OnApplicationShutdown, OnModuleInit {
@@ -43,8 +43,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
private userEntityService: UserEntityService,
private idService: IdService,
private globalEventService: GlobalEventService,
- private proxyAccountService: ProxyAccountService,
private queueService: QueueService,
+ private systemAccountService: SystemAccountService,
) {
this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
lifetime: 1000 * 60 * 30, // 30m
@@ -111,10 +111,8 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit {
// このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする
if (this.userEntityService.isRemoteUser(target)) {
- const proxy = await this.proxyAccountService.fetch();
- if (proxy) {
- this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: target.id } }]);
- }
+ const proxy = await this.systemAccountService.fetch('proxy');
+ this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: target.id } }]);
}
}
diff --git a/packages/backend/src/core/UserWebhookService.ts b/packages/backend/src/core/UserWebhookService.ts
index 08db4c9afc..2f79eb429a 100644
--- a/packages/backend/src/core/UserWebhookService.ts
+++ b/packages/backend/src/core/UserWebhookService.ts
@@ -15,7 +15,7 @@ import { QueueService } from '@/core/QueueService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
export type UserWebhookPayload<T extends WebhookEventTypes> =
- T extends 'note' | 'reply' | 'renote' |'mention' | 'edited' ? {
+ T extends 'note' | 'reply' | 'renote' | 'mention' | 'edited' ? {
note: Packed<'Note'>,
} :
T extends 'follow' | 'unfollow' ? {
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index 81eaa5f95d..cb534a229c 100644
--- a/packages/backend/src/core/UtilityService.ts
+++ b/packages/backend/src/core/UtilityService.ts
@@ -39,6 +39,14 @@ export class UtilityService {
return this.punyHost(uri) === this.toPuny(this.config.host);
}
+ // メールアドレスのバリデーションを行う
+ // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
+ @bindThis
+ public validateEmailFormat(email: string): boolean {
+ const regexp = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
+ return regexp.test(email);
+ }
+
@bindThis
public isBlockedHost(blockedHosts: string[], host: string | null): boolean {
if (host == null) return false;
diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts
index ed75e4f467..372e1e2ab7 100644
--- a/packages/backend/src/core/WebAuthnService.ts
+++ b/packages/backend/src/core/WebAuthnService.ts
@@ -127,11 +127,11 @@ export class WebAuthnService {
const { registrationInfo } = verification;
return {
- credentialID: registrationInfo.credentialID,
- credentialPublicKey: registrationInfo.credentialPublicKey,
+ credentialID: registrationInfo.credential.id,
+ credentialPublicKey: registrationInfo.credential.publicKey,
attestationObject: registrationInfo.attestationObject,
fmt: registrationInfo.fmt,
- counter: registrationInfo.counter,
+ counter: registrationInfo.credential.counter,
userVerified: registrationInfo.userVerified,
credentialDeviceType: registrationInfo.credentialDeviceType,
credentialBackedUp: registrationInfo.credentialBackedUp,
@@ -212,9 +212,9 @@ export class WebAuthnService {
expectedChallenge: challenge,
expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId,
- authenticator: {
- credentialID: key.id,
- credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
+ credential: {
+ id: key.id,
+ publicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
},
@@ -292,9 +292,9 @@ export class WebAuthnService {
expectedChallenge: challenge,
expectedOrigin: relyingParty.origin,
expectedRPID: relyingParty.rpId,
- authenticator: {
- credentialID: key.id,
- credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
+ credential: {
+ id: key.id,
+ publicKey: Buffer.from(key.publicKey, 'base64url'),
counter: key.counter,
transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
},
diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts
index 891bdf7334..8d2e181aa5 100644
--- a/packages/backend/src/core/WebhookTestService.ts
+++ b/packages/backend/src/core/WebhookTestService.ts
@@ -7,42 +7,16 @@ 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 { AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js';
-import { Packed } from '@/misc/json-schema.js';
+import { type AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js';
+import { type Packed } from '@/misc/json-schema.js';
import { type WebhookEventTypes } from '@/models/Webhook.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.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>): AbuseReportPayload {
- const result: MiAbuseUserReport = {
- id: 'dummy-abuse-report1',
- targetUserId: 'dummy-target-user',
- targetUser: null,
- reporterId: 'dummy-reporter-user',
- reporter: null,
- assigneeId: null,
- assignee: null,
- resolved: false,
- forwarded: false,
- 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 {
return {
id: 'dummy-user-1',
@@ -79,13 +53,13 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser {
isBot: false,
isCat: true,
speakAsCat: true,
- isRoot: false,
isExplorable: true,
isHibernated: false,
isDeleted: false,
requireSigninToViewContents: false,
makeNotesFollowersOnlyBefore: null,
makeNotesHiddenBefore: null,
+ chatScope: 'mutual',
emojis: [],
score: 0,
host: null,
@@ -150,135 +124,6 @@ function generateDummyNote(override?: Partial<MiNote>): MiNote {
};
}
-function toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Packed<'Note'> {
- return {
- id: note.id,
- createdAt: new Date().toISOString(),
- deletedAt: null,
- text: note.text,
- cw: note.cw,
- userId: note.userId,
- user: toPackedUserLite(note.user ?? generateDummyUser()),
- replyId: note.replyId,
- renoteId: note.renoteId,
- isHidden: false,
- visibility: note.visibility,
- mentions: note.mentions,
- visibleUserIds: note.visibleUserIds,
- fileIds: note.fileIds,
- files: [],
- tags: note.tags,
- poll: null,
- emojis: note.emojis,
- channelId: note.channelId,
- channel: note.channel,
- localOnly: note.localOnly,
- reactionAcceptance: note.reactionAcceptance,
- reactionEmojis: {},
- reactions: {},
- reactionCount: 0,
- renoteCount: note.renoteCount,
- repliesCount: note.repliesCount,
- uri: note.uri ?? undefined,
- url: note.url ?? undefined,
- reactionAndUserPairCache: note.reactionAndUserPairCache,
- ...(detail ? {
- clippedCount: note.clippedCount,
- reply: note.reply ? toPackedNote(note.reply, false) : null,
- renote: note.renote ? toPackedNote(note.renote, true) : null,
- myReaction: null,
- } : {}),
- ...override,
- };
-}
-
-function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'UserLite'> {
- return {
- id: user.id,
- name: user.name,
- username: user.username,
- host: user.host,
- avatarUrl: user.avatarUrl,
- avatarBlurhash: user.avatarBlurhash,
- avatarDecorations: user.avatarDecorations.map(it => ({
- id: it.id,
- angle: it.angle,
- flipH: it.flipH,
- url: 'https://example.com/dummy-image001.png',
- offsetX: it.offsetX,
- offsetY: it.offsetY,
- })),
- isBot: user.isBot,
- isCat: user.isCat,
- speakAsCat: user.speakAsCat,
- emojis: user.emojis,
- onlineStatus: 'active',
- badgeRoles: [],
- noindex: user.noindex,
- isModerator: false,
- isAdmin: false,
- isSystem: false,
- isSilenced: user.isSilenced,
- enableRss: true,
- mandatoryCW: null,
- ...override,
- };
-}
-
-function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Packed<'UserDetailedNotMe'> {
- return {
- ...toPackedUserLite(user),
- url: null,
- uri: null,
- movedTo: null,
- alsoKnownAs: [],
- createdAt: new Date().toISOString(),
- updatedAt: user.updatedAt?.toISOString() ?? null,
- lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
- bannerUrl: user.bannerUrl,
- bannerBlurhash: user.bannerBlurhash,
- backgroundUrl: user.backgroundUrl,
- backgroundBlurhash: user.backgroundBlurhash,
- isLocked: user.isLocked,
- isSilenced: false,
- isSuspended: user.isSuspended,
- description: null,
- location: null,
- birthday: null,
- lang: null,
- fields: [],
- verifiedLinks: [],
- followersCount: user.followersCount,
- followingCount: user.followingCount,
- notesCount: user.notesCount,
- pinnedNoteIds: [],
- pinnedNotes: [],
- pinnedPageId: null,
- pinnedPage: null,
- publicReactions: true,
- followersVisibility: 'public',
- followingVisibility: 'public',
- twoFactorEnabled: false,
- usePasswordLessLogin: false,
- securityKeys: false,
- roles: [],
- memo: null,
- moderationNote: undefined,
- isFollowing: false,
- isFollowed: false,
- hasPendingFollowRequestFromYou: false,
- hasPendingFollowRequestToYou: false,
- isBlocking: false,
- isBlocked: false,
- isMuted: false,
- isRenoteMuted: false,
- notify: 'none',
- withReplies: true,
- listenbrainz: null,
- ...override,
- };
-}
-
const dummyUser1 = generateDummyUser();
const dummyUser2 = generateDummyUser({
id: 'dummy-user-2',
@@ -311,6 +156,7 @@ export class WebhookTestService {
};
constructor(
+ private customEmojiService: CustomEmojiService,
private userWebhookService: UserWebhookService,
private systemWebhookService: SystemWebhookService,
private queueService: QueueService,
@@ -381,19 +227,19 @@ export class WebhookTestService {
switch (params.type) {
case 'note': {
- send('note', { note: toPackedNote(dummyNote1) });
+ send('note', { note: await this.toPackedNote(dummyNote1) });
break;
}
case 'reply': {
- send('reply', { note: toPackedNote(dummyReply1) });
+ send('reply', { note: await this.toPackedNote(dummyReply1) });
break;
}
case 'renote': {
- send('renote', { note: toPackedNote(dummyRenote1) });
+ send('renote', { note: await this.toPackedNote(dummyRenote1) });
break;
}
case 'mention': {
- send('mention', { note: toPackedNote(dummyMention1) });
+ send('mention', { note: await this.toPackedNote(dummyMention1) });
break;
}
case 'edited': {
@@ -401,15 +247,15 @@ export class WebhookTestService {
break;
}
case 'follow': {
- send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) });
+ send('follow', { user: await this.toPackedUserDetailedNotMe(dummyUser1) });
break;
}
case 'followed': {
- send('followed', { user: toPackedUserLite(dummyUser2) });
+ send('followed', { user: await this.toPackedUserLite(dummyUser2) });
break;
}
case 'unfollow': {
- send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) });
+ send('unfollow', { user: await this.toPackedUserDetailedNotMe(dummyUser3) });
break;
}
// まだ実装されていない (#9485)
@@ -458,7 +304,7 @@ export class WebhookTestService {
switch (params.type) {
case 'abuseReport': {
- send('abuseReport', generateAbuseReport({
+ send('abuseReport', await this.generateAbuseReport({
targetUserId: dummyUser1.id,
targetUser: dummyUser1,
reporterId: dummyUser2.id,
@@ -467,7 +313,7 @@ export class WebhookTestService {
break;
}
case 'abuseReportResolved': {
- send('abuseReportResolved', generateAbuseReport({
+ send('abuseReportResolved', await this.generateAbuseReport({
targetUserId: dummyUser1.id,
targetUser: dummyUser1,
reporterId: dummyUser2.id,
@@ -479,7 +325,7 @@ export class WebhookTestService {
break;
}
case 'userCreated': {
- send('userCreated', toPackedUserLite(dummyUser1));
+ send('userCreated', await this.toPackedUserLite(dummyUser1));
break;
}
case 'inactiveModeratorsWarning': {
@@ -505,4 +351,154 @@ export class WebhookTestService {
}
}
}
+
+ @bindThis
+ private async generateAbuseReport(override?: Partial<MiAbuseUserReport>): Promise<AbuseReportPayload> {
+ const result: MiAbuseUserReport = {
+ id: 'dummy-abuse-report1',
+ targetUserId: 'dummy-target-user',
+ targetUser: null,
+ reporterId: 'dummy-reporter-user',
+ reporter: null,
+ assigneeId: null,
+ assignee: null,
+ resolved: false,
+ forwarded: false,
+ comment: 'This is a dummy report for testing purposes.',
+ targetUserHost: null,
+ reporterHost: null,
+ resolvedAs: null,
+ moderationNote: 'foo',
+ ...override,
+ };
+
+ return {
+ ...result,
+ targetUser: result.targetUser ? await this.toPackedUserLite(result.targetUser) : null,
+ reporter: result.reporter ? await this.toPackedUserLite(result.reporter) : null,
+ assignee: result.assignee ? await this.toPackedUserLite(result.assignee) : null,
+ };
+ }
+
+ @bindThis
+ private async toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Promise<Packed<'Note'>> {
+ return {
+ id: note.id,
+ createdAt: new Date().toISOString(),
+ deletedAt: null,
+ text: note.text,
+ cw: note.cw,
+ userId: note.userId,
+ user: await this.toPackedUserLite(note.user ?? generateDummyUser()),
+ replyId: note.replyId,
+ renoteId: note.renoteId,
+ isHidden: false,
+ visibility: note.visibility,
+ mentions: note.mentions,
+ visibleUserIds: note.visibleUserIds,
+ fileIds: note.fileIds,
+ files: [],
+ tags: note.tags,
+ poll: null,
+ emojis: await this.customEmojiService.populateEmojis(note.emojis, note.userHost),
+ channelId: note.channelId,
+ channel: note.channel,
+ localOnly: note.localOnly,
+ reactionAcceptance: note.reactionAcceptance,
+ reactionEmojis: {},
+ reactions: {},
+ reactionCount: 0,
+ renoteCount: note.renoteCount,
+ repliesCount: note.repliesCount,
+ uri: note.uri ?? undefined,
+ url: note.url ?? undefined,
+ reactionAndUserPairCache: note.reactionAndUserPairCache,
+ ...(detail ? {
+ clippedCount: note.clippedCount,
+ reply: note.reply ? await this.toPackedNote(note.reply, false) : null,
+ renote: note.renote ? await this.toPackedNote(note.renote, true) : null,
+ myReaction: null,
+ } : {}),
+ ...override,
+ };
+ }
+
+ @bindThis
+ private async toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Promise<Packed<'UserLite'>> {
+ return {
+ id: user.id,
+ name: user.name,
+ username: user.username,
+ host: user.host,
+ avatarUrl: user.avatarUrl,
+ avatarBlurhash: user.avatarBlurhash,
+ avatarDecorations: user.avatarDecorations.map(it => ({
+ id: it.id,
+ angle: it.angle,
+ flipH: it.flipH,
+ url: 'https://example.com/dummy-image001.png',
+ offsetX: it.offsetX,
+ offsetY: it.offsetY,
+ })),
+ isBot: user.isBot,
+ isCat: user.isCat,
+ emojis: await this.customEmojiService.populateEmojis(user.emojis, user.host),
+ onlineStatus: 'active',
+ badgeRoles: [],
+ ...override,
+ };
+ }
+
+ @bindThis
+ private async toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Promise<Packed<'UserDetailedNotMe'>> {
+ return {
+ ...await this.toPackedUserLite(user),
+ url: null,
+ uri: null,
+ movedTo: null,
+ alsoKnownAs: [],
+ createdAt: new Date().toISOString(),
+ updatedAt: user.updatedAt?.toISOString() ?? null,
+ lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
+ bannerUrl: user.bannerUrl,
+ bannerBlurhash: user.bannerBlurhash,
+ isLocked: user.isLocked,
+ isSilenced: false,
+ isSuspended: user.isSuspended,
+ description: null,
+ location: null,
+ birthday: null,
+ lang: null,
+ fields: [],
+ verifiedLinks: [],
+ followersCount: user.followersCount,
+ followingCount: user.followingCount,
+ notesCount: user.notesCount,
+ pinnedNoteIds: [],
+ pinnedNotes: [],
+ pinnedPageId: null,
+ pinnedPage: null,
+ publicReactions: true,
+ followersVisibility: 'public',
+ followingVisibility: 'public',
+ chatScope: 'mutual',
+ twoFactorEnabled: false,
+ usePasswordLessLogin: false,
+ securityKeys: false,
+ roles: [],
+ memo: null,
+ moderationNote: undefined,
+ isFollowing: false,
+ isFollowed: false,
+ hasPendingFollowRequestFromYou: false,
+ hasPendingFollowRequestToYou: false,
+ isBlocking: false,
+ isBlocked: false,
+ isMuted: false,
+ isRenoteMuted: false,
+ notify: 'none',
+ withReplies: true,
+ ...override,
+ };
+ }
}
diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
index f045333d2a..eaa592b9e0 100644
--- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
+++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
@@ -197,6 +197,25 @@ export class ApDeliverManagerService {
await manager.execute();
}
+ /**
+ * Deliver activity to users
+ * @param actor
+ * @param activity Activity
+ * @param targets Target users
+ */
+ @bindThis
+ public async deliverToUsers(actor: { id: MiLocalUser['id']; host: null; }, activity: IActivity, targets: MiRemoteUser[]): Promise<void> {
+ const manager = new DeliverManager(
+ this.userEntityService,
+ this.followingsRepository,
+ this.queueService,
+ actor,
+ activity,
+ );
+ for (const to of targets) manager.addDirectRecipe(to);
+ await manager.execute();
+ }
+
@bindThis
public createDeliverManager(actor: { id: MiUser['id']; host: null; }, activity: IActivity | null): DeliverManager {
return new DeliverManager(
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 402d5ab2a4..ac4a408fa6 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -598,19 +598,12 @@ export class ApInboxService {
return `skip: delete actor ${actor.uri} !== ${uri}`;
}
- const user = await this.usersRepository.findOneBy({ id: actor.id });
- if (user == null) {
- return 'skip: actor not found';
- } else if (user.isDeleted) {
- return 'skip: already deleted';
+ if (!(await this.usersRepository.update({ id: actor.id, isDeleted: false }, { isDeleted: true })).affected) {
+ return 'skip: already deleted or actor not found';
}
const job = await this.queueService.createDeleteAccountJob(actor);
- await this.usersRepository.update(actor.id, {
- isDeleted: true,
- });
-
this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: actor.id });
return `ok: queued ${job.name} ${job.id}`;
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 61878c60e8..6612303196 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -30,6 +30,7 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { IdService } from '@/core/IdService.js';
import { appendContentWarning } from '@/misc/append-content-warning.js';
import { QueryService } from '@/core/QueryService.js';
+import { UtilityService } from '@/core/UtilityService.js';
import { JsonLdService } from './JsonLdService.js';
import { ApMfmService } from './ApMfmService.js';
import { CONTEXT } from './misc/contexts.js';
@@ -72,6 +73,7 @@ export class ApRendererService {
private mfmService: MfmService,
private idService: IdService,
private readonly queryService: QueryService,
+ private utilityService: UtilityService,
) {
}
@@ -267,6 +269,49 @@ export class ApRendererService {
}
@bindThis
+ public renderIdenticon(user: MiLocalUser): IApImage {
+ return {
+ type: 'Image',
+ url: this.userEntityService.getIdenticonUrl(user),
+ sensitive: false,
+ name: null,
+ };
+ }
+
+ @bindThis
+ public renderSystemAvatar(user: MiLocalUser): IApImage {
+ if (this.meta.iconUrl == null) return this.renderIdenticon(user);
+ return {
+ type: 'Image',
+ url: this.meta.iconUrl,
+ sensitive: false,
+ name: null,
+ };
+ }
+
+ @bindThis
+ public renderSystemBanner(): IApImage | null {
+ if (this.meta.bannerUrl == null) return null;
+ return {
+ type: 'Image',
+ url: this.meta.bannerUrl,
+ sensitive: false,
+ name: null,
+ };
+ }
+
+ @bindThis
+ public renderSystemBackground(): IApImage | null {
+ if (this.meta.backgroundImageUrl == null) return null;
+ return {
+ type: 'Image',
+ url: this.meta.backgroundImageUrl,
+ sensitive: false,
+ name: null,
+ };
+ }
+
+ @bindThis
public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey {
return {
id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`,
@@ -503,11 +548,28 @@ export class ApRendererService {
this.userProfilesRepository.findOneByOrFail({ userId: user.id }),
]);
+ const tryRewriteUrl = (maybeUrl: string) => {
+ const urlSafeRegex = /^(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/;
+ try {
+ const match = maybeUrl.match(urlSafeRegex);
+ if (!match) {
+ return maybeUrl;
+ }
+ const urlPart = match[0];
+ const urlPartParsed = new URL(urlPart);
+ const restPart = maybeUrl.slice(match[0].length);
+
+ return `<a href="${urlPartParsed.href}" rel="me nofollow noopener" target="_blank">${urlPart}</a>${restPart}`;
+ } catch (e) {
+ return maybeUrl;
+ }
+ };
+
const attachment = profile.fields.map(field => ({
type: 'PropertyValue',
name: field.name,
value: (field.value.startsWith('http://') || field.value.startsWith('https://'))
- ? `<a href="${new URL(field.value).href}" rel="me nofollow noopener" target="_blank">${new URL(field.value).href}</a>`
+ ? tryRewriteUrl(field.value)
: field.value,
}));
@@ -542,9 +604,9 @@ export class ApRendererService {
_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,
+ icon: avatar ? this.renderImage(avatar) : isSystem ? this.renderSystemAvatar(user) : this.renderIdenticon(user),
+ image: banner ? this.renderImage(banner) : isSystem ? this.renderSystemBanner() : null,
+ backgroundUrl: background ? this.renderImage(background) : isSystem ? this.renderSystemBackground() : null,
tag,
manuallyApprovesFollowers: user.isLocked,
discoverable: user.isExplorable,
@@ -660,7 +722,7 @@ export class ApRendererService {
@bindThis
public renderUndo(object: string | IObject, user: { id: MiUser['id'] }): IUndo {
- const id = typeof object !== 'string' && typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined;
+ const id = typeof object !== 'string' && typeof object.id === 'string' && this.utilityService.isUriLocal(object.id) ? `${object.id}/undo` : undefined;
return {
type: 'Undo',
diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts
index 952d1f5219..5caee21610 100644
--- a/packages/backend/src/core/activitypub/ApRequestService.ts
+++ b/packages/backend/src/core/activitypub/ApRequestService.ts
@@ -17,6 +17,7 @@ import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
+import { FetchAllowSoftFailMask as FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject, IObjectWithId } from './type.js';
type Request = {
@@ -185,7 +186,7 @@ export class ApRequestService {
* @param followAlternate
*/
@bindThis
- public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise<IObjectWithId> {
+ public async signedGet(url: string, user: { id: MiUser['id'] }, allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict, followAlternate?: boolean): Promise<IObjectWithId> {
const _followAlternate = followAlternate ?? true;
const keypair = await this.userKeypairService.getUserKeypair(user.id);
@@ -254,7 +255,7 @@ export class ApRequestService {
if (alternate) {
const href = alternate.getAttribute('href');
if (href && this.apUtilityService.haveSameAuthority(url, href)) {
- return await this.signedGet(href, user, false);
+ return await this.signedGet(href, user, allowSoftfail, false);
}
}
} catch (e) {
@@ -271,7 +272,7 @@ export class ApRequestService {
// Make sure the object ID matches the final URL (which is where it actually exists).
// The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match.
- this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url);
+ this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url, allowSoftfail);
return activity as IObjectWithId;
}
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index 12c3202af1..27dcf2372b 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -6,7 +6,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull, Not } from 'typeorm';
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
-import { InstanceActorService } from '@/core/InstanceActorService.js';
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta, SkApFetchLog } from '@/models/_.js';
import type { Config } from '@/config.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
@@ -16,13 +15,15 @@ import { bindThis } from '@/decorators.js';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { fromTuple } from '@/misc/from-tuple.js';
-import { IdentifiableError } from '@/misc/identifiable-error.js';
import { ApLogService, calculateDurationSince, extractObjectContext } from '@/core/ApLogService.js';
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
import { getApId, getNullableApId, IObjectWithId, isCollectionOrOrderedCollection } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
+import { FetchAllowSoftFailMask } from './misc/check-against-url.js';
import type { IObject, ICollection, IOrderedCollection, ApObject } from './type.js';
export class Resolver {
@@ -39,7 +40,7 @@ export class Resolver {
private noteReactionsRepository: NoteReactionsRepository,
private followRequestsRepository: FollowRequestsRepository,
private utilityService: UtilityService,
- private instanceActorService: InstanceActorService,
+ private systemAccountService: SystemAccountService,
private apRequestService: ApRequestService,
private httpRequestService: HttpRequestService,
private apRendererService: ApRendererService,
@@ -103,11 +104,10 @@ export class Resolver {
return await this.resolve(id);
}
- public async resolve(value: string | [string]): Promise<IObjectWithId>;
- public async resolve(value: string | IObject | [string | IObject]): Promise<IObject>;
+ public async resolve(value: string | [string], allowSoftfail?: FetchAllowSoftFailMask): Promise<IObjectWithId>;
+ public async resolve(value: string | IObject | [string | IObject], allowSoftfail?: FetchAllowSoftFailMask): Promise<IObject>;
@bindThis
- public async resolve(value: string | IObject | [string | IObject]): Promise<IObject> {
- // eslint-disable-next-line no-param-reassign
+ public async resolve(value: string | IObject | [string | IObject], allowSoftfail: FetchAllowSoftFailMask = FetchAllowSoftFailMask.Strict): Promise<IObject> {
value = fromTuple(value);
if (typeof value !== 'string') {
@@ -116,13 +116,13 @@ export class Resolver {
const host = this.utilityService.extractDbHost(value);
if (this.config.activityLogging.enabled && !this.utilityService.isSelfHost(host)) {
- return await this._resolveLogged(value, host);
+ return await this._resolveLogged(value, host, allowSoftfail);
} else {
- return await this._resolve(value, host);
+ return await this._resolve(value, host, allowSoftfail);
}
}
- private async _resolveLogged(requestUri: string, host: string): Promise<IObjectWithId> {
+ private async _resolveLogged(requestUri: string, host: string, allowSoftfail: FetchAllowSoftFailMask): Promise<IObjectWithId> {
const startTime = process.hrtime.bigint();
const log = await this.apLogService.createFetchLog({
@@ -131,7 +131,7 @@ export class Resolver {
});
try {
- const result = await this._resolve(requestUri, host, log);
+ const result = await this._resolve(requestUri, host, allowSoftfail, log);
log.accepted = true;
log.result = 'ok';
@@ -151,7 +151,7 @@ export class Resolver {
}
}
- private async _resolve(value: string, host: string, log?: SkApFetchLog): Promise<IObjectWithId> {
+ private async _resolve(value: string, host: string, allowSoftfail: FetchAllowSoftFailMask, log?: SkApFetchLog): Promise<IObjectWithId> {
if (value.includes('#')) {
// URLs with fragment parts cannot be resolved correctly because
// the fragment part does not get transmitted over HTTP(S).
@@ -178,12 +178,12 @@ export class Resolver {
}
if (this.config.signToActivityPubGet && !this.user) {
- this.user = await this.instanceActorService.getInstanceActor();
+ this.user = await this.systemAccountService.fetch('actor');
}
const object = (this.user
- ? await this.apRequestService.signedGet(value, this.user)
- : await this.httpRequestService.getActivityJson(value));
+ ? await this.apRequestService.signedGet(value, this.user, allowSoftfail) as IObject
+ : await this.httpRequestService.getActivityJson(value, allowSoftfail)) as IObject;
if (log) {
const { object: objectOnly, context, contextHash } = extractObjectContext(object);
@@ -307,7 +307,7 @@ export class ApResolverService {
private followRequestsRepository: FollowRequestsRepository,
private utilityService: UtilityService,
- private instanceActorService: InstanceActorService,
+ private systemAccountService: SystemAccountService,
private apRequestService: ApRequestService,
private httpRequestService: HttpRequestService,
private apRendererService: ApRendererService,
@@ -332,7 +332,7 @@ export class ApResolverService {
this.noteReactionsRepository,
this.followRequestsRepository,
this.utilityService,
- this.instanceActorService,
+ this.systemAccountService,
this.apRequestService,
this.httpRequestService,
this.apRendererService,
diff --git a/packages/backend/src/core/activitypub/misc/check-against-url.ts b/packages/backend/src/core/activitypub/misc/check-against-url.ts
new file mode 100644
index 0000000000..282859907d
--- /dev/null
+++ b/packages/backend/src/core/activitypub/misc/check-against-url.ts
@@ -0,0 +1,38 @@
+/*
+ * SPDX-FileCopyrightText: dakkar and sharkey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import type { IObject } from '../type.js';
+
+export enum FetchAllowSoftFailMask {
+ // Allow no softfail flags
+ Strict = 0,
+ // The values in tuple (requestUrl, finalUrl, objectId) are not all identical
+ //
+ // This condition is common for user-initiated lookups but should not be allowed in federation loop
+ //
+ // Allow variations:
+ // good example: https://alice.example.com/@user -> https://alice.example.com/user/:userId
+ // problematic example: https://alice.example.com/redirect?url=https://bad.example.com/ -> https://bad.example.com/ -> https://alice.example.com/somethingElse
+ NonCanonicalId = 1 << 0,
+ // Allow the final object to be at most one subdomain deeper than the request URL, similar to SPF relaxed alignment
+ //
+ // Currently no code path allows this flag to be set, but is kept in case of future use as some niche deployments do this, and we provide a pre-reviewed mechanism to opt-in.
+ //
+ // Allow variations:
+ // good example: https://example.com/@user -> https://activitypub.example.com/@user { id: 'https://activitypub.example.com/@user' }
+ // problematic example: https://example.com/@user -> https://untrusted.example.com/@user { id: 'https://untrusted.example.com/@user' }
+ MisalignedOrigin = 1 << 1,
+ // The requested URL has a different host than the returned object ID, although the final URL is still consistent with the object ID
+ //
+ // This condition is common for user-initiated lookups using an intermediate host but should not be allowed in federation loops
+ //
+ // Allow variations:
+ // good example: https://alice.example.com/@user@bob.example.com -> https://bob.example.com/@user { id: 'https://bob.example.com/@user' }
+ // problematic example: https://alice.example.com/definitelyAlice -> https://bob.example.com/@somebodyElse { id: 'https://bob.example.com/@somebodyElse' }
+ CrossOrigin = 1 << 2 | MisalignedOrigin,
+ // Allow all softfail flags
+ //
+ // do not use this flag on released code
+ Any = ~0,
+}
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index da29a3c527..59f1d73fb0 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -568,7 +568,7 @@ export class ApPersonService implements OnModuleInit {
inbox: person.inbox,
sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null,
followersUri: person.followers ? getApId(person.followers) : undefined,
- featured: person.featured,
+ featured: person.featured ? getApId(person.featured) : undefined,
emojis: emojiNames,
name: truncate(person.name, nameLength),
tags,
@@ -608,7 +608,9 @@ export class ApPersonService implements OnModuleInit {
if (moving) updates.movedAt = new Date();
// Update user
- await this.usersRepository.update(exist.id, updates);
+ if (!(await this.usersRepository.update({ id: exist.id, isDeleted: false }, updates)).affected) {
+ return 'skip';
+ }
if (person.publicKey) {
await this.userPublickeysRepository.update({ userId: exist.id }, {
@@ -714,7 +716,7 @@ export class ApPersonService implements OnModuleInit {
@bindThis
public async updateFeatured(userId: MiUser['id'], resolver?: Resolver): Promise<void> {
- const user = await this.usersRepository.findOneByOrFail({ id: userId });
+ const user = await this.usersRepository.findOneByOrFail({ id: userId, isDeleted: false });
if (!this.userEntityService.isRemoteUser(user)) return;
if (!user.featured) return;
diff --git a/packages/backend/src/core/entities/ChatEntityService.ts b/packages/backend/src/core/entities/ChatEntityService.ts
new file mode 100644
index 0000000000..099a9e3ad2
--- /dev/null
+++ b/packages/backend/src/core/entities/ChatEntityService.ts
@@ -0,0 +1,376 @@
+/*
+ * 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 { MiUser, ChatMessagesRepository, MiChatMessage, ChatRoomsRepository, MiChatRoom, MiChatRoomInvitation, ChatRoomInvitationsRepository, MiChatRoomMembership, ChatRoomMembershipsRepository } from '@/models/_.js';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { Packed } from '@/misc/json-schema.js';
+import type { } from '@/models/Blocking.js';
+import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
+import { UserEntityService } from './UserEntityService.js';
+import { DriveFileEntityService } from './DriveFileEntityService.js';
+import { In } from 'typeorm';
+
+@Injectable()
+export class ChatEntityService {
+ constructor(
+ @Inject(DI.chatMessagesRepository)
+ private chatMessagesRepository: ChatMessagesRepository,
+
+ @Inject(DI.chatRoomsRepository)
+ private chatRoomsRepository: ChatRoomsRepository,
+
+ @Inject(DI.chatRoomInvitationsRepository)
+ private chatRoomInvitationsRepository: ChatRoomInvitationsRepository,
+
+ @Inject(DI.chatRoomMembershipsRepository)
+ private chatRoomMembershipsRepository: ChatRoomMembershipsRepository,
+
+ private userEntityService: UserEntityService,
+ private driveFileEntityService: DriveFileEntityService,
+ private idService: IdService,
+ ) {
+ }
+
+ @bindThis
+ public async packMessageDetailed(
+ src: MiChatMessage['id'] | MiChatMessage,
+ me?: { id: MiUser['id'] },
+ options?: {
+ _hint_?: {
+ packedFiles?: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
+ packedUsers?: Map<MiChatMessage['id'], Packed<'UserLite'>>;
+ packedRooms?: Map<MiChatMessage['toRoomId'], Packed<'ChatRoom'> | null>;
+ };
+ },
+ ): Promise<Packed<'ChatMessage'>> {
+ const packedUsers = options?._hint_?.packedUsers;
+ const packedFiles = options?._hint_?.packedFiles;
+ const packedRooms = options?._hint_?.packedRooms;
+
+ const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
+
+ const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = [];
+
+ for (const record of message.reactions) {
+ const [userId, reaction] = record.split('/');
+ reactions.push({
+ user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId),
+ reaction,
+ });
+ }
+
+ return {
+ id: message.id,
+ createdAt: this.idService.parse(message.id).date.toISOString(),
+ text: message.text,
+ fromUserId: message.fromUserId,
+ fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId, me),
+ toUserId: message.toUserId,
+ toUser: message.toUserId ? (packedUsers?.get(message.toUserId) ?? await this.userEntityService.pack(message.toUser ?? message.toUserId, me)) : undefined,
+ toRoomId: message.toRoomId,
+ toRoom: message.toRoomId ? (packedRooms?.get(message.toRoomId) ?? await this.packRoom(message.toRoom ?? message.toRoomId, me)) : undefined,
+ fileId: message.fileId,
+ file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
+ reactions,
+ };
+ }
+
+ @bindThis
+ public async packMessagesDetailed(
+ messages: MiChatMessage[],
+ me: { id: MiUser['id'] },
+ ) {
+ if (messages.length === 0) return [];
+
+ const excludeMe = (x: MiUser | string) => {
+ if (typeof x === 'string') {
+ return x !== me.id;
+ } else {
+ return x.id !== me.id;
+ }
+ };
+
+ const users = [
+ ...messages.map((m) => m.fromUser ?? m.fromUserId).filter(excludeMe),
+ ...messages.map((m) => m.toUser ?? m.toUserId).filter(x => x != null).filter(excludeMe),
+ ];
+
+ const reactedUserIds = messages.flatMap(x => x.reactions.map(r => r.split('/')[0]));
+
+ for (const reactedUserId of reactedUserIds) {
+ if (!users.some(x => typeof x === 'string' ? x === reactedUserId : x.id === reactedUserId)) {
+ users.push(reactedUserId);
+ }
+ }
+
+ const [packedUsers, packedFiles, packedRooms] = await Promise.all([
+ this.userEntityService.packMany(users, me)
+ .then(users => new Map(users.map(u => [u.id, u]))),
+ this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null))
+ .then(files => new Map(files.map(f => [f.id, f]))),
+ this.packRooms(messages.map(m => m.toRoom ?? m.toRoomId).filter(x => x != null), me)
+ .then(rooms => new Map(rooms.map(r => [r.id, r]))),
+ ]);
+
+ return Promise.all(messages.map(message => this.packMessageDetailed(message, me, { _hint_: { packedUsers, packedFiles, packedRooms } })));
+ }
+
+ @bindThis
+ public async packMessageLiteFor1on1(
+ src: MiChatMessage['id'] | MiChatMessage,
+ options?: {
+ _hint_?: {
+ packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
+ };
+ },
+ ): Promise<Packed<'ChatMessageLite'>> {
+ const packedFiles = options?._hint_?.packedFiles;
+
+ const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
+
+ const reactions: { reaction: string; }[] = [];
+
+ for (const record of message.reactions) {
+ const [userId, reaction] = record.split('/');
+ reactions.push({
+ reaction,
+ });
+ }
+
+ return {
+ id: message.id,
+ createdAt: this.idService.parse(message.id).date.toISOString(),
+ text: message.text,
+ fromUserId: message.fromUserId,
+ toUserId: message.toUserId,
+ fileId: message.fileId,
+ file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
+ reactions,
+ };
+ }
+
+ @bindThis
+ public async packMessagesLiteFor1on1(
+ messages: MiChatMessage[],
+ ) {
+ if (messages.length === 0) return [];
+
+ const [packedFiles] = await Promise.all([
+ this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null))
+ .then(files => new Map(files.map(f => [f.id, f]))),
+ ]);
+
+ return Promise.all(messages.map(message => this.packMessageLiteFor1on1(message, { _hint_: { packedFiles } })));
+ }
+
+ @bindThis
+ public async packMessageLiteForRoom(
+ src: MiChatMessage['id'] | MiChatMessage,
+ options?: {
+ _hint_?: {
+ packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
+ packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
+ };
+ },
+ ): Promise<Packed<'ChatMessageLite'>> {
+ const packedFiles = options?._hint_?.packedFiles;
+ const packedUsers = options?._hint_?.packedUsers;
+
+ const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
+
+ const reactions: { user: Packed<'UserLite'>; reaction: string; }[] = [];
+
+ for (const record of message.reactions) {
+ const [userId, reaction] = record.split('/');
+ reactions.push({
+ user: packedUsers?.get(userId) ?? await this.userEntityService.pack(userId),
+ reaction,
+ });
+ }
+
+ return {
+ id: message.id,
+ createdAt: this.idService.parse(message.id).date.toISOString(),
+ text: message.text,
+ fromUserId: message.fromUserId,
+ fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId),
+ toRoomId: message.toRoomId,
+ fileId: message.fileId,
+ file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
+ reactions,
+ };
+ }
+
+ @bindThis
+ public async packMessagesLiteForRoom(
+ messages: MiChatMessage[],
+ ) {
+ if (messages.length === 0) return [];
+
+ const users = messages.map(x => x.fromUser ?? x.fromUserId);
+ const reactedUserIds = messages.flatMap(x => x.reactions.map(r => r.split('/')[0]));
+
+ for (const reactedUserId of reactedUserIds) {
+ if (!users.some(x => typeof x === 'string' ? x === reactedUserId : x.id === reactedUserId)) {
+ users.push(reactedUserId);
+ }
+ }
+
+ const [packedUsers, packedFiles] = await Promise.all([
+ this.userEntityService.packMany(users)
+ .then(users => new Map(users.map(u => [u.id, u]))),
+ this.driveFileEntityService.packMany(messages.map(m => m.file).filter(x => x != null))
+ .then(files => new Map(files.map(f => [f.id, f]))),
+ ]);
+
+ return Promise.all(messages.map(message => this.packMessageLiteForRoom(message, { _hint_: { packedFiles, packedUsers } })));
+ }
+
+ @bindThis
+ public async packRoom(
+ src: MiChatRoom['id'] | MiChatRoom,
+ me?: { id: MiUser['id'] },
+ options?: {
+ _hint_?: {
+ packedOwners: Map<MiChatRoom['id'], Packed<'UserLite'>>;
+ memberships?: Map<MiChatRoom['id'], MiChatRoomMembership | null | undefined>;
+ };
+ },
+ ): Promise<Packed<'ChatRoom'>> {
+ const room = typeof src === 'object' ? src : await this.chatRoomsRepository.findOneByOrFail({ id: src });
+
+ const membership = me && me.id !== room.ownerId ? (options?._hint_?.memberships?.get(room.id) ?? await this.chatRoomMembershipsRepository.findOneBy({ roomId: room.id, userId: me.id })) : null;
+
+ return {
+ id: room.id,
+ createdAt: this.idService.parse(room.id).date.toISOString(),
+ name: room.name,
+ description: room.description,
+ ownerId: room.ownerId,
+ owner: options?._hint_?.packedOwners.get(room.ownerId) ?? await this.userEntityService.pack(room.owner ?? room.ownerId, me),
+ isMuted: membership != null ? membership.isMuted : false,
+ };
+ }
+
+ @bindThis
+ public async packRooms(
+ rooms: (MiChatRoom | MiChatRoom['id'])[],
+ me: { id: MiUser['id'] },
+ ) {
+ if (rooms.length === 0) return [];
+
+ const _rooms = rooms.filter((room): room is MiChatRoom => typeof room !== 'string');
+ if (_rooms.length !== rooms.length) {
+ _rooms.push(
+ ...await this.chatRoomsRepository.find({
+ where: {
+ id: In(rooms.filter((room): room is string => typeof room === 'string')),
+ },
+ relations: ['owner'],
+ }),
+ );
+ }
+
+ const owners = _rooms.map(x => x.owner ?? x.ownerId);
+
+ const [packedOwners, memberships] = await Promise.all([
+ this.userEntityService.packMany(owners, me)
+ .then(users => new Map(users.map(u => [u.id, u]))),
+ this.chatRoomMembershipsRepository.find({
+ where: {
+ roomId: In(_rooms.map(x => x.id)),
+ userId: me.id,
+ },
+ }).then(memberships => new Map(_rooms.map(r => [r.id, memberships.find(m => m.roomId === r.id)]))),
+ ]);
+
+ return Promise.all(_rooms.map(room => this.packRoom(room, me, { _hint_: { packedOwners, memberships } })));
+ }
+
+ @bindThis
+ public async packRoomInvitation(
+ src: MiChatRoomInvitation['id'] | MiChatRoomInvitation,
+ me: { id: MiUser['id'] },
+ options?: {
+ _hint_?: {
+ packedRooms: Map<MiChatRoomInvitation['roomId'], Packed<'ChatRoom'>>;
+ packedUsers: Map<MiChatRoomInvitation['id'], Packed<'UserLite'>>;
+ };
+ },
+ ): Promise<Packed<'ChatRoomInvitation'>> {
+ const invitation = typeof src === 'object' ? src : await this.chatRoomInvitationsRepository.findOneByOrFail({ id: src });
+
+ return {
+ id: invitation.id,
+ createdAt: this.idService.parse(invitation.id).date.toISOString(),
+ roomId: invitation.roomId,
+ room: options?._hint_?.packedRooms.get(invitation.roomId) ?? await this.packRoom(invitation.room ?? invitation.roomId, me),
+ userId: invitation.userId,
+ user: options?._hint_?.packedUsers.get(invitation.userId) ?? await this.userEntityService.pack(invitation.user ?? invitation.userId, me),
+ };
+ }
+
+ @bindThis
+ public async packRoomInvitations(
+ invitations: MiChatRoomInvitation[],
+ me: { id: MiUser['id'] },
+ ) {
+ if (invitations.length === 0) return [];
+
+ return Promise.all(invitations.map(invitation => this.packRoomInvitation(invitation, me)));
+ }
+
+ @bindThis
+ public async packRoomMembership(
+ src: MiChatRoomMembership['id'] | MiChatRoomMembership,
+ me: { id: MiUser['id'] },
+ options?: {
+ populateUser?: boolean;
+ populateRoom?: boolean;
+ _hint_?: {
+ packedRooms: Map<MiChatRoomMembership['roomId'], Packed<'ChatRoom'>>;
+ packedUsers: Map<MiChatRoomMembership['id'], Packed<'UserLite'>>;
+ };
+ },
+ ): Promise<Packed<'ChatRoomMembership'>> {
+ const membership = typeof src === 'object' ? src : await this.chatRoomMembershipsRepository.findOneByOrFail({ id: src });
+
+ return {
+ id: membership.id,
+ createdAt: this.idService.parse(membership.id).date.toISOString(),
+ userId: membership.userId,
+ user: options?.populateUser ? (options._hint_?.packedUsers.get(membership.userId) ?? await this.userEntityService.pack(membership.user ?? membership.userId, me)) : undefined,
+ roomId: membership.roomId,
+ room: options?.populateRoom ? (options._hint_?.packedRooms.get(membership.roomId) ?? await this.packRoom(membership.room ?? membership.roomId, me)) : undefined,
+ };
+ }
+
+ @bindThis
+ public async packRoomMemberships(
+ memberships: MiChatRoomMembership[],
+ me: { id: MiUser['id'] },
+ options: {
+ populateUser?: boolean;
+ populateRoom?: boolean;
+ } = {},
+ ) {
+ if (memberships.length === 0) return [];
+
+ const users = memberships.map(x => x.user ?? x.userId);
+ const rooms = memberships.map(x => x.room ?? x.roomId);
+
+ const [packedUsers, packedRooms] = await Promise.all([
+ this.userEntityService.packMany(users, me)
+ .then(users => new Map(users.map(u => [u.id, u]))),
+ this.packRooms(rooms, me)
+ .then(rooms => new Map(rooms.map(r => [r.id, r]))),
+ ]);
+
+ return Promise.all(memberships.map(membership => this.packRoomMembership(membership, me, { ...options, _hint_: { packedUsers, packedRooms } })));
+ }
+}
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index 3f3a1bad33..83e28ebfc9 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -10,8 +10,7 @@ import type { Packed } from '@/misc/json-schema.js';
import type { MiMeta } from '@/models/Meta.js';
import type { AdsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { InstanceActorService } from '@/core/InstanceActorService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
@@ -28,8 +27,7 @@ export class MetaEntityService {
@Inject(DI.adsRepository)
private adsRepository: AdsRepository,
- private userEntityService: UserEntityService,
- private instanceActorService: InstanceActorService,
+ private systemAccountService: SystemAccountService,
) { }
@bindThis
@@ -102,6 +100,7 @@ export class MetaEntityService {
enableFC: instance.enableFC,
fcSiteKey: instance.fcSiteKey,
enableTestcaptcha: instance.enableTestcaptcha,
+ googleAnalyticsMeasurementId: instance.googleAnalyticsMeasurementId,
swPublickey: instance.swPublicKey,
themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
@@ -161,14 +160,14 @@ export class MetaEntityService {
const packed = await this.pack(instance);
- const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null;
+ const proxyAccount = await this.systemAccountService.fetch('proxy');
const packDetailed: Packed<'MetaDetailed'> = {
...packed,
cacheRemoteFiles: instance.cacheRemoteFiles,
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
- requireSetup: !await this.instanceActorService.realLocalUsersPresent(),
- proxyAccountName: proxyAccount ? proxyAccount.username : null,
+ requireSetup: this.meta.rootUserId == null,
+ proxyAccountName: proxyAccount.username,
features: {
localTimeline: instance.policies.ltlAvailable,
globalTimeline: instance.policies.gtlAvailable,
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index f5452baaef..cfcf10a0f4 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -30,10 +30,10 @@ import type {
FollowingsRepository,
FollowRequestsRepository,
MiFollowing,
+ MiMeta,
MiUserNotePining,
MiUserProfile,
MutingsRepository,
- NoteUnreadsRepository,
RenoteMutingsRepository,
UserMemoRepository,
UserNotePiningsRepository,
@@ -49,10 +49,10 @@ import { IdService } from '@/core/IdService.js';
import type { AnnouncementService } from '@/core/AnnouncementService.js';
import type { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
+import { ChatService } from '@/core/ChatService.js';
import { isSystemAccount } from '@/misc/is-system-account.js';
import type { OnModuleInit } from '@nestjs/common';
import type { NoteEntityService } from './NoteEntityService.js';
-import type { DriveFileEntityService } from './DriveFileEntityService.js';
import type { PageEntityService } from './PageEntityService.js';
/* eslint-disable @typescript-eslint/no-non-null-assertion */
@@ -62,12 +62,14 @@ const ajv = new Ajv();
function isLocalUser(user: MiUser): user is MiLocalUser;
function isLocalUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: null; });
+
function isLocalUser(user: MiUser | { host: MiUser['host'] }): boolean {
return user.host == null;
}
function isRemoteUser(user: MiUser): user is MiRemoteUser;
function isRemoteUser<T extends { host: MiUser['host'] }>(user: T): user is (T & { host: string; });
+
function isRemoteUser(user: MiUser | { host: MiUser['host'] }): boolean {
return !isLocalUser(user);
}
@@ -85,7 +87,7 @@ export type UserRelation = {
isRenoteMuted: boolean
isInstanceMuted?: boolean
memo?: string | null
-}
+};
@Injectable()
export class UserEntityService implements OnModuleInit {
@@ -99,6 +101,7 @@ export class UserEntityService implements OnModuleInit {
private federatedInstanceService: FederatedInstanceService;
private idService: IdService;
private avatarDecorationService: AvatarDecorationService;
+ private chatService: ChatService;
constructor(
private moduleRef: ModuleRef,
@@ -106,6 +109,9 @@ export class UserEntityService implements OnModuleInit {
@Inject(DI.config)
private config: Config,
+ @Inject(DI.meta)
+ private meta: MiMeta,
+
@Inject(DI.redis)
private redisClient: Redis.Redis,
@@ -133,9 +139,6 @@ export class UserEntityService implements OnModuleInit {
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
- @Inject(DI.noteUnreadsRepository)
- private noteUnreadsRepository: NoteUnreadsRepository,
-
@Inject(DI.userNotePiningsRepository)
private userNotePiningsRepository: UserNotePiningsRepository,
@@ -158,6 +161,7 @@ export class UserEntityService implements OnModuleInit {
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
this.idService = this.moduleRef.get('IdService');
this.avatarDecorationService = this.moduleRef.get('AvatarDecorationService');
+ this.chatService = this.moduleRef.get('ChatService');
}
//#region Validators
@@ -447,7 +451,11 @@ export class UserEntityService implements OnModuleInit {
@bindThis
public getIdenticonUrl(user: MiUser): string {
- return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
+ if ((user.host == null || user.host === this.config.host) && user.username.includes('.') && this.meta.iconUrl) { // ローカルのシステムアカウントの場合
+ return this.meta.iconUrl;
+ } else {
+ return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
+ }
}
@bindThis
@@ -655,6 +663,7 @@ 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,
+ chatScope: user.chatScope,
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,
@@ -698,14 +707,9 @@ export class UserEntityService implements OnModuleInit {
isDeleted: user.isDeleted,
twoFactorBackupCodesStock: profile?.twoFactorBackupSecret?.length === 5 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none',
hideOnlineStatus: user.hideOnlineStatus,
- hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({
- where: { userId: user.id, isSpecified: true },
- take: 1,
- }).then(count => count > 0),
- hasUnreadMentions: this.noteUnreadsRepository.count({
- where: { userId: user.id, isMentioned: true },
- take: 1,
- }).then(count => count > 0),
+ hasUnreadSpecifiedNotes: false, // 後方互換性のため
+ hasUnreadMentions: false, // 後方互換性のため
+ hasUnreadChatMessages: this.chatService.hasUnreadMessages(user.id),
hasUnreadAnnouncement: unreadAnnouncements!.length > 0,
unreadAnnouncements,
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index 461fcf11c3..099d48c81a 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -29,7 +29,6 @@ export const DI = {
noteFavoritesRepository: Symbol('noteFavoritesRepository'),
noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'),
noteReactionsRepository: Symbol('noteReactionsRepository'),
- noteUnreadsRepository: Symbol('noteUnreadsRepository'),
pollsRepository: Symbol('pollsRepository'),
pollVotesRepository: Symbol('pollVotesRepository'),
userProfilesRepository: Symbol('userProfilesRepository'),
@@ -79,6 +78,7 @@ export const DI = {
registryItemsRepository: Symbol('registryItemsRepository'),
webhooksRepository: Symbol('webhooksRepository'),
systemWebhooksRepository: Symbol('systemWebhooksRepository'),
+ systemAccountsRepository: Symbol('systemAccountsRepository'),
adsRepository: Symbol('adsRepository'),
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
@@ -87,6 +87,11 @@ export const DI = {
flashsRepository: Symbol('flashsRepository'),
flashLikesRepository: Symbol('flashLikesRepository'),
userMemosRepository: Symbol('userMemosRepository'),
+ chatMessagesRepository: Symbol('chatMessagesRepository'),
+ chatApprovalsRepository: Symbol('chatApprovalsRepository'),
+ chatRoomsRepository: Symbol('chatRoomsRepository'),
+ chatRoomMembershipsRepository: Symbol('chatRoomMembershipsRepository'),
+ chatRoomInvitationsRepository: Symbol('chatRoomInvitationsRepository'),
noteEditRepository: Symbol('noteEditRepository'),
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
reversiGamesRepository: Symbol('reversiGamesRepository'),
diff --git a/packages/backend/src/misc/FileWriterStream.ts b/packages/backend/src/misc/FileWriterStream.ts
index 367a8eb560..27c67cb5df 100644
--- a/packages/backend/src/misc/FileWriterStream.ts
+++ b/packages/backend/src/misc/FileWriterStream.ts
@@ -4,6 +4,7 @@
*/
import * as fs from 'node:fs/promises';
+import { WritableStream } from 'node:stream/web';
import type { PathLike } from 'node:fs';
/**
diff --git a/packages/backend/src/misc/is-native-token.ts b/packages/backend/src/misc/is-native-token.ts
deleted file mode 100644
index 300c4c05b3..0000000000
--- a/packages/backend/src/misc/is-native-token.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-// eslint-disable-next-line import/no-default-export
-export default (token: string) => token.length === 16;
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index f612591eda..bc9308ca9b 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -63,6 +63,10 @@ import {
} from '@/models/json-schema/meta.js';
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
+import { packedChatMessageSchema, packedChatMessageLiteSchema } from '@/models/json-schema/chat-message.js';
+import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js';
+import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js';
+import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js';
export const refs = {
UserLite: packedUserLiteSchema,
@@ -120,6 +124,11 @@ export const refs = {
MetaDetailed: packedMetaDetailedSchema,
SystemWebhook: packedSystemWebhookSchema,
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
+ ChatMessage: packedChatMessageSchema,
+ ChatMessageLite: packedChatMessageLiteSchema,
+ ChatRoom: packedChatRoomSchema,
+ ChatRoomInvitation: packedChatRoomInvitationSchema,
+ ChatRoomMembership: packedChatRoomMembershipSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
@@ -143,7 +152,7 @@ type OfSchema = {
readonly anyOf?: ReadonlyArray<Schema>;
readonly oneOf?: ReadonlyArray<Schema>;
readonly allOf?: ReadonlyArray<Schema>;
-}
+};
export interface Schema extends OfSchema {
readonly type?: TypeStringef;
@@ -166,15 +175,16 @@ export interface Schema extends OfSchema {
readonly maximum?: number;
readonly minimum?: number;
readonly pattern?: string;
+ readonly additionalProperties?: Schema | boolean;
}
type RequiredPropertyNames<s extends Obj> = {
[K in keyof s]:
- // K is not optional
- s[K]['optional'] extends false ? K :
- // K has default value
- s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K :
- never
+ // K is not optional
+ s[K]['optional'] extends false ? K :
+ // K has default value
+ s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K :
+ never
}[keyof s];
export type Obj = Record<string, Schema>;
@@ -213,11 +223,18 @@ type ObjectSchemaTypeDef<p extends Schema> =
p['anyOf'] extends ReadonlyArray<Schema> ? p['anyOf'][number]['required'] extends ReadonlyArray<keyof p['properties']> ?
UnionObjType<p['properties'], NonNullable<p['anyOf'][number]['required']>> & ObjType<p['properties'], NonNullable<p['required']>>
: never
- : ObjType<p['properties'], NonNullable<p['required']>>
- :
- p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
- p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
- any
+ : ObjType<p['properties'], NonNullable<p['required']>>
+ :
+ p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
+ p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
+ p['additionalProperties'] extends true ? Record<string, any> :
+ p['additionalProperties'] extends Schema ?
+ p['additionalProperties'] extends infer AdditionalProperties ?
+ AdditionalProperties extends Schema ?
+ Record<string, SchemaType<AdditionalProperties>> :
+ never :
+ never :
+ any;
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>;
@@ -227,30 +244,30 @@ export type SchemaTypeDef<p extends Schema> =
p['type'] extends 'number' ? number :
p['type'] extends 'string' ? (
p['enum'] extends readonly (string | null)[] ?
- p['enum'][number] :
- p['format'] extends 'date-time' ? string : // Dateにする??
- string
+ p['enum'][number] :
+ p['format'] extends 'date-time' ? string : // Dateにする??
+ string
) :
- p['type'] extends 'boolean' ? boolean :
- p['type'] extends 'object' ? ObjectSchemaTypeDef<p> :
- p['type'] extends 'array' ? (
- p['items'] extends OfSchema ? (
- p['items']['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] :
- p['items']['oneOf'] extends ReadonlyArray<Schema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> :
- p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
- never
- ) :
- p['prefixItems'] extends ReadonlyArray<Schema> ? (
- p['items'] extends NonNullable<Schema> ? [...ArrayToTuple<p['prefixItems']>, ...SchemaType<p['items']>[]] :
- p['items'] extends false ? ArrayToTuple<p['prefixItems']> :
- p['unevaluatedItems'] extends false ? ArrayToTuple<p['prefixItems']> :
- [...ArrayToTuple<p['prefixItems']>, ...unknown[]]
+ p['type'] extends 'boolean' ? boolean :
+ p['type'] extends 'object' ? ObjectSchemaTypeDef<p> :
+ p['type'] extends 'array' ? (
+ p['items'] extends OfSchema ? (
+ p['items']['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] :
+ p['items']['oneOf'] extends ReadonlyArray<Schema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> :
+ p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
+ never
+ ) :
+ p['prefixItems'] extends ReadonlyArray<Schema> ? (
+ p['items'] extends NonNullable<Schema> ? [...ArrayToTuple<p['prefixItems']>, ...SchemaType<p['items']>[]] :
+ p['items'] extends false ? ArrayToTuple<p['prefixItems']> :
+ p['unevaluatedItems'] extends false ? ArrayToTuple<p['prefixItems']> :
+ [...ArrayToTuple<p['prefixItems']>, ...unknown[]]
+ ) :
+ p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] :
+ any[]
) :
- p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] :
- any[]
- ) :
- p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & PartialIntersection<UnionSchemaType<p['anyOf']>> :
- p['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['oneOf']> :
- any;
+ p['anyOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['anyOf']> & PartialIntersection<UnionSchemaType<p['anyOf']>> :
+ p['oneOf'] extends ReadonlyArray<Schema> ? UnionSchemaType<p['oneOf']> :
+ any;
export type SchemaType<p extends Schema> = NullOrUndefined<p, SchemaTypeDef<p>>;
diff --git a/packages/backend/src/misc/json-value.ts b/packages/backend/src/misc/json-value.ts
index bd7fe12058..195f7c4d47 100644
--- a/packages/backend/src/misc/json-value.ts
+++ b/packages/backend/src/misc/json-value.ts
@@ -4,7 +4,7 @@
*/
export type JsonValue = JsonArray | JsonObject | string | number | boolean | null;
-export type JsonObject = {[K in string]?: JsonValue};
+export type JsonObject = { [K in string]?: JsonValue };
export type JsonArray = JsonValue[];
export function isJsonObject(value: JsonValue | undefined): value is JsonObject {
diff --git a/packages/backend/src/misc/generate-native-user-token.ts b/packages/backend/src/misc/token.ts
index 85fb383ba2..5d37cba26d 100644
--- a/packages/backend/src/misc/generate-native-user-token.ts
+++ b/packages/backend/src/misc/token.ts
@@ -5,5 +5,6 @@
import { secureRndstr } from '@/misc/secure-rndstr.js';
-// eslint-disable-next-line import/no-default-export
-export default () => secureRndstr(16);
+export const generateNativeUserToken = () => secureRndstr(16);
+
+export const isNativeUserToken = (token: string) => token.length === 16;
diff --git a/packages/backend/src/models/ChatApproval.ts b/packages/backend/src/models/ChatApproval.ts
new file mode 100644
index 0000000000..55c9f07e9a
--- /dev/null
+++ b/packages/backend/src/models/ChatApproval.ts
@@ -0,0 +1,39 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+
+@Entity('chat_approval')
+@Index(['userId', 'otherId'], { unique: true })
+export class MiChatApproval {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Index()
+ @Column({
+ ...id(),
+ })
+ public userId: MiUser['id'];
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public user: MiUser | null;
+
+ @Index()
+ @Column({
+ ...id(),
+ })
+ public otherId: MiUser['id'];
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public other: MiUser | null;
+}
diff --git a/packages/backend/src/models/ChatMessage.ts b/packages/backend/src/models/ChatMessage.ts
new file mode 100644
index 0000000000..3d2b64268e
--- /dev/null
+++ b/packages/backend/src/models/ChatMessage.ts
@@ -0,0 +1,85 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+import { MiDriveFile } from './DriveFile.js';
+import { MiChatRoom } from './ChatRoom.js';
+
+@Entity('chat_message')
+export class MiChatMessage {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Index()
+ @Column({
+ ...id(),
+ })
+ public fromUserId: MiUser['id'];
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public fromUser: MiUser | null;
+
+ @Index()
+ @Column({
+ ...id(), nullable: true,
+ })
+ public toUserId: MiUser['id'] | null;
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public toUser: MiUser | null;
+
+ @Index()
+ @Column({
+ ...id(), nullable: true,
+ })
+ public toRoomId: MiChatRoom['id'] | null;
+
+ @ManyToOne(type => MiChatRoom, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public toRoom: MiChatRoom | null;
+
+ @Column('varchar', {
+ length: 4096, nullable: true,
+ })
+ public text: string | null;
+
+ @Column('varchar', {
+ length: 512, nullable: true,
+ })
+ public uri: string | null;
+
+ @Column({
+ ...id(),
+ array: true, default: '{}',
+ })
+ public reads: MiUser['id'][];
+
+ @Column({
+ ...id(),
+ nullable: true,
+ })
+ public fileId: MiDriveFile['id'] | null;
+
+ @ManyToOne(type => MiDriveFile, {
+ onDelete: 'SET NULL',
+ })
+ @JoinColumn()
+ public file: MiDriveFile | null;
+
+ @Column('varchar', {
+ length: 1024, array: true, default: '{}',
+ })
+ public reactions: string[];
+}
diff --git a/packages/backend/src/models/ChatRoom.ts b/packages/backend/src/models/ChatRoom.ts
new file mode 100644
index 0000000000..ad2a910b78
--- /dev/null
+++ b/packages/backend/src/models/ChatRoom.ts
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+
+@Entity('chat_room')
+export class MiChatRoom {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Column('varchar', {
+ length: 256,
+ })
+ public name: string;
+
+ @Index()
+ @Column({
+ ...id(),
+ })
+ public ownerId: MiUser['id'];
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public owner: MiUser | null;
+
+ @Column('varchar', {
+ length: 2048, default: '',
+ })
+ public description: string;
+
+ @Column('boolean', {
+ default: false,
+ })
+ public isArchived: boolean;
+}
diff --git a/packages/backend/src/models/ChatRoomInvitation.ts b/packages/backend/src/models/ChatRoomInvitation.ts
new file mode 100644
index 0000000000..36ce12bc92
--- /dev/null
+++ b/packages/backend/src/models/ChatRoomInvitation.ts
@@ -0,0 +1,45 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+import { MiChatRoom } from './ChatRoom.js';
+
+@Entity('chat_room_invitation')
+@Index(['userId', 'roomId'], { unique: true })
+export class MiChatRoomInvitation {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Index()
+ @Column({
+ ...id(),
+ })
+ public userId: MiUser['id'];
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public user: MiUser | null;
+
+ @Index()
+ @Column({
+ ...id(),
+ })
+ public roomId: MiChatRoom['id'];
+
+ @ManyToOne(type => MiChatRoom, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public room: MiChatRoom | null;
+
+ @Column('boolean', {
+ default: false,
+ })
+ public ignored: boolean;
+}
diff --git a/packages/backend/src/models/ChatRoomMembership.ts b/packages/backend/src/models/ChatRoomMembership.ts
new file mode 100644
index 0000000000..3cb5524859
--- /dev/null
+++ b/packages/backend/src/models/ChatRoomMembership.ts
@@ -0,0 +1,45 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+import { MiChatRoom } from './ChatRoom.js';
+
+@Entity('chat_room_membership')
+@Index(['userId', 'roomId'], { unique: true })
+export class MiChatRoomMembership {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Index()
+ @Column({
+ ...id(),
+ })
+ public userId: MiUser['id'];
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public user: MiUser | null;
+
+ @Index()
+ @Column({
+ ...id(),
+ })
+ public roomId: MiChatRoom['id'];
+
+ @ManyToOne(type => MiChatRoom, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public room: MiChatRoom | null;
+
+ @Column('boolean', {
+ default: false,
+ })
+ public isMuted: boolean;
+}
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index f4bc2a8db7..8c3f8a9317 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
+import { Entity, Column, PrimaryColumn, ManyToOne } from 'typeorm';
import { type InstanceUnsignedFetchOption, instanceUnsignedFetchOptions } from '@/const.js';
import { id } from './util/id.js';
import { MiUser } from './User.js';
@@ -16,6 +16,18 @@ export class MiMeta {
})
public id: string;
+ @Column({
+ ...id(),
+ nullable: true,
+ })
+ public rootUserId: MiUser['id'] | null;
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'SET NULL',
+ nullable: true,
+ })
+ public rootUser: MiUser | null;
+
@Column('varchar', {
length: 1024, nullable: true,
})
@@ -179,18 +191,6 @@ export class MiMeta {
})
public cacheRemoteSensitiveFiles: boolean;
- @Column({
- ...id(),
- nullable: true,
- })
- public proxyAccountId: MiUser['id'] | null;
-
- @ManyToOne(type => MiUser, {
- onDelete: 'SET NULL',
- })
- @JoinColumn()
- public proxyAccount: MiUser | null;
-
@Column('boolean', {
default: false,
})
@@ -753,11 +753,16 @@ export class MiMeta {
/**
* In combination with user.allowUnsignedFetch, controls enforcement of HTTP signatures for inbound ActivityPub fetches (GET requests).
- * TODO warning if config value is present
*/
@Column('enum', {
enum: instanceUnsignedFetchOptions,
default: 'always',
})
public allowUnsignedFetch: InstanceUnsignedFetchOption;
+
+ @Column('varchar', {
+ length: 64,
+ nullable: true,
+ })
+ public googleAnalyticsMeasurementId: string | null;
}
diff --git a/packages/backend/src/models/NoteUnread.ts b/packages/backend/src/models/NoteUnread.ts
deleted file mode 100644
index c759181117..0000000000
--- a/packages/backend/src/models/NoteUnread.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
-import { id } from './util/id.js';
-import { MiUser } from './User.js';
-import { MiNote } from './Note.js';
-import type { MiChannel } from './Channel.js';
-
-@Entity('note_unread')
-@Index(['userId', 'noteId'], { unique: true })
-export class MiNoteUnread {
- @PrimaryColumn(id())
- public id: string;
-
- @Index()
- @Column(id())
- public userId: MiUser['id'];
-
- @ManyToOne(type => MiUser, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public user: MiUser | null;
-
- @Index()
- @Column(id())
- public noteId: MiNote['id'];
-
- @ManyToOne(type => MiNote, {
- onDelete: 'CASCADE',
- })
- @JoinColumn()
- public note: MiNote | null;
-
- /**
- * メンションか否か
- */
- @Index()
- @Column('boolean')
- public isMentioned: boolean;
-
- /**
- * ダイレクト投稿か否か
- */
- @Index()
- @Column('boolean')
- public isSpecified: boolean;
-
- //#region Denormalized fields
- @Index()
- @Column({
- ...id(),
- comment: '[Denormalized]',
- })
- public noteUserId: MiUser['id'];
-
- @Index()
- @Column({
- ...id(),
- nullable: true,
- comment: '[Denormalized]',
- })
- public noteChannelId: MiChannel['id'] | null;
- //#endregion
-}
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index 6d7a453879..46f88b9f22 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -91,6 +91,10 @@ export type MiNotification = {
id: string;
createdAt: string;
} | {
+ type: 'createToken';
+ id: string;
+ createdAt: string;
+} | {
type: 'app';
id: string;
createdAt: string;
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index 78510ba588..5e0154fe50 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { Provider } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import {
@@ -45,7 +44,6 @@ import {
MiNoteReaction,
MiNoteSchedule,
MiNoteThreadMuting,
- MiNoteUnread,
MiPage,
MiPageLike,
MiPasswordResetRequest,
@@ -65,6 +63,7 @@ import {
MiRoleAssignment,
MiSignin,
MiSwSubscription,
+ MiSystemAccount,
MiSystemWebhook,
MiUsedUsername,
MiUser,
@@ -80,11 +79,17 @@ import {
MiUserPublickey,
MiUserSecurityKey,
MiWebhook,
+ MiChatMessage,
+ MiChatRoom,
+ MiChatRoomMembership,
+ MiChatRoomInvitation,
+ MiChatApproval,
NoteEdit,
SkApContext,
SkApFetchLog,
SkApInboxLog,
} from './_.js';
+import type { Provider } from '@nestjs/common';
import type { DataSource } from 'typeorm';
const $usersRepository: Provider = {
@@ -165,12 +170,6 @@ const $noteReactionsRepository: Provider = {
inject: [DI.db],
};
-const $noteUnreadsRepository: Provider = {
- provide: DI.noteUnreadsRepository,
- useFactory: (db: DataSource) => db.getRepository(MiNoteUnread).extend(miRepository as MiRepository<MiNoteUnread>),
- inject: [DI.db],
-};
-
const $pollsRepository: Provider = {
provide: DI.pollsRepository,
useFactory: (db: DataSource) => db.getRepository(MiPoll).extend(miRepository as MiRepository<MiPoll>),
@@ -315,6 +314,12 @@ const $swSubscriptionsRepository: Provider = {
inject: [DI.db],
};
+const $systemAccountsRepository: Provider = {
+ provide: DI.systemAccountsRepository,
+ useFactory: (db: DataSource) => db.getRepository(MiSystemAccount).extend(miRepository as MiRepository<MiSystemAccount>),
+ inject: [DI.db],
+};
+
const $hashtagsRepository: Provider = {
provide: DI.hashtagsRepository,
useFactory: (db: DataSource) => db.getRepository(MiHashtag).extend(miRepository as MiRepository<MiHashtag>),
@@ -329,7 +334,7 @@ const $abuseUserReportsRepository: Provider = {
const $abuseReportNotificationRecipientRepository: Provider = {
provide: DI.abuseReportNotificationRecipientRepository,
- useFactory: (db: DataSource) => db.getRepository(MiAbuseReportNotificationRecipient),
+ useFactory: (db: DataSource) => db.getRepository(MiAbuseReportNotificationRecipient).extend(miRepository as MiRepository<MiAbuseReportNotificationRecipient>),
inject: [DI.db],
};
@@ -461,7 +466,7 @@ const $webhooksRepository: Provider = {
const $systemWebhooksRepository: Provider = {
provide: DI.systemWebhooksRepository,
- useFactory: (db: DataSource) => db.getRepository(MiSystemWebhook),
+ useFactory: (db: DataSource) => db.getRepository(MiSystemWebhook).extend(miRepository as MiRepository<MiSystemWebhook>),
inject: [DI.db],
};
@@ -519,6 +524,36 @@ const $noteEditRepository: Provider = {
inject: [DI.db],
};
+const $chatMessagesRepository: Provider = {
+ provide: DI.chatMessagesRepository,
+ useFactory: (db: DataSource) => db.getRepository(MiChatMessage).extend(miRepository as MiRepository<MiChatMessage>),
+ inject: [DI.db],
+};
+
+const $chatRoomsRepository: Provider = {
+ provide: DI.chatRoomsRepository,
+ useFactory: (db: DataSource) => db.getRepository(MiChatRoom).extend(miRepository as MiRepository<MiChatRoom>),
+ inject: [DI.db],
+};
+
+const $chatRoomMembershipsRepository: Provider = {
+ provide: DI.chatRoomMembershipsRepository,
+ useFactory: (db: DataSource) => db.getRepository(MiChatRoomMembership).extend(miRepository as MiRepository<MiChatRoomMembership>),
+ inject: [DI.db],
+};
+
+const $chatRoomInvitationsRepository: Provider = {
+ provide: DI.chatRoomInvitationsRepository,
+ useFactory: (db: DataSource) => db.getRepository(MiChatRoomInvitation).extend(miRepository as MiRepository<MiChatRoomInvitation>),
+ inject: [DI.db],
+};
+
+const $chatApprovalsRepository: Provider = {
+ provide: DI.chatApprovalsRepository,
+ useFactory: (db: DataSource) => db.getRepository(MiChatApproval).extend(miRepository as MiRepository<MiChatApproval>),
+ inject: [DI.db],
+};
+
const $bubbleGameRecordsRepository: Provider = {
provide: DI.bubbleGameRecordsRepository,
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord).extend(miRepository as MiRepository<MiBubbleGameRecord>),
@@ -553,7 +588,6 @@ const $noteScheduleRepository: Provider = {
$noteFavoritesRepository,
$noteThreadMutingsRepository,
$noteReactionsRepository,
- $noteUnreadsRepository,
$pollsRepository,
$pollVotesRepository,
$userProfilesRepository,
@@ -578,6 +612,7 @@ const $noteScheduleRepository: Provider = {
$renoteMutingsRepository,
$blockingsRepository,
$swSubscriptionsRepository,
+ $systemAccountsRepository,
$hashtagsRepository,
$abuseUserReportsRepository,
$abuseReportNotificationRecipientRepository,
@@ -611,6 +646,11 @@ const $noteScheduleRepository: Provider = {
$flashsRepository,
$flashLikesRepository,
$userMemosRepository,
+ $chatMessagesRepository,
+ $chatRoomsRepository,
+ $chatRoomMembershipsRepository,
+ $chatRoomInvitationsRepository,
+ $chatApprovalsRepository,
$noteEditRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
@@ -630,7 +670,6 @@ const $noteScheduleRepository: Provider = {
$noteFavoritesRepository,
$noteThreadMutingsRepository,
$noteReactionsRepository,
- $noteUnreadsRepository,
$pollsRepository,
$pollVotesRepository,
$userProfilesRepository,
@@ -655,6 +694,7 @@ const $noteScheduleRepository: Provider = {
$renoteMutingsRepository,
$blockingsRepository,
$swSubscriptionsRepository,
+ $systemAccountsRepository,
$hashtagsRepository,
$abuseUserReportsRepository,
$abuseReportNotificationRecipientRepository,
@@ -688,6 +728,11 @@ const $noteScheduleRepository: Provider = {
$flashsRepository,
$flashLikesRepository,
$userMemosRepository,
+ $chatMessagesRepository,
+ $chatRoomsRepository,
+ $chatRoomMembershipsRepository,
+ $chatRoomInvitationsRepository,
+ $chatApprovalsRepository,
$noteEditRepository,
$bubbleGameRecordsRepository,
$reversiGamesRepository,
diff --git a/packages/backend/src/models/SystemAccount.ts b/packages/backend/src/models/SystemAccount.ts
new file mode 100644
index 0000000000..f32880b81d
--- /dev/null
+++ b/packages/backend/src/models/SystemAccount.ts
@@ -0,0 +1,31 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
+import { Serialized } from '@/types.js';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+
+@Entity('system_account')
+@Index(['type'], { unique: true })
+export class MiSystemAccount {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Index()
+ @Column(id())
+ public userId: MiUser['id'];
+
+ @ManyToOne(type => MiUser, {
+ onDelete: 'CASCADE',
+ })
+ @JoinColumn()
+ public user: MiUser | null;
+
+ @Column('varchar', {
+ length: 256,
+ })
+ public type: string;
+}
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index 3bc2494577..0678746e26 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -227,12 +227,6 @@ export class MiUser {
})
public speakAsCat: boolean;
- @Column('boolean', {
- default: false,
- comment: 'Whether the User is the root.',
- })
- public isRoot: boolean;
-
@Index()
@Column('boolean', {
default: true,
@@ -274,6 +268,17 @@ export class MiUser {
})
public emojis: string[];
+ // チャットを許可する相手
+ // everyone: 誰からでも
+ // followers: フォロワーのみ
+ // following: フォローしているユーザーのみ
+ // mutual: 相互フォローのみ
+ // none: 誰からも受け付けない
+ @Column('varchar', {
+ length: 128, default: 'mutual',
+ })
+ public chatScope: 'everyone' | 'followers' | 'following' | 'mutual' | 'none';
+
@Index()
@Column('varchar', {
length: 128, nullable: true,
@@ -379,24 +384,24 @@ export class MiUser {
export type MiLocalUser = MiUser & {
host: null;
uri: null;
-}
+};
export type MiPartialLocalUser = Partial<MiUser> & {
id: MiUser['id'];
host: null;
uri: null;
-}
+};
export type MiRemoteUser = MiUser & {
host: string;
uri: string;
-}
+};
export type MiPartialRemoteUser = Partial<MiUser> & {
id: MiUser['id'];
host: string;
uri: string;
-}
+};
export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
export const passwordSchema = { type: 'string', minLength: 1 } as const;
diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts
index 4bd6e78ef4..0ec619f38d 100644
--- a/packages/backend/src/models/_.ts
+++ b/packages/backend/src/models/_.ts
@@ -3,13 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, Repository, SelectQueryBuilder, TypeORMError } from 'typeorm';
-import { DriverUtils } from 'typeorm/driver/DriverUtils.js';
+import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, Repository, SelectQueryBuilder } from 'typeorm';
import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js';
import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js';
import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
-import { ObjectUtils } from 'typeorm/util/ObjectUtils.js';
-import { OrmUtils } from 'typeorm/util/OrmUtils.js';
import { SkLatestNote } from '@/models/LatestNote.js';
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
@@ -44,7 +41,6 @@ import { MiNote } from '@/models/Note.js';
import { MiNoteFavorite } from '@/models/NoteFavorite.js';
import { MiNoteReaction } from '@/models/NoteReaction.js';
import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js';
-import { MiNoteUnread } from '@/models/NoteUnread.js';
import { MiPage } from '@/models/Page.js';
import { MiPageLike } from '@/models/PageLike.js';
import { MiPasswordResetRequest } from '@/models/PasswordResetRequest.js';
@@ -57,6 +53,7 @@ import { MiRegistryItem } from '@/models/RegistryItem.js';
import { MiRelay } from '@/models/Relay.js';
import { MiSignin } from '@/models/Signin.js';
import { MiSwSubscription } from '@/models/SwSubscription.js';
+import { MiSystemAccount } from '@/models/SystemAccount.js';
import { MiUsedUsername } from '@/models/UsedUsername.js';
import { MiUser } from '@/models/User.js';
import { MiUserIp } from '@/models/UserIp.js';
@@ -79,6 +76,11 @@ import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
import { NoteEdit } from '@/models/NoteEdit.js';
+import { MiChatMessage } from '@/models/ChatMessage.js';
+import { MiChatRoom } from '@/models/ChatRoom.js';
+import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
+import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
+import { MiChatApproval } from '@/models/ChatApproval.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
import { MiNoteSchedule } from '@/models/NoteSchedule.js';
@@ -169,7 +171,6 @@ export {
MiNoteReaction,
MiNoteSchedule,
MiNoteThreadMuting,
- MiNoteUnread,
MiPage,
MiPageLike,
MiPasswordResetRequest,
@@ -182,6 +183,7 @@ export {
MiRelay,
MiSignin,
MiSwSubscription,
+ MiSystemAccount,
MiUsedUsername,
MiUser,
MiUserIp,
@@ -203,9 +205,14 @@ export {
MiFlash,
MiFlashLike,
MiUserMemo,
- NoteEdit,
+ MiChatMessage,
+ MiChatRoom,
+ MiChatRoomMembership,
+ MiChatRoomInvitation,
+ MiChatApproval,
MiBubbleGameRecord,
MiReversiGame,
+ NoteEdit,
};
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport> & MiRepository<MiAbuseUserReport>;
@@ -245,7 +252,6 @@ export type NotesRepository = Repository<MiNote> & MiRepository<MiNote>;
export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<MiNoteFavorite>;
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
-export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
export type PageLikesRepository = Repository<MiPageLike> & MiRepository<MiPageLike>;
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest> & MiRepository<MiPasswordResetRequest>;
@@ -258,6 +264,7 @@ export type RegistryItemsRepository = Repository<MiRegistryItem> & MiRepository<
export type RelaysRepository = Repository<MiRelay> & MiRepository<MiRelay>;
export type SigninsRepository = Repository<MiSignin> & MiRepository<MiSignin>;
export type SwSubscriptionsRepository = Repository<MiSwSubscription> & MiRepository<MiSwSubscription>;
+export type SystemAccountsRepository = Repository<MiSystemAccount> & MiRepository<MiSystemAccount>;
export type UsedUsernamesRepository = Repository<MiUsedUsername> & MiRepository<MiUsedUsername>;
export type UsersRepository = Repository<MiUser> & MiRepository<MiUser>;
export type UserIpsRepository = Repository<MiUserIp> & MiRepository<MiUserIp>;
@@ -279,6 +286,11 @@ export type RoleAssignmentsRepository = Repository<MiRoleAssignment> & MiReposit
export type FlashsRepository = Repository<MiFlash> & MiRepository<MiFlash>;
export type FlashLikesRepository = Repository<MiFlashLike> & MiRepository<MiFlashLike>;
export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMemo>;
+export type ChatMessagesRepository = Repository<MiChatMessage> & MiRepository<MiChatMessage>;
+export type ChatRoomsRepository = Repository<MiChatRoom> & MiRepository<MiChatRoom>;
+export type ChatRoomMembershipsRepository = Repository<MiChatRoomMembership> & MiRepository<MiChatRoomMembership>;
+export type ChatRoomInvitationsRepository = Repository<MiChatRoomInvitation> & MiRepository<MiChatRoomInvitation>;
+export type ChatApprovalsRepository = Repository<MiChatApproval> & MiRepository<MiChatApproval>;
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
export type NoteEditRepository = Repository<NoteEdit> & MiRepository<NoteEdit>;
diff --git a/packages/backend/src/models/json-schema/chat-message.ts b/packages/backend/src/models/json-schema/chat-message.ts
new file mode 100644
index 0000000000..44b7298702
--- /dev/null
+++ b/packages/backend/src/models/json-schema/chat-message.ts
@@ -0,0 +1,146 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedChatMessageSchema = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ createdAt: {
+ type: 'string',
+ format: 'date-time',
+ optional: false, nullable: false,
+ },
+ fromUserId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ fromUser: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'UserLite',
+ },
+ toUserId: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ toUser: {
+ type: 'object',
+ optional: true, nullable: true,
+ ref: 'UserLite',
+ },
+ toRoomId: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ toRoom: {
+ type: 'object',
+ optional: true, nullable: true,
+ ref: 'ChatRoom',
+ },
+ text: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ fileId: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ file: {
+ type: 'object',
+ optional: true, nullable: true,
+ ref: 'DriveFile',
+ },
+ isRead: {
+ type: 'boolean',
+ optional: true, nullable: false,
+ },
+ reactions: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ reaction: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ user: {
+ type: 'object',
+ optional: true, nullable: true,
+ ref: 'UserLite',
+ },
+ },
+ },
+ },
+ },
+} as const;
+
+export const packedChatMessageLiteSchema = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ createdAt: {
+ type: 'string',
+ format: 'date-time',
+ optional: false, nullable: false,
+ },
+ fromUserId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ fromUser: {
+ type: 'object',
+ optional: true, nullable: false,
+ ref: 'UserLite',
+ },
+ toUserId: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ toRoomId: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ text: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ fileId: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ file: {
+ type: 'object',
+ optional: true, nullable: true,
+ ref: 'DriveFile',
+ },
+ reactions: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ reaction: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ user: {
+ type: 'object',
+ optional: true, nullable: true,
+ ref: 'UserLite',
+ },
+ },
+ },
+ },
+ },
+} as const;
diff --git a/packages/backend/src/models/json-schema/chat-room-invitation.ts b/packages/backend/src/models/json-schema/chat-room-invitation.ts
new file mode 100644
index 0000000000..204c959b2c
--- /dev/null
+++ b/packages/backend/src/models/json-schema/chat-room-invitation.ts
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedChatRoomInvitationSchema = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ createdAt: {
+ type: 'string',
+ format: 'date-time',
+ optional: false, nullable: false,
+ },
+ userId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ user: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'UserLite',
+ },
+ roomId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ room: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoom',
+ },
+ },
+} as const;
diff --git a/packages/backend/src/models/json-schema/chat-room-membership.ts b/packages/backend/src/models/json-schema/chat-room-membership.ts
new file mode 100644
index 0000000000..adb73f9dde
--- /dev/null
+++ b/packages/backend/src/models/json-schema/chat-room-membership.ts
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedChatRoomMembershipSchema = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ createdAt: {
+ type: 'string',
+ format: 'date-time',
+ optional: false, nullable: false,
+ },
+ userId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ user: {
+ type: 'object',
+ optional: true, nullable: false,
+ ref: 'UserLite',
+ },
+ roomId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ room: {
+ type: 'object',
+ optional: true, nullable: false,
+ ref: 'ChatRoom',
+ },
+ },
+} as const;
diff --git a/packages/backend/src/models/json-schema/chat-room.ts b/packages/backend/src/models/json-schema/chat-room.ts
new file mode 100644
index 0000000000..e97556e378
--- /dev/null
+++ b/packages/backend/src/models/json-schema/chat-room.ts
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedChatRoomSchema = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ createdAt: {
+ type: 'string',
+ format: 'date-time',
+ optional: false, nullable: false,
+ },
+ ownerId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ owner: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'UserLite',
+ },
+ name: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ description: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ isMuted: {
+ type: 'boolean',
+ optional: true, nullable: false,
+ },
+ },
+} as const;
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index fd735c1edd..a8d9fe3e4e 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -149,6 +149,10 @@ export const packedMetaLiteSchema = {
type: 'boolean',
optional: false, nullable: false,
},
+ googleAnalyticsMeasurementId: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
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 248234a674..bf33dab575 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -339,6 +339,16 @@ export const packedNotificationSchema = {
type: {
type: 'string',
optional: false, nullable: false,
+ enum: ['createToken'],
+ },
+ },
+ }, {
+ type: 'object',
+ properties: {
+ ...baseSchema.properties,
+ type: {
+ type: 'string',
+ optional: false, nullable: false,
enum: ['app'],
},
body: {
diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts
index ef0bb9f141..2537351dc0 100644
--- a/packages/backend/src/models/json-schema/role.ts
+++ b/packages/backend/src/models/json-schema/role.ts
@@ -300,6 +300,10 @@ export const packedRolePoliciesSchema = {
type: 'integer',
optional: false, nullable: false,
},
+ canChat: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
},
} as const;
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 83a456fc57..e7160aa40d 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -418,6 +418,11 @@ export const packedUserDetailedNotMeOnlySchema = {
nullable: false, optional: false,
enum: ['public', 'followers', 'private'],
},
+ chatScope: {
+ type: 'string',
+ nullable: false, optional: false,
+ enum: ['everyone', 'following', 'followers', 'mutual', 'none'],
+ },
roles: {
type: 'array',
nullable: false, optional: false,
@@ -609,6 +614,10 @@ export const packedMeDetailedOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
+ hasUnreadChatMessages: {
+ type: 'boolean',
+ nullable: false, optional: false,
+ },
hasUnreadNotification: {
type: 'boolean',
nullable: false, optional: false,
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 1a5fdc8412..ffc2daa62c 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -8,6 +8,9 @@ import pg from 'pg';
import { DataSource, Logger } from 'typeorm';
import * as highlight from 'cli-highlight';
import { entities as charts } from '@/core/chart/entities.js';
+import { Config } from '@/config.js';
+import MisskeyLogger from '@/logger.js';
+import { bindThis } from '@/decorators.js';
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
@@ -42,7 +45,6 @@ import { MiNote } from '@/models/Note.js';
import { MiNoteFavorite } from '@/models/NoteFavorite.js';
import { MiNoteReaction } from '@/models/NoteReaction.js';
import { MiNoteThreadMuting } from '@/models/NoteThreadMuting.js';
-import { MiNoteUnread } from '@/models/NoteUnread.js';
import { MiPage } from '@/models/Page.js';
import { MiPageLike } from '@/models/PageLike.js';
import { MiPasswordResetRequest } from '@/models/PasswordResetRequest.js';
@@ -76,14 +78,16 @@ import { MiRoleAssignment } from '@/models/RoleAssignment.js';
import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiUserMemo } from '@/models/UserMemo.js';
-import { NoteEdit } from '@/models/NoteEdit.js';
+import { MiChatMessage } from '@/models/ChatMessage.js';
+import { MiChatRoom } from '@/models/ChatRoom.js';
+import { MiChatRoomMembership } from '@/models/ChatRoomMembership.js';
+import { MiChatRoomInvitation } from '@/models/ChatRoomInvitation.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
+import { MiChatApproval } from '@/models/ChatApproval.js';
+import { MiSystemAccount } from '@/models/SystemAccount.js';
+import { NoteEdit } from '@/models/NoteEdit.js';
import { MiNoteSchedule } from '@/models/NoteSchedule.js';
-
-import { Config } from '@/config.js';
-import MisskeyLogger from '@/logger.js';
-import { bindThis } from '@/decorators.js';
import { SkLatestNote } from '@/models/LatestNote.js';
import { SkApContext } from '@/models/SkApContext.js';
import { SkApFetchLog } from '@/models/SkApFetchLog.js';
@@ -98,7 +102,7 @@ const sqlLogger = dbLogger.createSubLogger('sql', 'gray');
export type LoggerProps = {
disableQueryTruncation?: boolean;
enableQueryParamLogging?: boolean;
-}
+};
function highlightSql(sql: string) {
return highlight.highlight(sql, {
@@ -205,7 +209,6 @@ export const entities = [
MiNoteReaction,
MiNoteSchedule,
MiNoteThreadMuting,
- MiNoteUnread,
MiPage,
MiPageLike,
MiGalleryPost,
@@ -217,6 +220,7 @@ export const entities = [
MiEmoji,
MiHashtag,
MiSwSubscription,
+ MiSystemAccount,
MiAbuseUserReport,
MiAbuseReportNotificationRecipient,
MiRegistrationTicket,
@@ -245,9 +249,14 @@ export const entities = [
MiFlash,
MiFlashLike,
MiUserMemo,
- NoteEdit,
+ MiChatMessage,
+ MiChatRoom,
+ MiChatRoomMembership,
+ MiChatRoomInvitation,
+ MiChatApproval,
MiBubbleGameRecord,
MiReversiGame,
+ NoteEdit,
...charts,
];
diff --git a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
index ef21b6142e..db8d2e789e 100644
--- a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
+++ b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -29,7 +29,7 @@ export type ModeratorInactivityEvaluationResult = {
isModeratorsInactive: boolean;
inactiveModerators: MiUser[];
remainingTime: ModeratorInactivityRemainingTime;
-}
+};
export type ModeratorInactivityRemainingTime = {
time: number;
diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
index 383fa0c26a..d08cadd378 100644
--- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
@@ -6,6 +6,7 @@
import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common';
import { ZipReader } from 'slacc';
+import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
@@ -91,6 +92,7 @@ export class ImportCustomEmojisProcessorService {
const emojiPath = outputPath + '/' + record.fileName;
await this.emojisRepository.delete({
name: nameNfc,
+ host: IsNull(),
});
try {
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index fc7c66591a..50630e4061 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -151,12 +151,12 @@ export class InboxProcessorService implements OnApplicationShutdown {
// それでもわからなければ終了
if (authUser == null) {
- throw new Bull.UnrecoverableError('skip: failed to resolve user');
+ throw new Bull.UnrecoverableError(`skip: failed to resolve user ${getApId(activity.actor)}`);
}
// publicKey がなくても終了
if (authUser.key == null) {
- throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey');
+ throw new Bull.UnrecoverableError(`skip: failed to resolve user publicKey ${getApId(activity.actor)}`);
}
// HTTP-Signatureの検証
diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts
index a900675a86..7d4d609f46 100644
--- a/packages/backend/src/queue/types.ts
+++ b/packages/backend/src/queue/types.ts
@@ -38,7 +38,7 @@ export type RelationshipJobData = {
silent?: boolean;
requestId?: string;
withReplies?: boolean;
-}
+};
export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T];
@@ -69,11 +69,11 @@ export type DbJobMap = {
importUserLists: DbUserImportJobData;
importCustomEmojis: DbUserImportJobData;
deleteAccount: DbUserDeleteJobData;
-}
+};
export type DbJobDataWithUser = {
user: ThinUser;
-}
+};
export type DbExportFollowingData = {
user: ThinUser;
@@ -83,7 +83,7 @@ export type DbExportFollowingData = {
export type DBExportAntennasData = {
user: ThinUser
-}
+};
export type DbUserDeleteJobData = {
user: ThinUser;
@@ -105,7 +105,7 @@ export type DbNoteImportJobData = {
export type DBAntennaImportJobData = {
user: ThinUser,
antenna: Antenna
-}
+};
export type DbUserImportToDbJobData = {
user: ThinUser;
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index ea534af458..c7aa694964 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -252,6 +252,11 @@ export class ActivityPubServerService {
@bindThis
private inbox(request: FastifyRequest, reply: FastifyReply) {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
let signature;
try {
@@ -323,6 +328,11 @@ export class ActivityPubServerService {
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
reply: FastifyReply,
) {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const { reject } = await this.checkAuthorizedFetch(request, reply, request.params.user);
if (reject) return;
@@ -415,6 +425,11 @@ export class ActivityPubServerService {
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
reply: FastifyReply,
) {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const { reject } = await this.checkAuthorizedFetch(request, reply, request.params.user);
if (reject) return;
@@ -504,6 +519,11 @@ export class ActivityPubServerService {
@bindThis
private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const { reject } = await this.checkAuthorizedFetch(request, reply, request.params.user);
if (reject) return;
@@ -550,6 +570,11 @@ export class ActivityPubServerService {
}>,
reply: FastifyReply,
) {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const { reject } = await this.checkAuthorizedFetch(request, reply, request.params.user);
if (reject) return;
@@ -636,6 +661,11 @@ export class ActivityPubServerService {
@bindThis
private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null, redact = false) {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
if (user == null) {
reply.code(404);
return;
@@ -728,6 +758,11 @@ export class ActivityPubServerService {
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept');
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const note = await this.notesRepository.findOneBy({
id: request.params.note,
visibility: In(['public', 'home']),
@@ -762,6 +797,11 @@ export class ActivityPubServerService {
fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => {
vary(reply.raw, 'Accept');
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const note = await this.notesRepository.findOneBy({
id: request.params.note,
userHost: IsNull(),
@@ -852,6 +892,11 @@ export class ActivityPubServerService {
// publickey
fastify.get<{ Params: { user: string; } }>('/users/:user/publickey', async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const { reject } = await this.checkAuthorizedFetch(request, reply, request.params.user, true);
if (reject) return;
@@ -884,6 +929,11 @@ export class ActivityPubServerService {
vary(reply.raw, 'Accept');
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const userId = request.params.user;
const user = await this.usersRepository.findOneBy({
@@ -897,6 +947,11 @@ export class ActivityPubServerService {
fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept');
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const acct = Acct.parse(request.params.acct);
const user = await this.usersRepository.findOneBy({
@@ -914,6 +969,11 @@ export class ActivityPubServerService {
// emoji
fastify.get<{ Params: { emoji: string; } }>('/emojis/:emoji', async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const { reject } = await this.checkAuthorizedFetch(request, reply);
if (reject) return;
@@ -933,6 +993,11 @@ export class ActivityPubServerService {
// like
fastify.get<{ Params: { like: string; } }>('/likes/:like', async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const reaction = await this.noteReactionsRepository.findOneBy({ id: request.params.like });
const { reject } = await this.checkAuthorizedFetch(request, reply, reaction?.userId);
@@ -956,6 +1021,11 @@ export class ActivityPubServerService {
// follow
fastify.get<{ Params: { follower: string; followee: string; } }>('/follows/:follower/:followee', async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const { reject } = await this.checkAuthorizedFetch(request, reply, request.params.follower);
if (reject) return;
@@ -983,7 +1053,12 @@ export class ActivityPubServerService {
});
// follow
- fastify.get<{ Params: { followRequestId: string ; } }>('/follows/:followRequestId', async (request, reply) => {
+ fastify.get<{ Params: { followRequestId: string; } }>('/follows/:followRequestId', async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
// This may be used before the follow is completed, so we do not
// check if the following exists and only check if the follow request exists.
diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts
index a7e13a1b78..b8a72fedb9 100644
--- a/packages/backend/src/server/FileServerService.ts
+++ b/packages/backend/src/server/FileServerService.ts
@@ -519,7 +519,7 @@ export class FileServerService {
@bindThis
private async downloadAndDetectTypeFromUrl(url: string): Promise<
- { state: 'remote' ; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; }
+ { state: 'remote'; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; }
> {
const [path, cleanup] = await createTemp();
try {
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index 6dee6ecd78..55e8827696 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -8,11 +8,11 @@ import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { MemorySingleCache } from '@/misc/cache.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import NotesChart from '@/core/chart/charts/notes.js';
import UsersChart from '@/core/chart/charts/users.js';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
const nodeinfo2_1path = '/nodeinfo/2.1';
@@ -25,7 +25,7 @@ export class NodeinfoServerService {
@Inject(DI.config)
private config: Config,
- private userEntityService: UserEntityService,
+ private systemAccountService: SystemAccountService,
private metaService: MetaService,
private notesChart: NotesChart,
private usersChart: UsersChart,
@@ -69,7 +69,7 @@ export class NodeinfoServerService {
const activeHalfyear = null;
const activeMonth = null;
- const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null;
+ const proxyAccount = await this.systemAccountService.fetch('proxy');
const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
@@ -130,7 +130,7 @@ export class NodeinfoServerService {
maxRemoteAltTextLength: this.config.maxRemoteAltTextLength,
enableEmail: meta.enableEmail,
enableServiceWorker: meta.enableServiceWorker,
- proxyAccountName: proxyAccount ? proxyAccount.username : null,
+ proxyAccountName: proxyAccount.username,
themeColor: meta.themeColor ?? '#86b300',
},
};
diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts
index 2c067afe88..15d3e33ec8 100644
--- a/packages/backend/src/server/ServerModule.ts
+++ b/packages/backend/src/server/ServerModule.ts
@@ -50,6 +50,8 @@ import { ServerStatsChannelService } from './api/stream/channels/server-stats.js
import { UserListChannelService } from './api/stream/channels/user-list.js';
import { MastodonApiServerService } from './api/mastodon/MastodonApiServerService.js';
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
+import { ChatUserChannelService } from './api/stream/channels/chat-user.js';
+import { ChatRoomChannelService } from './api/stream/channels/chat-room.js';
import { ReversiChannelService } from './api/stream/channels/reversi.js';
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
@@ -93,6 +95,8 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
BubbleTimelineChannelService,
HashtagChannelService,
RoleTimelineChannelService,
+ ChatUserChannelService,
+ ChatRoomChannelService,
ReversiChannelService,
ReversiGameChannelService,
HomeTimelineChannelService,
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 690fdcfe29..9ae8f2efe4 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -105,6 +105,43 @@ export class ServerService implements OnApplicationShutdown {
serve: false,
});
+ // if the requester looks like to be performing an ActivityPub object lookup, reject all external redirects
+ //
+ // this will break lookup that involve copying a URL from a third-party server, like trying to lookup http://charlie.example.com/@alice@alice.com
+ //
+ // this is not required by standard but protect us from peers that did not validate final URL.
+ if (this.config.disallowExternalApRedirect) {
+ const maybeApLookupRegex = /application\/activity\+json|application\/ld\+json.+activitystreams/i;
+ fastify.addHook('onSend', (request, reply, _, done) => {
+ const location = reply.getHeader('location');
+ if (reply.statusCode < 300 || reply.statusCode >= 400 || typeof location !== 'string') {
+ done();
+ return;
+ }
+
+ if (!maybeApLookupRegex.test(request.headers.accept ?? '')) {
+ done();
+ return;
+ }
+
+ const effectiveLocation = process.env.NODE_ENV === 'production' ? location : location.replace(/^http:\/\//, 'https://');
+ if (effectiveLocation.startsWith(`https://${this.config.host}/`)) {
+ done();
+ return;
+ }
+
+ reply.status(406);
+ reply.removeHeader('location');
+ reply.header('content-type', 'text/plain; charset=utf-8');
+ reply.header('link', `<${encodeURI(location)}>; rel="canonical"`);
+ done(null, [
+ "Refusing to relay remote ActivityPub object lookup.",
+ "",
+ `Please remove 'application/activity+json' and 'application/ld+json' from the Accept header or fetch using the authoritative URL at ${location}.`,
+ ].join('\n'));
+ });
+ }
+
fastify.register(this.apiServerService.createServer, { prefix: '/api' });
fastify.register(this.openApiServerService.createServer);
fastify.register(this.mastodonApiServerService.createServer, { prefix: '/api' });
diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts
index 70f672e5d9..25fa725320 100644
--- a/packages/backend/src/server/WellKnownServerService.ts
+++ b/packages/backend/src/server/WellKnownServerService.ts
@@ -8,7 +8,7 @@ import { IsNull } from 'typeorm';
import vary from 'vary';
import fastifyAccepts from '@fastify/accepts';
import { DI } from '@/di-symbols.js';
-import type { UsersRepository } from '@/models/_.js';
+import type { MiMeta, UsersRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js';
import type { MiUser } from '@/models/User.js';
@@ -26,6 +26,9 @@ export class WellKnownServerService {
@Inject(DI.config)
private config: Config,
+ @Inject(DI.meta)
+ private meta: MiMeta,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -66,6 +69,11 @@ export class WellKnownServerService {
});
fastify.get('/.well-known/host-meta', async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
reply.header('Content-Type', xrd);
return XRD({ element: 'Link', attributes: {
rel: 'lrdd',
@@ -75,6 +83,11 @@ export class WellKnownServerService {
});
fastify.get('/.well-known/host-meta.json', async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
reply.header('Content-Type', 'application/json');
return {
links: [{
@@ -86,6 +99,11 @@ export class WellKnownServerService {
});
fastify.get('/.well-known/nodeinfo', async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
return { links: this.nodeinfoServerService.getLinks() };
});
@@ -99,6 +117,11 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
*/
fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => {
+ if (this.meta.federation === 'none') {
+ reply.code(403);
+ return;
+ }
+
const fromId = (id: MiUser['id']): FindOptionsWhere<MiUser> => ({
id,
host: IsNull(),
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index 5ce358d68f..b22a8c1837 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -369,7 +369,7 @@ export class ApiCallService implements OnApplicationShutdown {
}
}
- if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
+ if ((ep.meta.requireModerator || ep.meta.requireAdmin) && (this.meta.rootUserId !== user!.id)) {
const myRoles = await this.roleService.getUserRoles(user!.id);
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
throw new ApiError({
@@ -389,10 +389,10 @@ export class ApiCallService implements OnApplicationShutdown {
}
}
- if (ep.meta.requireRolePolicy != null && !user!.isRoot) {
+ if (ep.meta.requiredRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
const myRoles = await this.roleService.getUserRoles(user!.id);
const policies = await this.roleService.getUserPolicies(user!.id);
- if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
+ if (!policies[ep.meta.requiredRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
throw new ApiError({
message: 'You are not assigned to a required role.',
code: 'ROLE_PERMISSION_DENIED',
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 0d77309537..12459d5698 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -6,7 +6,6 @@
import { Inject, Injectable } from '@nestjs/common';
import cors from '@fastify/cors';
import multipart from '@fastify/multipart';
-import fastifyCookie from '@fastify/cookie';
import { ModuleRef } from '@nestjs/core';
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { Config } from '@/config.js';
@@ -57,8 +56,6 @@ export class ApiServerService {
},
});
- fastify.register(fastifyCookie, {});
-
// Prevent cache
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index 690ff2e022..601618553e 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -11,7 +11,7 @@ import type { MiAccessToken } from '@/models/AccessToken.js';
import { MemoryKVCache } from '@/misc/cache.js';
import type { MiApp } from '@/models/App.js';
import { CacheService } from '@/core/CacheService.js';
-import isNativeToken from '@/misc/is-native-token.js';
+import { isNativeUserToken } from '@/misc/token.js';
import { bindThis } from '@/decorators.js';
export class AuthenticationError extends Error {
@@ -46,7 +46,7 @@ export class AuthenticateService implements OnApplicationShutdown {
return [null, null];
}
- if (isNativeToken(token)) {
+ if (isNativeUserToken(token)) {
const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
() => this.usersRepository.findOneBy({ token }) as Promise<MiLocalUser | null>);
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index 0954744f81..9e6b1d8761 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -11,7 +11,6 @@ import proxyAddr from 'proxy-addr';
import ms from 'ms';
import { DI } from '@/di-symbols.js';
import type { UsersRepository, MiAccessToken } from '@/models/_.js';
-import { NoteReadService } from '@/core/NoteReadService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { CacheService } from '@/core/CacheService.js';
@@ -42,7 +41,6 @@ export class StreamingApiServerService {
private usersRepository: UsersRepository,
private cacheService: CacheService,
- private noteReadService: NoteReadService,
private authenticateService: AuthenticateService,
private channelsService: ChannelsService,
private notificationService: NotificationService,
@@ -154,7 +152,6 @@ export class StreamingApiServerService {
const stream = new MainStreamConnection(
this.channelsService,
- this.noteReadService,
this.notificationService,
this.cacheService,
this.channelFollowingService,
diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts
index a641a14448..562f498cbd 100644
--- a/packages/backend/src/server/api/endpoint-list.ts
+++ b/packages/backend/src/server/api/endpoint-list.ts
@@ -109,6 +109,7 @@ export * as 'admin/unsilence-user' from './endpoints/admin/unsilence-user.js';
export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js';
export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js';
export * as 'admin/update-meta' from './endpoints/admin/update-meta.js';
+export * as 'admin/update-proxy-account' from './endpoints/admin/update-proxy-account.js';
export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js';
export * as 'announcements' from './endpoints/announcements.js';
export * as 'announcements/show' from './endpoints/announcements/show.js';
@@ -273,7 +274,6 @@ export * as 'i/notifications-grouped' from './endpoints/i/notifications-grouped.
export * as 'i/page-likes' from './endpoints/i/page-likes.js';
export * as 'i/pages' from './endpoints/i/pages.js';
export * as 'i/pin' from './endpoints/i/pin.js';
-export * as 'i/read-all-unread-notes' from './endpoints/i/read-all-unread-notes.js';
export * as 'i/read-announcement' from './endpoints/i/read-announcement.js';
export * as 'i/regenerate-token' from './endpoints/i/regenerate-token.js';
export * as 'i/registry/get' from './endpoints/i/registry/get.js';
@@ -418,4 +418,26 @@ export * as 'users/search' from './endpoints/users/search.js';
export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js';
export * as 'users/show' from './endpoints/users/show.js';
export * as 'users/update-memo' from './endpoints/users/update-memo.js';
+export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js';
+export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js';
+export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js';
+export * as 'chat/messages/show' from './endpoints/chat/messages/show.js';
+export * as 'chat/messages/react' from './endpoints/chat/messages/react.js';
+export * as 'chat/messages/user-timeline' from './endpoints/chat/messages/user-timeline.js';
+export * as 'chat/messages/room-timeline' from './endpoints/chat/messages/room-timeline.js';
+export * as 'chat/messages/search' from './endpoints/chat/messages/search.js';
+export * as 'chat/rooms/create' from './endpoints/chat/rooms/create.js';
+export * as 'chat/rooms/delete' from './endpoints/chat/rooms/delete.js';
+export * as 'chat/rooms/join' from './endpoints/chat/rooms/join.js';
+export * as 'chat/rooms/leave' from './endpoints/chat/rooms/leave.js';
+export * as 'chat/rooms/mute' from './endpoints/chat/rooms/mute.js';
+export * as 'chat/rooms/show' from './endpoints/chat/rooms/show.js';
+export * as 'chat/rooms/owned' from './endpoints/chat/rooms/owned.js';
+export * as 'chat/rooms/joining' from './endpoints/chat/rooms/joining.js';
+export * as 'chat/rooms/update' from './endpoints/chat/rooms/update.js';
+export * as 'chat/rooms/members' from './endpoints/chat/rooms/members.js';
+export * as 'chat/rooms/invitations/create' from './endpoints/chat/rooms/invitations/create.js';
+export * as 'chat/rooms/invitations/ignore' from './endpoints/chat/rooms/invitations/ignore.js';
+export * as 'chat/rooms/invitations/inbox' from './endpoints/chat/rooms/invitations/inbox.js';
+export * as 'chat/history' from './endpoints/chat/history.js';
export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index fd6b9bb14b..0ba041c536 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -40,7 +40,7 @@ interface IEndpointMetaBase {
*/
readonly requireAdmin?: boolean;
- readonly requireRolePolicy?: KeyOf<'RolePolicies'>;
+ readonly requiredRolePolicy?: KeyOf<'RolePolicies'>;
/**
* 引っ越し済みのユーザーによるリクエストを禁止するか
@@ -100,7 +100,7 @@ export type IEndpointMeta = (Omit<IEndpointMetaBase, 'requireCrential' | 'requir
}) | (Omit<IEndpointMetaBase, 'requireAdmin' | 'kind'> & {
requireAdmin: true,
kind: (typeof permissions)[number],
-})
+});
export interface IEndpoint {
name: string;
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 1a47f56bc6..88490800cf 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -5,10 +5,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository } from '@/models/_.js';
+import type { MiMeta, UsersRepository } 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';
@@ -90,20 +89,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.config)
private config: Config,
+ @Inject(DI.meta)
+ private serverSettings: MiMeta,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private roleService: RoleService,
private userEntityService: UserEntityService,
private signupService: SignupService,
- private instanceActorService: InstanceActorService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, _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.serverSettings.rootUserId == null && me == null && token == null) {
// 初回セットアップの場合
if (this.config.setupPassword != null) {
// 初期パスワードが設定されている場合
@@ -127,7 +127,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
// Anonymous access is only allowed for initial instance setup (this check may be redundant)
- if (!me && realUsers) {
+ if (!me && this.serverSettings.rootUserId != null) {
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 ece1984cff..d04f52dd64 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
@@ -42,10 +42,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('user not found');
}
- if (user.isRoot) {
- throw new Error('cannot delete a root account');
- }
-
await this.deleteAccoountService.deleteAccount(user, me);
});
}
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 87d80cbe80..0121c302ac 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
@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageAvatarDecorations',
+ requiredRolePolicy: 'canManageAvatarDecorations',
kind: 'write:admin:avatar-decorations',
res: {
diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts
index 3a5673d99d..13660d0b8c 100644
--- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts
@@ -13,7 +13,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageAvatarDecorations',
+ requiredRolePolicy: 'canManageAvatarDecorations',
kind: 'write:admin:avatar-decorations',
errors: {
},
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 d785f085ac..d4d9a7235b 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
@@ -13,7 +13,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageAvatarDecorations',
+ requiredRolePolicy: 'canManageAvatarDecorations',
kind: 'read:admin:avatar-decorations',
res: {
diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts
index 34b3b5a11f..22476a6888 100644
--- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts
@@ -13,7 +13,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageAvatarDecorations',
+ requiredRolePolicy: 'canManageAvatarDecorations',
kind: 'write:admin:avatar-decorations',
errors: {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
index 795b579041..56db393996 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index 1c5316a002..5ef8307df0 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -16,7 +16,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
errors: {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
index 07ffa0b1c7..cbf78ada3e 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -17,7 +17,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
errors: {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
index cec9f700c3..7993edcc07 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
index 50c45b6ac5..87ed3f5f18 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
errors: {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
index ee7706f31a..7b544bee8d 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
@@ -13,7 +13,7 @@ import { DI } from '@/di-symbols.js';
export const meta = {
secure: true,
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
} as const;
export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
index 1182918ea2..7982c1f0bd 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
@@ -16,7 +16,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'read:admin:emoji',
res: {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
index f35a6667f4..b1b8e63d2f 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -16,7 +16,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'read:admin:emoji',
res: {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
index 066eb1c7d9..2d8867b9fd 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
index 8980ef0c86..8086af8ed5 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
index 2510349210..5d3b39d7da 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
index a0205ae24a..4b916508a7 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
} as const;
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 fd6db9c4ab..492122422c 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -14,7 +14,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji',
errors: {
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index d3f24e07bb..d05f6e3c02 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -10,6 +10,7 @@ import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { instanceUnsignedFetchOptions } from '@/const.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
export const meta = {
tags: ['meta'],
@@ -86,6 +87,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ googleAnalyticsMeasurementId: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
swPublickey: {
type: 'string',
optional: false, nullable: true,
@@ -265,7 +270,7 @@ export const meta = {
},
proxyAccountId: {
type: 'string',
- optional: false, nullable: true,
+ optional: false, nullable: false,
format: 'id',
},
email: {
@@ -580,6 +585,7 @@ export const meta = {
},
federation: {
type: 'string',
+ enum: ['all', 'specified', 'none'],
optional: false, nullable: false,
},
federationHosts: {
@@ -617,10 +623,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private config: Config,
private metaService: MetaService,
+ private systemAccountService: SystemAccountService,
) {
super(meta, paramDef, async () => {
const instance = await this.metaService.fetch(true);
+ const proxy = await this.systemAccountService.fetch('proxy');
+
return {
maintainerName: instance.maintainerName,
maintainerEmail: instance.maintainerEmail,
@@ -652,6 +661,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
enableFC: instance.enableFC,
fcSiteKey: instance.fcSiteKey,
enableTestcaptcha: instance.enableTestcaptcha,
+ googleAnalyticsMeasurementId: instance.googleAnalyticsMeasurementId,
swPublickey: instance.swPublicKey,
themeColor: instance.themeColor,
mascotImageUrl: instance.mascotImageUrl,
@@ -693,7 +703,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
enableBotTrending: instance.enableBotTrending,
- proxyAccountId: instance.proxyAccountId,
+ proxyAccountId: proxy.id,
email: instance.email,
smtpSecure: instance.smtpSecure,
smtpHost: instance.smtpHost,
diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
index e4bb545f5d..b99f420928 100644
--- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
//import bcrypt from 'bcryptjs';
import * as argon2 from 'argon2';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
+import type { UsersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@@ -45,6 +45,9 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
+ @Inject(DI.meta)
+ private serverSettings: MiMeta,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -60,7 +63,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('user not found');
}
- if (user.isRoot) {
+ if (this.serverSettings.rootUserId === user.id) {
throw new Error('cannot reset password of root');
}
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 33d4bbd00f..86dbd44e20 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -91,12 +91,12 @@ export const paramDef = {
fcSiteKey: { type: 'string', nullable: true },
fcSecretKey: { type: 'string', nullable: true },
enableTestcaptcha: { type: 'boolean' },
+ googleAnalyticsMeasurementId: { type: 'string', nullable: true },
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
setSensitiveFlagAutomatically: { type: 'boolean' },
enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
enableBotTrending: { type: 'boolean' },
- proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
maintainerName: { type: 'string', nullable: true },
maintainerEmail: { type: 'string', nullable: true },
langs: {
@@ -130,7 +130,7 @@ export const paramDef = {
useObjectStorage: { type: 'boolean' },
objectStorageBaseUrl: { type: 'string', nullable: true },
objectStorageBucket: { type: 'string', nullable: true },
- objectStoragePrefix: { type: 'string', nullable: true },
+ objectStoragePrefix: { type: 'string', pattern: /^[a-zA-Z0-9-._]*$/.source, nullable: true },
objectStorageEndpoint: { type: 'string', nullable: true },
objectStorageRegion: { type: 'string', nullable: true },
objectStoragePort: { type: 'integer', nullable: true },
@@ -413,6 +413,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.enableTestcaptcha = ps.enableTestcaptcha;
}
+ if (ps.googleAnalyticsMeasurementId !== undefined) {
+ // 空文字列をnullにしたいので??は使わない
+ set.googleAnalyticsMeasurementId = ps.googleAnalyticsMeasurementId || null;
+ }
+
if (ps.fcSiteKey !== undefined) {
set.fcSiteKey = ps.fcSiteKey;
}
@@ -425,10 +430,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.enableBotTrending = ps.enableBotTrending;
}
- if (ps.proxyAccountId !== undefined) {
- set.proxyAccountId = ps.proxyAccountId;
- }
-
if (ps.maintainerName !== undefined) {
set.maintainerName = ps.maintainerName;
}
diff --git a/packages/backend/src/server/api/endpoints/admin/update-proxy-account.ts b/packages/backend/src/server/api/endpoints/admin/update-proxy-account.ts
new file mode 100644
index 0000000000..6c9612c71a
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/update-proxy-account.ts
@@ -0,0 +1,62 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import {
+ descriptionSchema,
+} from '@/models/User.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+ kind: 'write:admin:account',
+
+ res: {
+ type: 'object',
+ nullable: false, optional: false,
+ ref: 'UserDetailed',
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ description: { ...descriptionSchema, nullable: true },
+ },
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private userEntityService: UserEntityService,
+ private moderationLogService: ModerationLogService,
+ private systemAccountService: SystemAccountService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const proxy = await this.systemAccountService.updateCorrespondingUserProfile('proxy', {
+ description: ps.description,
+ });
+
+ const updated = await this.userEntityService.pack(proxy.id, proxy, {
+ schema: 'MeDetailed',
+ });
+
+ if (ps.description !== undefined) {
+ this.moderationLogService.log(me, 'updateProxyAccountDescription', {
+ before: null, //TODO
+ after: ps.description,
+ });
+ }
+
+ return updated;
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index be4cf1e0ca..8ad2315209 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -8,7 +8,6 @@ import * as Redis from 'ioredis';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { NotesRepository, AntennasRepository } from '@/models/_.js';
import { QueryService } from '@/core/QueryService.js';
-import { NoteReadService } from '@/core/NoteReadService.js';
import { DI } from '@/di-symbols.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { IdService } from '@/core/IdService.js';
@@ -65,9 +64,6 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.redisForTimelines)
- private redisForTimelines: Redis.Redis,
-
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@@ -77,7 +73,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private idService: IdService,
private noteEntityService: NoteEntityService,
private queryService: QueryService,
- private noteReadService: NoteReadService,
private fanoutTimelineService: FanoutTimelineService,
private globalEventService: GlobalEventService,
) {
@@ -130,8 +125,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
notes.sort((a, b) => a.id > b.id ? -1 : 1);
}
- this.noteReadService.read(me.id, notes);
-
return await this.noteEntityService.packMany(notes, me);
});
}
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index 22bec8ef95..8cf7747d5f 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -21,6 +21,7 @@ import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
import { ApiError } from '../../error.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
export const meta = {
tags: ['federation'],
@@ -55,11 +56,6 @@ export const meta = {
code: 'RESPONSE_INVALID',
id: '70193c39-54f3-4813-82f0-70a680f7495b',
},
- responseInvalidIdHostNotMatch: {
- message: 'Requested URI and response URI host does not match.',
- code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH',
- id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a',
- },
noSuchObject: {
message: 'No such object.',
code: 'NO_SUCH_OBJECT',
@@ -165,7 +161,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// リモートから一旦オブジェクトフェッチ
const resolver = this.apResolverService.createResolver();
- const object = await resolver.resolve(uri).catch((err) => {
+ // allow ap/show exclusively to lookup URLs that are cross-origin or non-canonical (like https://alice.example.com/@bob@bob.example.com -> https://bob.example.com/@bob)
+ const object = await resolver.resolve(uri, FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId).catch((err) => {
if (err instanceof IdentifiableError) {
switch (err.id) {
// resolve
@@ -177,10 +174,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
throw new ApiError(meta.errors.federationNotAllowed);
case '72180409-793c-4973-868e-5a118eb5519b':
- case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
throw new ApiError(meta.errors.responseInvalid);
- case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
- throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
// resolveLocal
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
diff --git a/packages/backend/src/server/api/endpoints/chat/history.ts b/packages/backend/src/server/api/endpoints/chat/history.ts
new file mode 100644
index 0000000000..7553a751e0
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/history.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatMessage',
+ },
+ },
+
+ errors: {
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ room: { type: 'boolean', default: false },
+ },
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatEntityService: ChatEntityService,
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const history = ps.room ? await this.chatService.roomHistory(me.id, ps.limit) : await this.chatService.userHistory(me.id, ps.limit);
+
+ const packedMessages = await this.chatEntityService.packMessagesDetailed(history, me);
+
+ if (ps.room) {
+ const roomIds = history.map(m => m.toRoomId!);
+ const readStateMap = await this.chatService.getRoomReadStateMap(me.id, roomIds);
+
+ for (const message of packedMessages) {
+ message.isRead = readStateMap[message.toRoomId!] ?? false;
+ }
+ } else {
+ const otherIds = history.map(m => m.fromUserId === me.id ? m.toUserId! : m.fromUserId!);
+ const readStateMap = await this.chatService.getUserReadStateMap(me.id, otherIds);
+
+ for (const message of packedMessages) {
+ const otherId = message.fromUserId === me.id ? message.toUserId! : message.fromUserId!;
+ message.isRead = readStateMap[otherId] ?? false;
+ }
+ }
+
+ return packedMessages;
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts b/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts
new file mode 100644
index 0000000000..1f334d5750
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts
@@ -0,0 +1,105 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '@/server/api/error.js';
+import { ChatService } from '@/core/ChatService.js';
+import type { DriveFilesRepository, MiUser } from '@/models/_.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+ requiredRolePolicy: 'canChat',
+
+ prohibitMoved: true,
+
+ kind: 'write:chat',
+
+ limit: {
+ duration: ms('1hour'),
+ max: 500,
+ },
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatMessageLite',
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: '8098520d-2da5-4e8f-8ee1-df78b55a4ec6',
+ },
+
+ noSuchFile: {
+ message: 'No such file.',
+ code: 'NO_SUCH_FILE',
+ id: 'b6accbd3-1d7b-4d9f-bdb7-eb185bac06db',
+ },
+
+ contentRequired: {
+ message: 'Content required. You need to set text or fileId.',
+ code: 'CONTENT_REQUIRED',
+ id: '340517b7-6d04-42c0-bac1-37ee804e3594',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ text: { type: 'string', nullable: true, maxLength: 2000 },
+ fileId: { type: 'string', format: 'misskey:id' },
+ toRoomId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['toRoomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
+
+ private getterService: GetterService,
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const room = await this.chatService.findRoomById(ps.toRoomId);
+ if (room == null) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+
+ let file = null;
+ if (ps.fileId != null) {
+ file = await this.driveFilesRepository.findOneBy({
+ id: ps.fileId,
+ userId: me.id,
+ });
+
+ if (file == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ }
+
+ // テキストが無いかつ添付ファイルも無かったらエラー
+ if (ps.text == null && file == null) {
+ throw new ApiError(meta.errors.contentRequired);
+ }
+
+ return await this.chatService.createMessageToRoom(me, room, {
+ text: ps.text,
+ file: file,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts b/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts
new file mode 100644
index 0000000000..6b77a026fb
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts
@@ -0,0 +1,122 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '@/server/api/error.js';
+import { ChatService } from '@/core/ChatService.js';
+import type { DriveFilesRepository, MiUser } from '@/models/_.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+ requiredRolePolicy: 'canChat',
+
+ prohibitMoved: true,
+
+ kind: 'write:chat',
+
+ limit: {
+ duration: ms('1hour'),
+ max: 500,
+ },
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatMessageLite',
+ },
+
+ errors: {
+ recipientIsYourself: {
+ message: 'You can not send a message to yourself.',
+ code: 'RECIPIENT_IS_YOURSELF',
+ id: '17e2ba79-e22a-4cbc-bf91-d327643f4a7e',
+ },
+
+ noSuchUser: {
+ message: 'No such user.',
+ code: 'NO_SUCH_USER',
+ id: '11795c64-40ea-4198-b06e-3c873ed9039d',
+ },
+
+ noSuchFile: {
+ message: 'No such file.',
+ code: 'NO_SUCH_FILE',
+ id: '4372b8e2-185d-4146-8749-2f68864a3e5f',
+ },
+
+ contentRequired: {
+ message: 'Content required. You need to set text or fileId.',
+ code: 'CONTENT_REQUIRED',
+ id: '25587321-b0e6-449c-9239-f8925092942c',
+ },
+
+ youHaveBeenBlocked: {
+ message: 'You cannot send a message because you have been blocked by this user.',
+ code: 'YOU_HAVE_BEEN_BLOCKED',
+ id: 'c15a5199-7422-4968-941a-2a462c478f7d',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ text: { type: 'string', nullable: true, maxLength: 2000 },
+ fileId: { type: 'string', format: 'misskey:id' },
+ toUserId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['toUserId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
+
+ private getterService: GetterService,
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ let file = null;
+ if (ps.fileId != null) {
+ file = await this.driveFilesRepository.findOneBy({
+ id: ps.fileId,
+ userId: me.id,
+ });
+
+ if (file == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
+ }
+
+ // テキストが無いかつ添付ファイルも無かったらエラー
+ if (ps.text == null && file == null) {
+ throw new ApiError(meta.errors.contentRequired);
+ }
+
+ // Myself
+ if (ps.toUserId === me.id) {
+ throw new ApiError(meta.errors.recipientIsYourself);
+ }
+
+ const toUser = await this.getterService.getUser(ps.toUserId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
+
+ return await this.chatService.createMessageToUser(me, toUser, {
+ text: ps.text,
+ file: file,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts
new file mode 100644
index 0000000000..959599ddcf
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ },
+
+ errors: {
+ noSuchMessage: {
+ message: 'No such message.',
+ code: 'NO_SUCH_MESSAGE',
+ id: '36b67f0e-66a6-414b-83df-992a55294f17',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ messageId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['messageId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const message = await this.chatService.findMyMessageById(me.id, ps.messageId);
+ if (message == null) {
+ throw new ApiError(meta.errors.noSuchMessage);
+ }
+ await this.chatService.deleteMessage(message);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/react.ts b/packages/backend/src/server/api/endpoints/chat/messages/react.ts
new file mode 100644
index 0000000000..561e36ed19
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/messages/react.ts
@@ -0,0 +1,49 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ },
+
+ errors: {
+ noSuchMessage: {
+ message: 'No such message.',
+ code: 'NO_SUCH_MESSAGE',
+ id: '9b5839b9-0ba0-4351-8c35-37082093d200',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ messageId: { type: 'string', format: 'misskey:id' },
+ reaction: { type: 'string' },
+ },
+ required: ['messageId', 'reaction'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.chatService.react(ps.messageId, me.id, ps.reaction);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts
new file mode 100644
index 0000000000..ccc0030403
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatMessageLite',
+ },
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ roomId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['roomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatEntityService: ChatEntityService,
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const room = await this.chatService.findRoomById(ps.roomId);
+ if (room == null) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+
+ if (!(await this.chatService.isRoomMember(room, me.id))) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+
+ const messages = await this.chatService.roomTimeline(room.id, ps.limit, ps.sinceId, ps.untilId);
+
+ this.chatService.readRoomChatMessage(me.id, room.id);
+
+ return await this.chatEntityService.packMessagesLiteForRoom(messages);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/search.ts b/packages/backend/src/server/api/endpoints/chat/messages/search.ts
new file mode 100644
index 0000000000..4c989e5ca9
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/messages/search.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatMessage',
+ },
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: '460b3669-81b0-4dc9-a997-44442141bf83',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ query: { type: 'string', minLength: 1, maxLength: 256 },
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ userId: { type: 'string', format: 'misskey:id', nullable: true },
+ roomId: { type: 'string', format: 'misskey:id', nullable: true },
+ },
+ required: ['query'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatEntityService: ChatEntityService,
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ if (ps.roomId != null) {
+ const room = await this.chatService.findRoomById(ps.roomId);
+ if (room == null) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+
+ if (!(await this.chatService.isRoomMember(room, me.id))) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+ }
+
+ const messages = await this.chatService.searchMessages(me.id, ps.query, ps.limit, {
+ userId: ps.userId,
+ roomId: ps.roomId,
+ });
+
+ return await this.chatEntityService.packMessagesDetailed(messages, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/show.ts b/packages/backend/src/server/api/endpoints/chat/messages/show.ts
new file mode 100644
index 0000000000..371f7a7071
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/messages/show.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+import { RoleService } from '@/core/RoleService.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatMessage',
+ },
+
+ errors: {
+ noSuchMessage: {
+ message: 'No such message.',
+ code: 'NO_SUCH_MESSAGE',
+ id: '3710865b-1848-4da9-8d61-cfed15510b93',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ messageId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['messageId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ private roleService: RoleService,
+ private chatEntityService: ChatEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const message = await this.chatService.findMessageById(ps.messageId);
+ if (message == null) {
+ throw new ApiError(meta.errors.noSuchMessage);
+ }
+ if (message.fromUserId !== me.id && message.toUserId !== me.id && !(await this.roleService.isModerator(me))) {
+ throw new ApiError(meta.errors.noSuchMessage);
+ }
+ return this.chatEntityService.packMessageDetailed(message, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts
new file mode 100644
index 0000000000..9d308d79b0
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatMessageLite',
+ },
+ },
+
+ errors: {
+ noSuchUser: {
+ message: 'No such user.',
+ code: 'NO_SUCH_USER',
+ id: '11795c64-40ea-4198-b06e-3c873ed9039d',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatEntityService: ChatEntityService,
+ private chatService: ChatService,
+ private getterService: GetterService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const other = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
+
+ const messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, ps.sinceId, ps.untilId);
+
+ this.chatService.readUserChatMessage(me.id, other.id);
+
+ return await this.chatEntityService.packMessagesLiteFor1on1(messages);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/create.ts b/packages/backend/src/server/api/endpoints/chat/rooms/create.ts
new file mode 100644
index 0000000000..fa4cc8ceb4
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/create.ts
@@ -0,0 +1,62 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '@/server/api/error.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+ requiredRolePolicy: 'canChat',
+
+ prohibitMoved: true,
+
+ kind: 'write:chat',
+
+ limit: {
+ duration: ms('1day'),
+ max: 10,
+ },
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoom',
+ },
+
+ errors: {
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ name: { type: 'string', maxLength: 256 },
+ description: { type: 'string', maxLength: 1024 },
+ },
+ required: ['name'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ private chatEntityService: ChatEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const room = await this.chatService.createRoom(me, {
+ name: ps.name,
+ description: ps.description ?? '',
+ });
+ return await this.chatEntityService.packRoom(room);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts
new file mode 100644
index 0000000000..2ef0a778f1
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: 'd4e3753d-97bf-4a19-ab8e-21080fbc0f4b',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['roomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
+ if (room == null) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+ await this.chatService.deleteRoom(room);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts
new file mode 100644
index 0000000000..5da4a1a772
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts
@@ -0,0 +1,68 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '@/server/api/error.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+ requiredRolePolicy: 'canChat',
+
+ prohibitMoved: true,
+
+ kind: 'write:chat',
+
+ limit: {
+ duration: ms('1day'),
+ max: 50,
+ },
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoomInvitation',
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: '916f9507-49ba-4e90-b57f-1fd4deaa47a5',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['roomId', 'userId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ private chatEntityService: ChatEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
+ if (room == null) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+ const invitation = await this.chatService.createRoomInvitation(me.id, room.id, ps.userId);
+ return await this.chatEntityService.packRoomInvitation(invitation, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts
new file mode 100644
index 0000000000..8c017f7d01
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts
@@ -0,0 +1,48 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: '5130557e-5a11-4cfb-9cc5-fe60cda5de0d',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['roomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.chatService.ignoreRoomInvitation(me.id, ps.roomId);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts
new file mode 100644
index 0000000000..07337480fc
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoomInvitation',
+ },
+ },
+
+ errors: {
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ },
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatEntityService: ChatEntityService,
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
+ return this.chatEntityService.packRoomInvitations(invitations, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/join.ts b/packages/backend/src/server/api/endpoints/chat/rooms/join.ts
new file mode 100644
index 0000000000..dbd4d1ea5a
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/join.ts
@@ -0,0 +1,48 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: '84416476-5ce8-4a2c-b568-9569f1b10733',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['roomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.chatService.joinToRoom(me.id, ps.roomId);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts b/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts
new file mode 100644
index 0000000000..c4c6253236
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/joining.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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoomMembership',
+ },
+ },
+
+ errors: {
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ },
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ private chatEntityService: ChatEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, ps.sinceId, ps.untilId);
+
+ return this.chatEntityService.packRoomMemberships(memberships, me, {
+ populateUser: false,
+ populateRoom: true,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts b/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts
new file mode 100644
index 0000000000..724ad61f7e
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts
@@ -0,0 +1,48 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: 'cb7f3179-50e8-4389-8c30-dbe2650a67c9',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['roomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.chatService.leaveRoom(me.id, ps.roomId);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/members.ts b/packages/backend/src/server/api/endpoints/chat/rooms/members.ts
new file mode 100644
index 0000000000..407bfe74f1
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/members.ts
@@ -0,0 +1,74 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoomMembership',
+ },
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: '7b9fe84c-eafc-4d21-bf89-485458ed2c18',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['roomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ private chatEntityService: ChatEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const room = await this.chatService.findRoomById(ps.roomId);
+ if (room == null) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+
+ if (!(await this.chatService.isRoomMember(room, me.id))) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+
+ const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, ps.sinceId, ps.untilId);
+
+ return this.chatEntityService.packRoomMemberships(memberships, me, {
+ populateUser: true,
+ populateRoom: false,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts b/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts
new file mode 100644
index 0000000000..5208b8a253
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts
@@ -0,0 +1,49 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: 'c2cde4eb-8d0f-42f1-8f2f-c4d6bfc8e5df',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ mute: { type: 'boolean' },
+ },
+ required: ['roomId', 'mute'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.chatService.muteRoom(me.id, ps.roomId, ps.mute);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts b/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts
new file mode 100644
index 0000000000..6516120bca
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoom',
+ },
+ },
+
+ errors: {
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ },
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatEntityService: ChatEntityService,
+ private chatService: ChatService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
+ return this.chatEntityService.packRooms(rooms, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/show.ts b/packages/backend/src/server/api/endpoints/chat/rooms/show.ts
new file mode 100644
index 0000000000..547618ee7d
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/show.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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'read:chat',
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoom',
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: '857ae02f-8759-4d20-9adb-6e95fffe4fd7',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['roomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ private chatEntityService: ChatEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const room = await this.chatService.findRoomById(ps.roomId);
+ if (room == null) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+
+ return this.chatEntityService.packRoom(room, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/update.ts b/packages/backend/src/server/api/endpoints/chat/rooms/update.ts
new file mode 100644
index 0000000000..6f2a9c10b5
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/chat/rooms/update.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 { DI } from '@/di-symbols.js';
+import { ChatService } from '@/core/ChatService.js';
+import { ApiError } from '@/server/api/error.js';
+import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+
+export const meta = {
+ tags: ['chat'],
+
+ requireCredential: true,
+
+ kind: 'write:chat',
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'ChatRoom',
+ },
+
+ errors: {
+ noSuchRoom: {
+ message: 'No such room.',
+ code: 'NO_SUCH_ROOM',
+ id: 'fcdb0f92-bda6-47f9-bd05-343e0e020932',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ roomId: { type: 'string', format: 'misskey:id' },
+ name: { type: 'string', maxLength: 256 },
+ description: { type: 'string', maxLength: 1024 },
+ },
+ required: ['roomId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private chatService: ChatService,
+ private chatEntityService: ChatEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
+ if (room == null) {
+ throw new ApiError(meta.errors.noSuchRoom);
+ }
+
+ const updated = await this.chatService.updateRoom(room, {
+ name: ps.name,
+ description: ps.description,
+ });
+
+ return this.chatEntityService.packRoom(updated, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts
index c2f72ad9ae..e3b8f33f97 100644
--- a/packages/backend/src/server/api/endpoints/clips/create.ts
+++ b/packages/backend/src/server/api/endpoints/clips/create.ts
@@ -45,7 +45,7 @@ export const paramDef = {
properties: {
name: { type: 'string', minLength: 1, maxLength: 100 },
isPublic: { type: 'boolean', default: false },
- description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
+ description: { type: 'string', nullable: true, maxLength: 2048 },
},
required: ['name'],
} as const;
@@ -59,7 +59,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
let clip: MiClip;
try {
- clip = await this.clipService.create(me, ps.name, ps.isPublic, ps.description ?? null);
+ // 空文字列をnullにしたいので??は使わない
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ clip = await this.clipService.create(me, ps.name, ps.isPublic, ps.description || null);
} catch (e) {
if (e instanceof ClipService.TooManyClipsError) {
throw new ApiError(meta.errors.tooManyClips);
diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts
index b776f1357d..beab427b69 100644
--- a/packages/backend/src/server/api/endpoints/clips/update.ts
+++ b/packages/backend/src/server/api/endpoints/clips/update.ts
@@ -45,7 +45,7 @@ export const paramDef = {
clipId: { type: 'string', format: 'misskey:id' },
name: { type: 'string', minLength: 1, maxLength: 100 },
isPublic: { type: 'boolean' },
- description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
+ description: { type: 'string', nullable: true, maxLength: 2048 },
},
required: ['clipId'],
} as const;
@@ -59,7 +59,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
try {
- await this.clipService.update(me, ps.clipId, ps.name, ps.isPublic, ps.description);
+ // 空文字列をnullにしたいので??は使わない
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ await this.clipService.update(me, ps.clipId, ps.name, ps.isPublic, ps.description || null);
} catch (e) {
if (e instanceof ClipService.NoSuchClipError) {
throw new ApiError(meta.errors.noSuchClip);
diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts
index 8935c2c2da..b45d21410b 100644
--- a/packages/backend/src/server/api/endpoints/following/invalidate.ts
+++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts
@@ -96,7 +96,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.userFollowingService.unfollow(follower, followee);
- return await this.userEntityService.pack(followee.id, me);
+ return await this.userEntityService.pack(follower.id, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
index bdf6c065e8..ccec96ffbb 100644
--- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
@@ -16,7 +16,7 @@ import { ApiError } from '../../error.js';
export const meta = {
secure: true,
requireCredential: true,
- requireRolePolicy: 'canImportAntennas',
+ requiredRolePolicy: 'canImportAntennas',
prohibitMoved: true,
limit: {
diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
index d7bb6bcd22..2fa450558b 100644
--- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
@@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
export const meta = {
secure: true,
requireCredential: true,
- requireRolePolicy: 'canImportBlocking',
+ requiredRolePolicy: 'canImportBlocking',
prohibitMoved: true,
limit: {
diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts
index e03192d8c6..9186fca162 100644
--- a/packages/backend/src/server/api/endpoints/i/import-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-following.ts
@@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
export const meta = {
secure: true,
requireCredential: true,
- requireRolePolicy: 'canImportFollowing',
+ requiredRolePolicy: 'canImportFollowing',
prohibitMoved: true,
limit: {
duration: ms('1hour'),
diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts
index 76b285bb7e..b6dbacd371 100644
--- a/packages/backend/src/server/api/endpoints/i/import-muting.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts
@@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
export const meta = {
secure: true,
requireCredential: true,
- requireRolePolicy: 'canImportMuting',
+ requiredRolePolicy: 'canImportMuting',
prohibitMoved: true,
limit: {
diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
index 76ecfd082c..5de0a70bbb 100644
--- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
@@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
export const meta = {
secure: true,
requireCredential: true,
- requireRolePolicy: 'canImportUserLists',
+ requiredRolePolicy: 'canImportUserLists',
prohibitMoved: true,
limit: {
duration: ms('1hour'),
diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts
index 1bd641232c..7852b5a2e1 100644
--- a/packages/backend/src/server/api/endpoints/i/move.ts
+++ b/packages/backend/src/server/api/endpoints/i/move.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -19,6 +19,8 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import * as Acct from '@/misc/acct.js';
+import { DI } from '@/di-symbols.js';
+import { MiMeta } from '@/models/_.js';
export const meta = {
tags: ['users'],
@@ -81,6 +83,9 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
+ @Inject(DI.meta)
+ private serverSettings: MiMeta,
+
private remoteUserResolveService: RemoteUserResolveService,
private apiLoggerService: ApiLoggerService,
private accountMoveService: AccountMoveService,
@@ -92,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// check parameter
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
// abort if user is the root
- if (me.isRoot) throw new ApiError(meta.errors.rootForbidden);
+ if (this.serverSettings.rootUserId === me.id) throw new ApiError(meta.errors.rootForbidden);
// abort if user has already moved
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
diff --git a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
index ad4577be58..88d7f51c26 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
@@ -9,7 +9,6 @@ import { Inject, Injectable } from '@nestjs/common';
import type { NotesRepository } from '@/models/_.js';
import { obsoleteNotificationTypes, groupedNotificationTypes, FilterUnionByProperty } from '@/types.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { NoteReadService } from '@/core/NoteReadService.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { DI } from '@/di-symbols.js';
@@ -63,13 +62,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.redis)
private redisClient: Redis.Redis,
- @Inject(DI.notesRepository)
- private notesRepository: NotesRepository,
-
private idService: IdService,
private notificationEntityService: NotificationEntityService,
private notificationService: NotificationService,
- private noteReadService: NoteReadService,
) {
super(meta, paramDef, async (ps, me) => {
const EXTRA_LIMIT = 100;
@@ -162,14 +157,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
groupedNotifications = groupedNotifications.slice(0, ps.limit);
- const noteIds = groupedNotifications
- .filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote' | 'edited'> => ['mention', 'reply', 'quote', 'edited'].includes(notification.type))
- .map(notification => notification.noteId!);
-
- if (noteIds.length > 0) {
- const notes = await this.notesRepository.findBy({ id: In(noteIds) });
- this.noteReadService.read(me.id, notes);
- }
return await this.notificationEntityService.packGroupedMany(groupedNotifications, me.id);
});
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index 5e97b90f99..be8d0cfb34 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -9,7 +9,6 @@ import { Inject, Injectable } from '@nestjs/common';
import type { NotesRepository } from '@/models/_.js';
import { FilterUnionByProperty, notificationTypes, obsoleteNotificationTypes } from '@/types.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { NoteReadService } from '@/core/NoteReadService.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { DI } from '@/di-symbols.js';
@@ -69,7 +68,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private idService: IdService,
private notificationEntityService: NotificationEntityService,
private notificationService: NotificationService,
- private noteReadService: NoteReadService,
) {
super(meta, paramDef, async (ps, me) => {
// includeTypes が空の場合はクエリしない
@@ -136,15 +134,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.notificationService.readAllNotification(me.id);
}
- const noteIds = notifications
- .filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote' | 'edited'> => ['mention', 'reply', 'quote', 'edited'].includes(notification.type))
- .map(notification => notification.noteId);
-
- if (noteIds.length > 0) {
- const notes = await this.notesRepository.findBy({ id: In(noteIds) });
- this.noteReadService.read(me.id, notes);
- }
-
return await this.notificationEntityService.packMany(notifications, me.id);
});
}
diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts
deleted file mode 100644
index edf570fcc5..0000000000
--- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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 { NoteUnreadsRepository } from '@/models/_.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { DI } from '@/di-symbols.js';
-
-export const meta = {
- tags: ['account'],
-
- requireCredential: true,
-
- kind: 'write:account',
-
- // 2 calls per second
- limit: {
- duration: 1000,
- max: 2,
- },
-} as const;
-
-export const paramDef = {
- type: 'object',
- properties: {},
- required: [],
-} as const;
-
-@Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
- constructor(
- @Inject(DI.noteUnreadsRepository)
- private noteUnreadsRepository: NoteUnreadsRepository,
-
- private globalEventService: GlobalEventService,
- ) {
- super(meta, paramDef, async (ps, me) => {
- // Remove documents
- await this.noteUnreadsRepository.delete({
- userId: me.id,
- });
-
- // 全て既読になったイベントを発行
- this.globalEventService.publishMainStream(me.id, 'readAllUnreadMentions');
- this.globalEventService.publishMainStream(me.id, 'readAllUnreadSpecifiedNotes');
- });
- }
-}
diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
index 38328bb7d4..c7599aada2 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -9,7 +9,7 @@ import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
-import generateUserToken from '@/misc/generate-native-user-token.js';
+import { generateNativeUserToken } from '@/misc/token.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
@@ -57,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('incorrect password');
}
- const newToken = generateUserToken();
+ const newToken = generateNativeUserToken();
await this.usersRepository.update(me.id, {
token: newToken,
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index f1d201d081..8abe250f9f 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -215,6 +215,7 @@ export const paramDef = {
autoSensitive: { type: 'boolean' },
followingVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
followersVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
+ chatScope: { type: 'string', enum: ['everyone', 'followers', 'following', 'mutual', 'none'] },
pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true },
mutedWords: muteWords,
hardMutedWords: muteWords,
@@ -325,6 +326,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.listenbrainz !== undefined) profileUpdates.listenbrainz = ps.listenbrainz;
if (ps.followingVisibility !== undefined) profileUpdates.followingVisibility = ps.followingVisibility;
if (ps.followersVisibility !== undefined) profileUpdates.followersVisibility = ps.followersVisibility;
+ if (ps.chatScope !== undefined) updates.chatScope = ps.chatScope;
function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
// TODO: ちゃんと数える
diff --git a/packages/backend/src/server/api/endpoints/invite/create.ts b/packages/backend/src/server/api/endpoints/invite/create.ts
index d661eab364..f607a35515 100644
--- a/packages/backend/src/server/api/endpoints/invite/create.ts
+++ b/packages/backend/src/server/api/endpoints/invite/create.ts
@@ -18,7 +18,7 @@ export const meta = {
tags: ['meta'],
requireCredential: true,
- requireRolePolicy: 'canInvite',
+ requiredRolePolicy: 'canInvite',
kind: 'write:invite-codes',
errors: {
diff --git a/packages/backend/src/server/api/endpoints/invite/delete.ts b/packages/backend/src/server/api/endpoints/invite/delete.ts
index 408200164c..d15d400e9b 100644
--- a/packages/backend/src/server/api/endpoints/invite/delete.ts
+++ b/packages/backend/src/server/api/endpoints/invite/delete.ts
@@ -14,7 +14,7 @@ export const meta = {
tags: ['meta'],
requireCredential: true,
- requireRolePolicy: 'canInvite',
+ requiredRolePolicy: 'canInvite',
kind: 'write:invite-codes',
errors: {
diff --git a/packages/backend/src/server/api/endpoints/invite/limit.ts b/packages/backend/src/server/api/endpoints/invite/limit.ts
index 8d92c4957c..150f4de441 100644
--- a/packages/backend/src/server/api/endpoints/invite/limit.ts
+++ b/packages/backend/src/server/api/endpoints/invite/limit.ts
@@ -15,7 +15,7 @@ export const meta = {
tags: ['meta'],
requireCredential: true,
- requireRolePolicy: 'canInvite',
+ requiredRolePolicy: 'canInvite',
kind: 'read:invite-codes',
res: {
diff --git a/packages/backend/src/server/api/endpoints/invite/list.ts b/packages/backend/src/server/api/endpoints/invite/list.ts
index b63b41edd3..12e3873304 100644
--- a/packages/backend/src/server/api/endpoints/invite/list.ts
+++ b/packages/backend/src/server/api/endpoints/invite/list.ts
@@ -14,7 +14,7 @@ export const meta = {
tags: ['meta'],
requireCredential: true,
- requireRolePolicy: 'canInvite',
+ requiredRolePolicy: 'canInvite',
kind: 'read:invite-codes',
res: {
diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
index c42df7ca80..f962bd49f1 100644
--- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
+++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AccessTokensRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { DI } from '@/di-symbols.js';
@@ -56,6 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private accessTokensRepository: AccessTokensRepository,
private idService: IdService,
+ private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, me) => {
// Generate access token
@@ -77,6 +79,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
permission: ps.permission,
});
+ // アクセストークンが生成されたことを通知
+ this.notificationService.createNotification(me.id, 'createToken', {});
+
return {
token: accessToken,
};
diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts
index 38912421a4..30baa0edf8 100644
--- a/packages/backend/src/server/api/endpoints/notes/mentions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts
@@ -9,7 +9,6 @@ import type { NotesRepository, FollowingsRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
-import { NoteReadService } from '@/core/NoteReadService.js';
import { DI } from '@/di-symbols.js';
export const meta = {
@@ -58,7 +57,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private noteEntityService: NoteEntityService,
private queryService: QueryService,
- private noteReadService: NoteReadService,
) {
super(meta, paramDef, async (ps, me) => {
const followingQuery = this.followingsRepository.createQueryBuilder('following')
@@ -95,8 +93,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const mentions = await query.limit(ps.limit).getMany();
- this.noteReadService.read(me.id, mentions);
-
return await this.noteEntityService.packMany(mentions, me);
});
}
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
index 732d644a29..29c6aa7434 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
@@ -9,7 +9,6 @@ import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/_.js
import { IdService } from '@/core/IdService.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { GetterService } from '@/server/api/GetterService.js';
-import { NoteReadService } from '@/core/NoteReadService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
@@ -52,7 +51,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
private getterService: GetterService,
- private noteReadService: NoteReadService,
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
@@ -69,8 +67,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}],
});
- await this.noteReadService.read(me.id, mutedNotes);
-
await this.noteThreadMutingsRepository.insert({
id: this.idService.gen(),
threadId: note.threadId ?? note.id,
diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts
index bf40e1e3e0..fe23160bb8 100644
--- a/packages/backend/src/server/api/endpoints/reset-db.ts
+++ b/packages/backend/src/server/api/endpoints/reset-db.ts
@@ -6,9 +6,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import * as Redis from 'ioredis';
+import { LoggerService } from '@/core/LoggerService.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { resetDb } from '@/misc/reset-db.js';
+import { MetaService } from '@/core/MetaService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
export const meta = {
tags: ['non-productive'],
@@ -42,13 +45,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.redis)
private redisClient: Redis.Redis,
+
+ private loggerService: LoggerService,
+ private metaService: MetaService,
+ private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
- await redisClient.flushdb();
+ const logger = this.loggerService.getLogger('reset-db');
+ logger.info('---- Resetting database...');
+
+ await this.redisClient.flushdb();
await resetDb(this.db);
+ // DIコンテナで管理しているmetaのインスタンスには上記のリセット処理が届かないため、
+ // 初期値を流して明示的にリフレッシュする
+ const meta = await this.metaService.fetch(true);
+ this.globalEventService.publishInternalEvent('metaUpdated', { after: meta });
+
+ logger.info('---- Database reset complete.');
+
await new Promise(resolve => setTimeout(resolve, 1000));
});
}
diff --git a/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts
index 9426318e34..7139715293 100644
--- a/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts
@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireRolePolicy: 'canManageCustomEmojis',
+ requiredRolePolicy: 'canManageCustomEmojis',
kind: 'read:admin:emoji',
res: {
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
index 82ee0f47d7..85d1fd0bce 100644
--- a/packages/backend/src/server/api/openapi/gen-spec.ts
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -210,9 +210,15 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
spec.paths['/' + endpoint.name] = {
...(endpoint.meta.allowGet ? {
- get: info,
+ get: {
+ ...info,
+ operationId: 'get___' + info.operationId,
+ },
} : {}),
- post: info,
+ post: {
+ ...info,
+ operationId: 'post___' + info.operationId,
+ },
};
}
diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts
index 83c5fcdf52..13934628a8 100644
--- a/packages/backend/src/server/api/stream/ChannelsService.ts
+++ b/packages/backend/src/server/api/stream/ChannelsService.ts
@@ -20,6 +20,8 @@ import { AntennaChannelService } from './channels/antenna.js';
import { DriveChannelService } from './channels/drive.js';
import { HashtagChannelService } from './channels/hashtag.js';
import { RoleTimelineChannelService } from './channels/role-timeline.js';
+import { ChatUserChannelService } from './channels/chat-user.js';
+import { ChatRoomChannelService } from './channels/chat-room.js';
import { ReversiChannelService } from './channels/reversi.js';
import { ReversiGameChannelService } from './channels/reversi-game.js';
import { type MiChannelService } from './channel.js';
@@ -42,6 +44,8 @@ export class ChannelsService {
private serverStatsChannelService: ServerStatsChannelService,
private queueStatsChannelService: QueueStatsChannelService,
private adminChannelService: AdminChannelService,
+ private chatUserChannelService: ChatUserChannelService,
+ private chatRoomChannelService: ChatRoomChannelService,
private reversiChannelService: ReversiChannelService,
private reversiGameChannelService: ReversiGameChannelService,
) {
@@ -65,6 +69,8 @@ export class ChannelsService {
case 'serverStats': return this.serverStatsChannelService;
case 'queueStats': return this.queueStatsChannelService;
case 'admin': return this.adminChannelService;
+ case 'chatUser': return this.chatUserChannelService;
+ case 'chatRoom': return this.chatRoomChannelService;
case 'reversi': return this.reversiChannelService;
case 'reversiGame': return this.reversiGameChannelService;
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index e98e2a2f3f..a1ea947d20 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -7,7 +7,6 @@ import * as WebSocket from 'ws';
import type { MiUser } from '@/models/User.js';
import type { MiAccessToken } from '@/models/AccessToken.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { NoteReadService } from '@/core/NoteReadService.js';
import type { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
import { CacheService } from '@/core/CacheService.js';
@@ -51,7 +50,6 @@ export default class Connection {
constructor(
private channelsService: ChannelsService,
- private noteReadService: NoteReadService,
private notificationService: NotificationService,
private cacheService: CacheService,
private channelFollowingService: ChannelFollowingService,
@@ -154,7 +152,7 @@ export default class Connection {
case 'readNotification': this.onReadNotification(body); break;
case 'subNote': this.onSubscribeNote(body); break;
case 's': this.onSubscribeNote(body); break; // alias
- case 'sr': this.onSubscribeNote(body); this.readNote(body); break;
+ case 'sr': this.onSubscribeNote(body); break;
case 'unsubNote': this.onUnsubscribeNote(body); break;
case 'un': this.onUnsubscribeNote(body); break; // alias
case 'connect': this.onChannelConnectRequested(body); break;
@@ -190,19 +188,6 @@ export default class Connection {
}
@bindThis
- private readNote(body: JsonValue | undefined) {
- if (!isJsonObject(body)) return;
- const id = body.id;
-
- const note = this.cachedNotes.find(n => n.id === id);
- if (note == null) return;
-
- if (this.user && (note.userId !== this.user.id)) {
- this.noteReadService.read(this.user.id, [note]);
- }
- }
-
- @bindThis
private onReadNotification(payload: JsonValue | undefined) {
this.notificationService.readAllNotification(this.user!.id);
}
diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts
index 5189c86de8..9af816dfbb 100644
--- a/packages/backend/src/server/api/stream/channel.ts
+++ b/packages/backend/src/server/api/stream/channel.ts
@@ -106,8 +106,8 @@ export default abstract class Channel {
this.noteEntityService = noteEntityService;
}
- public send(payload: { type: string, body: JsonValue }): void
- public send(type: string, payload: JsonValue): void
+ public send(payload: { type: string, body: JsonValue }): void;
+ public send(type: string, payload: JsonValue): void;
@bindThis
public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) {
const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string);
@@ -166,4 +166,4 @@ export type MiChannelService<T extends boolean> = {
requireCredential: T;
kind: T extends true ? string : string | null | undefined;
create: (id: string, connection: Connection) => Channel;
-}
+};
diff --git a/packages/backend/src/server/api/stream/channels/chat-room.ts b/packages/backend/src/server/api/stream/channels/chat-room.ts
new file mode 100644
index 0000000000..e989969345
--- /dev/null
+++ b/packages/backend/src/server/api/stream/channels/chat-room.ts
@@ -0,0 +1,78 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { bindThis } from '@/decorators.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import type { JsonObject } from '@/misc/json-value.js';
+import { ChatService } from '@/core/ChatService.js';
+import Channel, { type MiChannelService } from '../channel.js';
+
+class ChatRoomChannel extends Channel {
+ public readonly chName = 'chatRoom';
+ public static shouldShare = false;
+ public static requireCredential = true as const;
+ public static kind = 'read:chat';
+ private roomId: string;
+
+ constructor(
+ private chatService: ChatService,
+
+ id: string,
+ connection: Channel['connection'],
+ ) {
+ super(id, connection);
+ }
+
+ @bindThis
+ public async init(params: JsonObject) {
+ if (typeof params.roomId !== 'string') return;
+ this.roomId = params.roomId;
+
+ this.subscriber.on(`chatRoomStream:${this.roomId}`, this.onEvent);
+ }
+
+ @bindThis
+ private async onEvent(data: GlobalEvents['chat']['payload']) {
+ this.send(data.type, data.body);
+ }
+
+ @bindThis
+ public onMessage(type: string, body: any) {
+ switch (type) {
+ case 'read':
+ if (this.roomId) {
+ this.chatService.readRoomChatMessage(this.user!.id, this.roomId);
+ }
+ break;
+ }
+ }
+
+ @bindThis
+ public dispose() {
+ this.subscriber.off(`chatRoomStream:${this.roomId}`, this.onEvent);
+ }
+}
+
+@Injectable()
+export class ChatRoomChannelService implements MiChannelService<true> {
+ public readonly shouldShare = ChatRoomChannel.shouldShare;
+ public readonly requireCredential = ChatRoomChannel.requireCredential;
+ public readonly kind = ChatRoomChannel.kind;
+
+ constructor(
+ private chatService: ChatService,
+ ) {
+ }
+
+ @bindThis
+ public create(id: string, connection: Channel['connection']): ChatRoomChannel {
+ return new ChatRoomChannel(
+ this.chatService,
+ id,
+ connection,
+ );
+ }
+}
diff --git a/packages/backend/src/server/api/stream/channels/chat-user.ts b/packages/backend/src/server/api/stream/channels/chat-user.ts
new file mode 100644
index 0000000000..c4e898cd5b
--- /dev/null
+++ b/packages/backend/src/server/api/stream/channels/chat-user.ts
@@ -0,0 +1,78 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { bindThis } from '@/decorators.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import type { JsonObject } from '@/misc/json-value.js';
+import { ChatService } from '@/core/ChatService.js';
+import Channel, { type MiChannelService } from '../channel.js';
+
+class ChatUserChannel extends Channel {
+ public readonly chName = 'chatUser';
+ public static shouldShare = false;
+ public static requireCredential = true as const;
+ public static kind = 'read:chat';
+ private otherId: string;
+
+ constructor(
+ private chatService: ChatService,
+
+ id: string,
+ connection: Channel['connection'],
+ ) {
+ super(id, connection);
+ }
+
+ @bindThis
+ public async init(params: JsonObject) {
+ if (typeof params.otherId !== 'string') return;
+ this.otherId = params.otherId;
+
+ this.subscriber.on(`chatUserStream:${this.user!.id}-${this.otherId}`, this.onEvent);
+ }
+
+ @bindThis
+ private async onEvent(data: GlobalEvents['chat']['payload']) {
+ this.send(data.type, data.body);
+ }
+
+ @bindThis
+ public onMessage(type: string, body: any) {
+ switch (type) {
+ case 'read':
+ if (this.otherId) {
+ this.chatService.readUserChatMessage(this.user!.id, this.otherId);
+ }
+ break;
+ }
+ }
+
+ @bindThis
+ public dispose() {
+ this.subscriber.off(`chatUserStream:${this.user!.id}-${this.otherId}`, this.onEvent);
+ }
+}
+
+@Injectable()
+export class ChatUserChannelService implements MiChannelService<true> {
+ public readonly shouldShare = ChatUserChannel.shouldShare;
+ public readonly requireCredential = ChatUserChannel.requireCredential;
+ public readonly kind = ChatUserChannel.kind;
+
+ constructor(
+ private chatService: ChatService,
+ ) {
+ }
+
+ @bindThis
+ public create(id: string, connection: Channel['connection']): ChatUserChannel {
+ return new ChatUserChannel(
+ this.chatService,
+ id,
+ connection,
+ );
+ }
+}
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 3ed811e737..165e4f3f73 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -7,16 +7,12 @@ import { randomUUID } from 'node:crypto';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
-import { createBullBoard } from '@bull-board/api';
-import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
-import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify';
import ms from 'ms';
import sharp from 'sharp';
import pug from 'pug';
import { In, IsNull } from 'typeorm';
import fastifyStatic from '@fastify/static';
import fastifyView from '@fastify/view';
-import fastifyCookie from '@fastify/cookie';
import fastifyProxy from '@fastify/http-proxy';
import vary from 'vary';
import htmlSafeJsonStringify from 'htmlescape';
@@ -226,65 +222,6 @@ export class ClientServerService {
@bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
- fastify.register(fastifyCookie, {});
-
- //#region Bull Dashboard
- const bullBoardPath = '/queue';
-
- // Authenticate
- fastify.addHook('onRequest', async (request, reply) => {
- if (request.routeOptions.url == null) {
- reply.code(404).send('Not found');
- return;
- }
-
- // %71ueueとかでリクエストされたら困るため
- const url = decodeURI(request.routeOptions.url);
- if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) {
- if (!url.startsWith(bullBoardPath + '/static/')) {
- reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
- }
-
- const token = request.cookies.token;
- if (token == null) {
- reply.code(401).send('Login required');
- return;
- }
- const user = await this.usersRepository.findOneBy({ token });
- if (user == null) {
- reply.code(403).send('No such user');
- return;
- }
- const isAdministrator = await this.roleService.isAdministrator(user);
- if (!isAdministrator) {
- reply.code(403).send('Access denied');
- return;
- }
- }
- });
-
- const bullBoardServerAdapter = new BullBoardFastifyAdapter();
-
- createBullBoard({
- queues: [
- this.systemQueue,
- this.endedPollNotificationQueue,
- this.deliverQueue,
- this.inboxQueue,
- this.dbQueue,
- this.relationshipQueue,
- this.objectStorageQueue,
- this.userWebhookDeliverQueue,
- this.systemWebhookDeliverQueue,
- this.scheduleNotePostQueue,
- ].map(q => new BullMQAdapter(q)),
- serverAdapter: bullBoardServerAdapter,
- });
-
- bullBoardServerAdapter.setBasePath(bullBoardPath);
- (fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
- //#endregion
-
fastify.register(fastifyView, {
root: _dirname + '/views',
engine: {
diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js
index 1af1dc545b..95f633c067 100644
--- a/packages/backend/src/server/web/boot.embed.js
+++ b/packages/backend/src/server/web/boot.embed.js
@@ -103,13 +103,18 @@
if (document.readyState === 'loading') {
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
}
+
+ const locale = JSON.parse(localStorage.getItem('locale') || '{}');
+
+ const title = locale?._bootErrors?.title || 'Failed to initialize Misskey';
+ const reload = locale?.reload || 'Reload';
+
document.body.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 9v4" /><path d="M12 16v.01" /></svg>
- <div class="message">読み込みに失敗しました</div>
+ <div class="message">${title}</div>
<div class="submessage">Failed to initialize Sharkey</div>
<div class="submessage">Error Code: ${code}</div>
<button onclick="location.reload(!0)">
- <div>リロード</div>
- <div><small>Reload</small></div>
+ <div>${reload}</div>
</button>`;
addStyle(`
#sharkey_app,
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 54750e26e5..40afbc0f7b 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -166,6 +166,22 @@
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
}
+ const locale = JSON.parse(localStorage.getItem('locale') || '{}');
+
+ const messages = Object.assign({
+ title: 'Failed to initialize Misskey',
+ solution: 'The following actions may solve the problem.',
+ solution1: 'Update your os and browser',
+ solution2: 'Disable an adblocker',
+ solution3: 'Clear the browser cache',
+ solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
+ otherOption: 'Other options',
+ otherOption1: 'Clear preferences and cache',
+ otherOption2: 'Start the simple client',
+ otherOption3: 'Start the repair tool',
+ }, locale?._bootErrors || {});
+ const reload = locale?.reload || 'Reload';
+
let errorsElement = document.getElementById('errors');
if (!errorsElement) {
@@ -175,32 +191,32 @@
<path d="M12 9v2m0 4v.01"></path>
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
</svg>
- <h1>Failed to load<br>読み込みに失敗しました</h1>
+ <h1>${messages.title}</h1>
<button class="button-big" onclick="location.reload(true);">
- <span class="button-label-big">Reload / リロード</span>
+ <span class="button-label-big">${reload}</span>
</button>
- <p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります。</b></p>
- <p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p>
- <p>Disable an adblocker / アドブロッカーを無効にする</p>
- <p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
- <p>&#40;Tor Browser&#41; Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p>
+ <p><b>${messages.solution}</b></p>
+ <p>${messages.solution1}</p>
+ <p>${messages.solution2}</p>
+ <p>${messages.solution3}</p>
+ <p>${messages.solution4}</p>
<details style="color: #86b300;">
- <summary>Other options / その他のオプション</summary>
+ <summary>${messages.otherOption}</summary>
<a href="/flush">
<button class="button-small">
- <span class="button-label-small">Clear preferences and cache</span>
+ <span class="button-label-small">${messages.otherOption1}</span>
</button>
</a>
<br>
<a href="/cli">
<button class="button-small">
- <span class="button-label-small">Start the simple client</span>
+ <span class="button-label-small">${messages.otherOption2}</span>
</button>
</a>
<br>
<a href="/bios">
<button class="button-small">
- <span class="button-label-small">Start the repair tool</span>
+ <span class="button-label-small">${messages.otherOption3}</span>
</button>
</a>
</details>
diff --git a/packages/backend/src/server/web/error.css b/packages/backend/src/server/web/error.css
index 6c96241970..8d03ceadf0 100644
--- a/packages/backend/src/server/web/error.css
+++ b/packages/backend/src/server/web/error.css
@@ -5,112 +5,107 @@
*/
* {
- font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+ font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
}
#sharkey_app,
#splash {
- display: none !important;
+ display: none !important;
}
body,
html {
- background-color: #222;
- color: #dfddcc;
- justify-content: center;
- margin: auto;
- padding: 10px;
- text-align: center;
+ background-color: #222;
+ color: #dfddcc;
+ justify-content: center;
+ margin: auto;
+ padding: 10px;
+ text-align: center;
}
button {
- border-radius: 999px;
- padding: 0px 12px 0px 12px;
- border: none;
- cursor: pointer;
- margin-bottom: 12px;
+ border-radius: 999px;
+ padding: 0px 12px 0px 12px;
+ border: none;
+ cursor: pointer;
+ margin-bottom: 12px;
}
.button-big {
- background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
- line-height: 50px;
+ background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
+ line-height: 50px;
}
.button-big:hover {
- background: rgb(153, 204, 0);
+ background: rgb(153, 204, 0);
}
.button-small {
- background: #444;
- line-height: 40px;
+ background: #444;
+ line-height: 40px;
}
.button-small:hover {
- background: #555;
+ background: #555;
}
.button-label-big {
- color: #222;
- font-weight: bold;
- font-size: 20px;
- padding: 12px;
+ color: #222;
+ font-weight: bold;
+ font-size: 1.2em;
+ padding: 12px;
}
.button-label-small {
- color: rgb(153, 204, 0);
- font-size: 16px;
- padding: 12px;
+ color: rgb(153, 204, 0);
+ font-size: 16px;
+ padding: 12px;
}
a {
- color: rgb(134, 179, 0);
- text-decoration: none;
+ color: rgb(134, 179, 0);
+ text-decoration: none;
}
p,
li {
- font-size: 16px;
-}
-
-.dont-worry,
-#msg {
- font-size: 18px;
+ font-size: 16px;
}
.icon-warning {
- color: #dec340;
- height: 4rem;
- padding-top: 2rem;
+ color: #dec340;
+ height: 4rem;
+ padding-top: 2rem;
}
h1 {
- font-size: 32px;
+ font-size: 1.5em;
+ margin: 1em;
}
code {
- display: block;
- font-family: Fira, FiraCode, monospace;
- background: #333;
- padding: 0.5rem 1rem;
- max-width: 40rem;
- border-radius: 10px;
- justify-content: center;
- margin: auto;
- white-space: pre-wrap;
- word-break: break-word;
+ display: block;
+ font-family: Fira, FiraCode, monospace;
+ background: #333;
+ padding: 0.5rem 1rem;
+ max-width: 40rem;
+ border-radius: 10px;
+ justify-content: center;
+ margin: auto;
+ white-space: pre-wrap;
+ word-break: break-word;
}
-summary {
- cursor: pointer;
+#errorInfo summary {
+ cursor: pointer;
}
-summary > * {
- display: inline;
- white-space: pre-wrap;
+#errorInfo summary>* {
+ display: inline;
}
@media screen and (max-width: 500px) {
- details {
- width: 50%;
- }
+ #errorInfo {
+ width: 50%;
+ }
}
diff --git a/packages/backend/src/server/web/error.js b/packages/backend/src/server/web/error.js
new file mode 100644
index 0000000000..4838dd6ef3
--- /dev/null
+++ b/packages/backend/src/server/web/error.js
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+'use strict';
+
+(() => {
+ document.addEventListener('DOMContentLoaded', () => {
+ const locale = JSON.parse(localStorage.getItem('locale') || '{}');
+
+ const messages = Object.assign({
+ title: 'Failed to initialize Misskey',
+ serverError: 'If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.',
+ solution: 'The following actions may solve the problem.',
+ solution1: 'Update your os and browser',
+ solution2: 'Disable an adblocker',
+ solution3: 'Clear the browser cache',
+ solution4: '(Tor Browser) Set dom.webaudio.enabled to true',
+ otherOption: 'Other options',
+ otherOption1: 'Clear preferences and cache',
+ otherOption2: 'Start the simple client',
+ otherOption3: 'Start the repair tool',
+ }, locale?._bootErrors || {});
+ const reload = locale?.reload || 'Reload';
+
+ const reloadEls = document.querySelectorAll('[data-i18n-reload]');
+ for (const el of reloadEls) {
+ el.textContent = reload;
+ }
+
+ const i18nEls = document.querySelectorAll('[data-i18n]');
+ for (const el of i18nEls) {
+ const key = el.dataset.i18n;
+ if (key && messages[key]) {
+ el.textContent = messages[key];
+ }
+ }
+ });
+})();
diff --git a/packages/backend/src/server/web/views/error.pug b/packages/backend/src/server/web/views/error.pug
index 39b75abc4c..33aa8adf0e 100644
--- a/packages/backend/src/server/web/views/error.pug
+++ b/packages/backend/src/server/web/views/error.pug
@@ -13,7 +13,7 @@ doctype html
Thank you for using Sharkey!
If you are reading this message... how about joining the development?
- https://activitypub.software/TransFem-org/Sharkey
+ https://github.com/misskey-dev/misskey
html
@@ -31,39 +31,45 @@ html
style
include ../error.css
+ script
+ include ../error.js
+
body
svg.icon-warning(xmlns="http://www.w3.org/2000/svg", viewBox="0 0 24 24", stroke-width="2", stroke="currentColor", fill="none", stroke-linecap="round", stroke-linejoin="round")
path(stroke="none", d="M0 0h24v24H0z", fill="none")
path(d="M12 9v2m0 4v.01")
path(d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75")
- h1 An error has occurred!
+ h1(data-i18n="title") Failed to initialize Sharkey
button.button-big(onclick="location.reload();")
- span.button-label-big Refresh
-
- p.dont-worry Don't worry, it's (probably) not your fault.
+ span.button-label-big(data-i18n-reload) Reload
- p If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
+ p(data-i18n="serverError") If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
div#errors
code.
ERROR CODE: #{code}
ERROR ID: #{id}
- p You may also try the following options:
+ p
+ b(data-i18n="solution") The following actions may solve the problem.
- p Update your os and browser.
- p Disable an adblocker.
+ p(data-i18n="solution1") Update your os and browser
+ p(data-i18n="solution2") Disable an adblocker
+ p(data-i18n="solution3") Clear your browser cache
+ p(data-i18n="solution4") (Tor Browser) Set dom.webaudio.enabled to true
- a(href="/flush")
- button.button-small
- span.button-label-small Clear preferences and cache
- br
- a(href="/cli")
- button.button-small
- span.button-label-small Start the simple client
- br
- a(href="/bios")
- button.button-small
- span.button-label-small Start the repair tool
+ details(style="color: #86b300;")
+ summary(data-i18n="otherOption") Other options
+ a(href="/flush")
+ button.button-small
+ span.button-label-small(data-i18n="otherOption1") Clear preferences and cache
+ br
+ a(href="/cli")
+ button.button-small
+ span.button-label-small(data-i18n="otherOption2") Start the simple client
+ br
+ a(href="/bios")
+ button.button-small
+ span.button-label-small(data-i18n="otherOption3") Start the repair tool
diff --git a/packages/backend/src/server/web/views/oauth.pug b/packages/backend/src/server/web/views/oauth.pug
index 1470dbfbdf..4195ccc3a3 100644
--- a/packages/backend/src/server/web/views/oauth.pug
+++ b/packages/backend/src/server/web/views/oauth.pug
@@ -6,4 +6,6 @@ block meta
//- XXX: Remove navigation bar in auth page?
meta(name='misskey:oauth:transaction-id' content=transactionId)
meta(name='misskey:oauth:client-name' content=clientName)
+ if clientLogo
+ meta(name='misskey:oauth:client-logo' content=clientLogo)
meta(name='misskey:oauth:scope' content=scope)
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index 2c6ef731b8..920fed57b1 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -18,6 +18,7 @@
* achievementEarned - 実績を獲得
* exportCompleted - エクスポートが完了
* login - ログイン
+ * createToken - トークン作成
* app - アプリ通知
* test - テスト通知(サーバー側)
*/
@@ -37,6 +38,7 @@ export const notificationTypes = [
'achievementEarned',
'exportCompleted',
'login',
+ 'createToken',
'scheduledNoteFailed',
'scheduledNotePosted',
'app',
@@ -151,6 +153,7 @@ export const moderationLogTypes = [
'createPromo',
'addRelay',
'removeRelay',
+ 'updateProxyAccountDescription',
] as const;
export type ModerationLogPayloads = {
@@ -450,6 +453,10 @@ export type ModerationLogPayloads = {
id: string;
host: string;
};
+ updateProxyAccountDescription: {
+ before: string | null;
+ after: string | null;
+ };
clearUserFiles: {
userId: string;
userUsername: string;
@@ -518,21 +525,21 @@ export type ModerationLogPayloads = {
export type Serialized<T> = {
[K in keyof T]:
- T[K] extends Date
- ? string
- : T[K] extends (Date | null)
- ? (string | null)
- : T[K] extends Record<string, any>
- ? Serialized<T[K]>
- : T[K] extends (Record<string, any> | null)
+ T[K] extends Date
+ ? string
+ : T[K] extends (Date | null)
+ ? (string | null)
+ : T[K] extends Record<string, any>
+ ? Serialized<T[K]>
+ : T[K] extends (Record<string, any> | null)
? (Serialized<T[K]> | null)
- : T[K] extends (Record<string, any> | undefined)
+ : T[K] extends (Record<string, any> | undefined)
? (Serialized<T[K]> | undefined)
- : T[K];
+ : T[K];
};
export type FilterUnionByProperty<
- Union,
- Property extends string | number | symbol,
- Condition
+ Union,
+ Property extends string | number | symbol,
+ Condition,
> = Union extends Record<Property, Condition> ? Union : never;
diff --git a/packages/backend/test-federation/compose.tpl.yml b/packages/backend/test-federation/compose.tpl.yml
index 8b270e58f7..25770063d3 100644
--- a/packages/backend/test-federation/compose.tpl.yml
+++ b/packages/backend/test-federation/compose.tpl.yml
@@ -17,7 +17,6 @@ services:
- ./.config/docker.env
environment:
- NODE_ENV=production
- - COREPACK_DEFAULT_TO_LATEST=0
volumes:
- type: bind
source: ../../../built
@@ -76,13 +75,17 @@ services:
target: /misskey/pnpm-workspace.yaml
read_only: true
- type: bind
+ source: ../../../scripts/dependency-patches
+ target: /misskey/scripts/dependency-patches
+ 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
+ npm install -g pnpm
pnpm -F backend migrate
pnpm -F backend start
"
diff --git a/packages/backend/test-federation/compose.yml b/packages/backend/test-federation/compose.yml
index a5a7223982..dfa51b940a 100644
--- a/packages/backend/test-federation/compose.yml
+++ b/packages/backend/test-federation/compose.yml
@@ -9,7 +9,7 @@ services:
service: misskey
command: >
bash -c "
- corepack enable && corepack prepare
+ npm install -g pnpm
pnpm -F backend i
pnpm -F misskey-js i
pnpm -F misskey-reversi i
@@ -20,12 +20,15 @@ services:
depends_on:
a.test:
condition: service_healthy
+ misskey.a.test:
+ condition: service_healthy
b.test:
condition: service_healthy
+ misskey.b.test:
+ condition: service_healthy
environment:
- NODE_ENV=development
- NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt
- - COREPACK_DEFAULT_TO_LATEST=0
volumes:
- type: bind
source: ../package.json
@@ -68,13 +71,17 @@ services:
target: /misskey/pnpm-workspace.yaml
read_only: true
- type: bind
+ source: ../../../scripts/dependency-patches
+ target: /misskey/scripts/dependency-patches
+ 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
+ npm install -g pnpm
pnpm -F misskey-js i --frozen-lockfile
pnpm -F backend i --frozen-lockfile
exec "$0" "$@"
@@ -86,8 +93,6 @@ services:
depends_on:
redis.test:
condition: service_healthy
- environment:
- - COREPACK_DEFAULT_TO_LATEST=0
volumes:
- type: bind
source: ../package.json
@@ -113,10 +118,14 @@ services:
source: ../../../pnpm-workspace.yaml
target: /misskey/pnpm-workspace.yaml
read_only: true
+ - type: bind
+ source: ../../../scripts/dependency-patches
+ target: /misskey/scripts/dependency-patches
+ read_only: true
working_dir: /misskey
command: >
bash -c "
- corepack enable && corepack prepare
+ npm install -g pnpm
pnpm -F backend i --frozen-lockfile
pnpm exec tsc -p ./packages/backend/test-federation
node ./packages/backend/test-federation/built/daemon.js
diff --git a/packages/backend/test-federation/test/abuse-report.test.ts b/packages/backend/test-federation/test/abuse-report.test.ts
index b54d6222b4..ddc8e4f9d0 100644
--- a/packages/backend/test-federation/test/abuse-report.test.ts
+++ b/packages/backend/test-federation/test/abuse-report.test.ts
@@ -35,7 +35,7 @@ describe('Abuse report', () => {
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.reporter.url, 'https://a.test/@system.actor');
strictEqual(reportInB.targetUserId, bob.id);
// NOTE: cannot forward multiple times
diff --git a/packages/backend/test-federation/test/note.test.ts b/packages/backend/test-federation/test/note.test.ts
index 220c22e198..1584f9587e 100644
--- a/packages/backend/test-federation/test/note.test.ts
+++ b/packages/backend/test-federation/test/note.test.ts
@@ -139,29 +139,99 @@ describe('Note', () => {
});
describe('Deletion', () => {
- describe('Check Delete consistency', () => {
- let carol: LoginUser;
+ describe('Check Delete is delivered', () => {
+ describe('To followers', () => {
+ let carol: LoginUser;
- beforeAll(async () => {
- carol = await createAccount('a.test');
+ beforeAll(async () => {
+ carol = await createAccount('a.test');
- await carol.client.request('following/create', { userId: bobInA.id });
- await sleep();
+ await carol.client.request('following/create', { userId: bobInA.id });
+ await sleep();
+ });
+
+ test('Check', 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;
+ },
+ );
+ });
+
+ afterAll(async () => {
+ await carol.client.request('following/delete', { 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();
+ describe('To renoted and not followed user', () => {
+ test('Check', async () => {
+ const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
+ const noteInA = await resolveRemoteNote('b.test', note.id, alice);
+ await alice.client.request('notes/create', { renoteId: noteInA.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;
- },
- );
+ await bob.client.request('notes/delete', { noteId: note.id });
+ await sleep();
+
+ await rejects(
+ async () => await alice.client.request('notes/show', { noteId: noteInA.id }),
+ (err: any) => {
+ strictEqual(err.code, 'NO_SUCH_NOTE');
+ return true;
+ },
+ );
+ });
+ });
+
+ describe('To replied and not followed user', () => {
+ test('Check', async () => {
+ const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
+ const noteInA = await resolveRemoteNote('b.test', note.id, alice);
+ await alice.client.request('notes/create', { text: 'Hello Bob!', replyId: noteInA.id });
+ await sleep();
+
+ await bob.client.request('notes/delete', { noteId: note.id });
+ await sleep();
+
+ await rejects(
+ async () => await alice.client.request('notes/show', { noteId: noteInA.id }),
+ (err: any) => {
+ strictEqual(err.code, 'NO_SUCH_NOTE');
+ return true;
+ },
+ );
+ });
+ });
+
+ /**
+ * FIXME: not delivered
+ * @see https://github.com/misskey-dev/misskey/issues/15548
+ */
+ describe('To only resolved and not followed user', () => {
+ test.failing('Check', async () => {
+ const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
+ const noteInA = await resolveRemoteNote('b.test', note.id, alice);
+ await sleep();
+
+ await bob.client.request('notes/delete', { noteId: note.id });
+ await sleep();
+
+ await rejects(
+ async () => await alice.client.request('notes/show', { noteId: noteInA.id }),
+ (err: any) => {
+ strictEqual(err.code, 'NO_SUCH_NOTE');
+ return true;
+ },
+ );
+ });
});
});
diff --git a/packages/backend/test-federation/test/timeline.test.ts b/packages/backend/test-federation/test/timeline.test.ts
index 2250bf4a42..00635e654b 100644
--- a/packages/backend/test-federation/test/timeline.test.ts
+++ b/packages/backend/test-federation/test/timeline.test.ts
@@ -24,7 +24,7 @@ describe('Timeline', () => {
});
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');
+ type TimelineEndpoint = keyof Misskey.Endpoints & (`notes/${string}timeline` | 'antennas/notes' | 'roles/notes' | 'notes/search-by-tag');
const timelineMap = new Map<TimelineChannel, TimelineEndpoint>([
['antenna', 'antennas/notes'],
['globalTimeline', 'notes/global-timeline'],
diff --git a/packages/backend/test-federation/test/user.test.ts b/packages/backend/test-federation/test/user.test.ts
index 76605e61d4..83dcb8df44 100644
--- a/packages/backend/test-federation/test/user.test.ts
+++ b/packages/backend/test-federation/test/user.test.ts
@@ -37,6 +37,7 @@ describe('User', () => {
'id',
'host',
'avatarUrl',
+ 'avatarBlurhash',
'instance',
'badgeRoles',
'url',
@@ -379,7 +380,8 @@ describe('User', () => {
strictEqual(followers.length, 1); // followed by Bob
await alice.client.request('i/delete-account', { password: alice.password });
- await sleep();
+ // NOTE: user deletion query is slow
+ await sleep(4000);
const following = await bob.client.request('users/following', { userId: bob.id });
strictEqual(following.length, 0); // no following relation
@@ -477,7 +479,8 @@ describe('User', () => {
strictEqual(followers.length, 1); // followed by Bob
await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
- await sleep();
+ // NOTE: user deletion query is slow
+ await sleep(4000);
const following = await bob.client.request('users/following', { userId: bob.id });
strictEqual(following.length, 0); // no following relation
diff --git a/packages/backend/test-federation/test/utils.ts b/packages/backend/test-federation/test/utils.ts
index 7b2d023685..b155103efa 100644
--- a/packages/backend/test-federation/test/utils.ts
+++ b/packages/backend/test-federation/test/utils.ts
@@ -22,7 +22,7 @@ export type LoginUser = SigninResponse & {
client: Misskey.api.APIClient;
username: string;
password: string;
-}
+};
/** used for avoiding overload and some endpoints */
export type Request = <
@@ -36,7 +36,7 @@ export type Request = <
type Host = 'a.test' | 'b.test';
-export async function sleep(ms = 200): Promise<void> {
+export async function sleep(ms = 250): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
diff --git a/packages/backend/test-server/.swcrc b/packages/backend/test-server/.swcrc
index e3d6935169..eeac7eabc6 100644
--- a/packages/backend/test-server/.swcrc
+++ b/packages/backend/test-server/.swcrc
@@ -1,5 +1,5 @@
{
- "$schema": "https://json.schemastore.org/swcrc",
+ "$schema": "https://swc.rs/schema.json",
"jsc": {
"parser": {
"syntax": "typescript",
diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts
index a130c3698d..7ae1ee4523 100644
--- a/packages/backend/test/e2e/clips.ts
+++ b/packages/backend/test/e2e/clips.ts
@@ -182,7 +182,6 @@ describe('クリップ', () => {
{ label: 'nameがnull', parameters: { name: null } },
{ label: 'nameが最大長+1', parameters: { name: 'x'.repeat(101) } },
{ label: 'isPublicがboolじゃない', parameters: { isPublic: 'true' } },
- { label: 'descriptionがゼロ長', parameters: { description: '' } },
{ label: 'descriptionが最大長+1', parameters: { description: 'a'.repeat(2049) } },
];
test.each(createClipDenyPattern)('の作成は$labelならできない', async ({ parameters }) => failedApiCall({
@@ -199,6 +198,23 @@ describe('クリップ', () => {
id: '3d81ceae-475f-4600-b2a8-2bc116157532',
}));
+ test('の作成はdescriptionが空文字ならnullになる', async () => {
+ const clip = await successfulApiCall({
+ endpoint: 'clips/create',
+ parameters: {
+ ...defaultCreate(),
+ description: '',
+ },
+ user: alice,
+ });
+
+ assert.deepStrictEqual(clip, {
+ ...clip,
+ ...defaultCreate(),
+ description: null,
+ });
+ });
+
test('の更新ができる', async () => {
const res = await update({
clipId: (await create()).id,
@@ -249,6 +265,24 @@ describe('クリップ', () => {
...assertion,
}));
+ test('の更新はdescriptionが空文字ならnullになる', async () => {
+ const clip = await successfulApiCall({
+ endpoint: 'clips/update',
+ parameters: {
+ clipId: (await create()).id,
+ name: 'updated',
+ description: '',
+ },
+ user: alice,
+ });
+
+ assert.deepStrictEqual(clip, {
+ ...clip,
+ name: 'updated',
+ description: null,
+ });
+ });
+
test('の削除ができる', async () => {
await deleteClip({
clipId: (await create()).id,
diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts
index 0322ac5546..740295bda8 100644
--- a/packages/backend/test/e2e/fetch-resource.ts
+++ b/packages/backend/test/e2e/fetch-resource.ts
@@ -6,17 +6,17 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
+import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
import type { SimpleGetResponse } from '../utils.js';
import type * as misskey from 'misskey-js';
-// Request Accept
+// Request Accept in lowercase
const ONLY_AP = 'application/activity+json';
const PREFER_AP = 'application/activity+json, */*';
const PREFER_HTML = 'text/html, */*';
const UNSPECIFIED = '*/*';
-// Response Content-Type
+// Response Content-Type in lowercase
const AP = 'application/activity+json; charset=utf-8';
const HTML = 'text/html; charset=utf-8';
const JSON_UTF8 = 'application/json; charset=utf-8';
@@ -44,7 +44,8 @@ describe('Webリソース', () => {
const { path, accept, cookie, type } = param;
const res = await simpleGet(path, accept, cookie);
assert.strictEqual(res.status, 200);
- assert.strictEqual(res.type, type ?? HTML);
+ // Header values are case-insensitive
+ assert.strictEqual(res.type?.toLowerCase(), (type ?? HTML).toLowerCase());
return res;
};
@@ -95,8 +96,7 @@ describe('Webリソース', () => {
describe.each([
{ path: '/', type: HTML },
{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
- // fastify-static gives charset=UTF-8 instead of utf-8 and that's okay
- { path: '/api-doc', type: 'text/html; charset=UTF-8' },
+ { path: '/api-doc', type: HTML },
{ path: '/api.json', type: JSON_UTF8 },
{ path: '/api-console', type: HTML },
{ path: '/_info_card_', type: HTML },
@@ -156,17 +156,17 @@ describe('Webリソース', () => {
describe(' has entry such ', () => {
beforeEach(() => {
- post(alice, { text: "**a**" });
+ post(alice, { text: '**a**' });
});
test('MFMを含まない。', async () => {
- const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text());
+ const content = await simpleGet(path(alice.username), '*/*', undefined, res => res.text());
const _body: unknown = content.body;
// JSONフィードのときは改めて文字列化する
- const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string;
+ const body: string = typeof (_body) === 'object' ? JSON.stringify(_body) : _body as string;
- if (body.includes("**a**")) {
- throw new Error("MFM shouldn't be included");
+ if (body.includes('**a**')) {
+ throw new Error('MFM shouldn\'t be included');
}
});
});
@@ -180,24 +180,6 @@ describe('Webリソース', () => {
}));
});
- describe.each([{ path: '/queue' }])('$path', ({ path }) => {
- test('はログインしないとGETできない。', async () => await notOk({
- path,
- status: 401,
- }));
-
- test('はadminでなければGETできない。', async () => await notOk({
- path,
- cookie: cookie(bob),
- status: 403,
- }));
-
- test('はadminならGETできる。', async () => await ok({
- path,
- cookie: cookie(alice),
- }));
- });
-
describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
test('はGETできない。', async () => await notOk({
path,
diff --git a/packages/backend/test/e2e/mute.ts b/packages/backend/test/e2e/mute.ts
index f37da288b7..b464c24287 100644
--- a/packages/backend/test/e2e/mute.ts
+++ b/packages/backend/test/e2e/mute.ts
@@ -51,30 +51,8 @@ describe('Mute', () => {
assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
});
- test('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async () => {
- // 状態リセット
- await api('i/read-all-unread-notes', {}, alice);
-
- await post(carol, { text: '@alice hi' });
-
- const res = await api('i', {}, alice);
-
- assert.strictEqual(res.status, 200);
- assert.strictEqual(res.body.hasUnreadMentions, false);
- });
-
- test('ミュートしているユーザーからメンションされても、ストリームに unreadMention イベントが流れてこない', async () => {
- // 状態リセット
- await api('i/read-all-unread-notes', {}, alice);
-
- const fired = await waitFire(alice, 'main', () => post(carol, { text: '@alice hi' }), msg => msg.type === 'unreadMention');
-
- assert.strictEqual(fired, false);
- });
-
test('ミュートしているユーザーからメンションされても、ストリームに unreadNotification イベントが流れてこない', async () => {
// 状態リセット
- await api('i/read-all-unread-notes', {}, alice);
await api('notifications/mark-all-as-read', {}, alice);
const fired = await waitFire(alice, 'main', () => post(carol, { text: '@alice hi' }), msg => msg.type === 'unreadNotification');
diff --git a/packages/backend/test/e2e/oauth.ts b/packages/backend/test/e2e/oauth.ts
index ef7a6a579d..f639f90ea6 100644
--- a/packages/backend/test/e2e/oauth.ts
+++ b/packages/backend/test/e2e/oauth.ts
@@ -72,11 +72,12 @@ const clientConfig: ModuleOptions<'client_id'> = {
},
};
-function getMeta(html: string): { transactionId: string | undefined, clientName: string | undefined } {
+function getMeta(html: string): { transactionId: string | undefined, clientName: string | undefined, clientLogo: string | undefined } {
const fragment = JSDOM.fragment(html);
return {
transactionId: fragment.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:transaction-id"]')?.content,
clientName: fragment.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-name"]')?.content,
+ clientLogo: fragment.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-logo"]')?.content,
};
}
@@ -915,6 +916,59 @@ describe('OAuth', () => {
assert.strictEqual(getMeta(await response.text()).clientName, `http://127.0.0.1:${clientPort}/`);
});
+ test('With Logo', async () => {
+ sender = (reply): void => {
+ reply.header('Link', '</redirect>; rel="redirect_uri"');
+ reply.send(`
+ <!DOCTYPE html>
+ <div class="h-app">
+ <a href="/" class="u-url p-name">Misklient</a>
+ <img src="/logo.png" class="u-logo" />
+ </div>
+ `);
+ reply.send();
+ };
+
+ const client = new AuthorizationCode(clientConfig);
+
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(response.status, 200);
+ const meta = getMeta(await response.text());
+ assert.strictEqual(meta.clientName, 'Misklient');
+ assert.strictEqual(meta.clientLogo, `http://127.0.0.1:${clientPort}/logo.png`);
+ });
+
+ test('Missing Logo', async () => {
+ sender = (reply): void => {
+ reply.header('Link', '</redirect>; rel="redirect_uri"');
+ reply.send(`
+ <!DOCTYPE html>
+ <div class="h-app"><a href="/" class="u-url p-name">Misklient
+ `);
+ reply.send();
+ };
+
+ const client = new AuthorizationCode(clientConfig);
+
+ const response = await fetch(client.authorizeURL({
+ redirect_uri,
+ scope: 'write:notes',
+ state: 'state',
+ code_challenge: 'code',
+ code_challenge_method: 'S256',
+ } as AuthorizationParamsExtended));
+ assert.strictEqual(response.status, 200);
+ const meta = getMeta(await response.text());
+ assert.strictEqual(meta.clientName, 'Misklient');
+ assert.strictEqual(meta.clientLogo, undefined);
+ });
+
test('Mismatching URL in h-app', async () => {
sender = (reply): void => {
reply.header('Link', '</redirect>; rel="redirect_uri"');
diff --git a/packages/backend/test/e2e/thread-mute.ts b/packages/backend/test/e2e/thread-mute.ts
index 1ac99df884..1edc178fc2 100644
--- a/packages/backend/test/e2e/thread-mute.ts
+++ b/packages/backend/test/e2e/thread-mute.ts
@@ -38,48 +38,6 @@ describe('Note thread mute', () => {
assert.strictEqual(res.body.some(note => note.id === carolReplyWithoutMention.id), false);
});
- test('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async () => {
- // 状態リセット
- await api('i/read-all-unread-notes', {}, alice);
-
- const bobNote = await post(bob, { text: '@alice @carol root note' });
-
- await api('notes/thread-muting/create', { noteId: bobNote.id }, alice);
-
- const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' });
-
- const res = await api('i', {}, alice);
-
- assert.strictEqual(res.status, 200);
- assert.strictEqual(res.body.hasUnreadMentions, false);
- });
-
- test('ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない', () => new Promise<void>(async done => {
- // 状態リセット
- await api('i/read-all-unread-notes', {}, alice);
-
- const bobNote = await post(bob, { text: '@alice @carol root note' });
-
- await api('notes/thread-muting/create', { noteId: bobNote.id }, alice);
-
- let fired = false;
-
- const ws = await connectStream(alice, 'main', async ({ type, body }) => {
- if (type === 'unreadMention') {
- if (body === bobNote.id) return;
- fired = true;
- }
- });
-
- const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' });
-
- setTimeout(() => {
- assert.strictEqual(fired, false);
- ws.close();
- done();
- }, 5000);
- }));
-
test('i/notifications にミュートしているスレッドの通知が含まれない', async () => {
const bobNote = await post(bob, { text: '@alice @carol root note' });
const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' });
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
index 319c8581f4..d6d2cb33f0 100644
--- a/packages/backend/test/e2e/timelines.ts
+++ b/packages/backend/test/e2e/timelines.ts
@@ -397,7 +397,7 @@ describe('Timelines', () => {
assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
assert.strictEqual(res.body.some(note => note.id === carolNote1.id), false);
assert.strictEqual(res.body.some(note => note.id === carolNote2.id), false);
- }, 1000 * 15);
+ }, 1000 * 30);
test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 7b21834b57..58cfab23dc 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -15,7 +15,7 @@ describe('ユーザー', () => {
// エンティティとしてのユーザーを主眼においたテストを記述する
// (Userを返すエンドポイントとUserエンティティを書き換えるエンドポイントをテストする)
- const stripUndefined = <T extends { [key: string]: any }, >(orig: T): Partial<T> => {
+ const stripUndefined = <T extends { [key: string]: any } >(orig: T): Partial<T> => {
return Object.entries({ ...orig })
.filter(([, value]) => value !== undefined)
.reduce((obj: Partial<T>, [key, value]) => {
@@ -86,6 +86,7 @@ describe('ユーザー', () => {
publicReactions: user.publicReactions,
followingVisibility: user.followingVisibility,
followersVisibility: user.followersVisibility,
+ chatScope: user.chatScope,
roles: user.roles,
memo: user.memo,
});
@@ -135,6 +136,7 @@ describe('ユーザー', () => {
hasUnreadAnnouncement: user.hasUnreadAnnouncement,
hasUnreadAntenna: user.hasUnreadAntenna,
hasUnreadChannel: user.hasUnreadChannel,
+ hasUnreadChatMessages: user.hasUnreadChatMessages,
hasUnreadNotification: user.hasUnreadNotification,
unreadNotificationsCount: user.unreadNotificationsCount,
hasPendingReceivedFollowRequest: user.hasPendingReceivedFollowRequest,
@@ -350,6 +352,7 @@ describe('ユーザー', () => {
assert.strictEqual(response.publicReactions, true);
assert.strictEqual(response.followingVisibility, 'public');
assert.strictEqual(response.followersVisibility, 'public');
+ assert.strictEqual(response.chatScope, 'mutual');
assert.deepStrictEqual(response.roles, []);
assert.strictEqual(response.memo, null);
@@ -376,6 +379,7 @@ describe('ユーザー', () => {
assert.strictEqual(response.hasUnreadAnnouncement, false);
assert.strictEqual(response.hasUnreadAntenna, false);
assert.strictEqual(response.hasUnreadChannel, false);
+ assert.strictEqual(response.hasUnreadChatMessages, false);
assert.strictEqual(response.hasUnreadNotification, false);
assert.strictEqual(response.unreadNotificationsCount, 0);
assert.strictEqual(response.hasPendingReceivedFollowRequest, false);
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
index d3302dc9bb..0bf85ef8eb 100644
--- a/packages/backend/test/misc/mock-resolver.ts
+++ b/packages/backend/test/misc/mock-resolver.ts
@@ -7,14 +7,10 @@ import type { Config } from '@/config.js';
import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import type { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import type { ApRequestService } from '@/core/activitypub/ApRequestService.js';
-import { Resolver } from '@/core/activitypub/ApResolverService.js';
import type { IObject, IObjectWithId } from '@/core/activitypub/type.js';
import type { HttpRequestService } from '@/core/HttpRequestService.js';
-import type { InstanceActorService } from '@/core/InstanceActorService.js';
import type { LoggerService } from '@/core/LoggerService.js';
-import type { MetaService } from '@/core/MetaService.js';
import type { UtilityService } from '@/core/UtilityService.js';
-import { bindThis } from '@/decorators.js';
import type {
FollowRequestsRepository,
MiMeta,
@@ -26,6 +22,9 @@ import type {
import { ApLogService } from '@/core/ApLogService.js';
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
import { fromTuple } from '@/misc/from-tuple.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
+import { bindThis } from '@/decorators.js';
+import { Resolver } from '@/core/activitypub/ApResolverService.js';
type MockResponse = {
type: string;
@@ -46,7 +45,7 @@ export class MockResolver extends Resolver {
{} as NoteReactionsRepository,
{} as FollowRequestsRepository,
{} as UtilityService,
- {} as InstanceActorService,
+ {} as SystemAccountService,
{} as ApRequestService,
{} as HttpRequestService,
{} as ApRendererService,
diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts
index 1326003c5e..6d555326fb 100644
--- a/packages/backend/test/unit/AbuseReportNotificationService.ts
+++ b/packages/backend/test/unit/AbuseReportNotificationService.ts
@@ -149,9 +149,9 @@ describe('AbuseReportNotificationService', () => {
});
beforeEach(async () => {
- 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 });
+ root = await createUser({ username: 'root', usernameLower: 'root' });
+ alice = await createUser({ username: 'alice', usernameLower: 'alice' });
+ bob = await createUser({ username: 'bob', usernameLower: 'bob' });
systemWebhook1 = await createWebhook();
systemWebhook2 = await createWebhook();
diff --git a/packages/backend/test/unit/FlashService.ts b/packages/backend/test/unit/FlashService.ts
index 12ffaf3421..f2d9832f50 100644
--- a/packages/backend/test/unit/FlashService.ts
+++ b/packages/backend/test/unit/FlashService.ts
@@ -79,9 +79,9 @@ describe('FlashService', () => {
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 });
+ root = await createUser({ username: 'root', usernameLower: 'root' });
+ alice = await createUser({ username: 'alice', usernameLower: 'alice' });
+ bob = await createUser({ username: 'bob', usernameLower: 'bob' });
});
afterEach(async () => {
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index e54c006a4f..41480fdd51 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -24,13 +24,13 @@ describe('MfmService', () => {
describe('toHtml', () => {
test('br', () => {
const input = 'foo\nbar\nbaz';
- const output = '<p><span>foo<br>bar<br>baz</span></p>';
+ const output = '<p><span>foo<br />bar<br />baz</span></p>';
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
});
test('br alt', () => {
const input = 'foo\r\nbar\rbaz';
- const output = '<p><span>foo<br>bar<br>baz</span></p>';
+ const output = '<p><span>foo<br />bar<br />baz</span></p>';
assert.equal(mfmService.toHtml(mfm.parse(input)), output);
});
diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts
index 9676abf07b..074430dd31 100644
--- a/packages/backend/test/unit/RelayService.ts
+++ b/packages/backend/test/unit/RelayService.ts
@@ -6,19 +6,18 @@
process.env.NODE_ENV = 'test';
import { jest } from '@jest/globals';
-import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
-import { GlobalModule } from '@/GlobalModule.js';
-import { RelayService } from '@/core/RelayService.js';
+import { ModuleMocker } from 'jest-mock';
+import type { TestingModule } from '@nestjs/testing';
+import type { MockFunctionMetadata } from 'jest-mock';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
-import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { QueueService } from '@/core/QueueService.js';
import { IdService } from '@/core/IdService.js';
-import type { RelaysRepository } from '@/models/_.js';
-import { DI } from '@/di-symbols.js';
-import type { TestingModule } from '@nestjs/testing';
-import type { MockFunctionMetadata } from 'jest-mock';
+import { QueueService } from '@/core/QueueService.js';
+import { RelayService } from '@/core/RelayService.js';
+import { SystemAccountService } from '@/core/SystemAccountService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { UtilityService } from '@/core/UtilityService.js';
const moduleMocker = new ModuleMocker(global);
@@ -26,8 +25,6 @@ describe('RelayService', () => {
let app: TestingModule;
let relayService: RelayService;
let queueService: jest.Mocked<QueueService>;
- let relaysRepository: RelaysRepository;
- let userEntityService: UserEntityService;
beforeAll(async () => {
app = await Test.createTestingModule({
@@ -36,10 +33,11 @@ describe('RelayService', () => {
],
providers: [
IdService,
- CreateSystemUserService,
ApRendererService,
RelayService,
UserEntityService,
+ SystemAccountService,
+ UtilityService,
],
})
.useMocker((token) => {
@@ -58,8 +56,6 @@ describe('RelayService', () => {
relayService = app.get<RelayService>(RelayService);
queueService = app.get<QueueService>(QueueService) as jest.Mocked<QueueService>;
- relaysRepository = app.get<RelaysRepository>(DI.relaysRepository);
- userEntityService = app.get<UserEntityService>(UserEntityService);
});
afterAll(async () => {
diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts
index 9c1b1008d6..553ff0982a 100644
--- a/packages/backend/test/unit/RoleService.ts
+++ b/packages/backend/test/unit/RoleService.ts
@@ -57,6 +57,12 @@ describe('RoleService', () => {
return await usersRepository.findOneByOrFail(x.identifiers[0]);
}
+ async function createRoot(data: Partial<MiUser> = {}) {
+ const user = await createUser(data);
+ meta.rootUserId = user.id;
+ return user;
+ }
+
async function createRole(data: Partial<MiRole> = {}) {
const x = await rolesRepository.insert({
id: genAidx(Date.now()),
@@ -279,7 +285,7 @@ describe('RoleService', () => {
describe('getModeratorIds', () => {
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 }),
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -305,7 +311,7 @@ describe('RoleService', () => {
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 }),
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -331,7 +337,7 @@ describe('RoleService', () => {
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 }),
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -357,7 +363,7 @@ describe('RoleService', () => {
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 }),
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -383,7 +389,7 @@ describe('RoleService', () => {
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 }),
+ createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -409,7 +415,7 @@ describe('RoleService', () => {
test('root has moderator role', async () => {
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
- createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+ createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -433,7 +439,7 @@ describe('RoleService', () => {
test('root has administrator role', async () => {
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
- createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+ createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -457,7 +463,7 @@ describe('RoleService', () => {
test('root has moderator role(expire)', async () => {
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
- createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+ createUser(), createUser(), createUser(), createRoot(),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
diff --git a/packages/backend/test/unit/SystemWebhookService.ts b/packages/backend/test/unit/SystemWebhookService.ts
index fee4acb305..61187e9f2a 100644
--- a/packages/backend/test/unit/SystemWebhookService.ts
+++ b/packages/backend/test/unit/SystemWebhookService.ts
@@ -97,7 +97,7 @@ describe('SystemWebhookService', () => {
}
async function beforeEachImpl() {
- root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
+ root = await createUser({ username: 'root', usernameLower: 'root' });
}
async function afterEachImpl() {
diff --git a/packages/backend/test/unit/UserSearchService.ts b/packages/backend/test/unit/UserSearchService.ts
index 7ea325d420..66a7f39ff1 100644
--- a/packages/backend/test/unit/UserSearchService.ts
+++ b/packages/backend/test/unit/UserSearchService.ts
@@ -113,7 +113,7 @@ describe('UserSearchService', () => {
});
beforeEach(async () => {
- root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
+ root = await createUser({ username: 'root', usernameLower: 'root' });
alice = await createUser({ username: 'Alice', usernameLower: 'alice' });
alyce = await createUser({ username: 'Alyce', usernameLower: 'alyce' });
alycia = await createUser({ username: 'Alycia', usernameLower: 'alycia' });
diff --git a/packages/backend/test/unit/UserWebhookService.ts b/packages/backend/test/unit/UserWebhookService.ts
index db8f96df28..a2a85e9489 100644
--- a/packages/backend/test/unit/UserWebhookService.ts
+++ b/packages/backend/test/unit/UserWebhookService.ts
@@ -91,7 +91,7 @@ describe('UserWebhookService', () => {
}
async function beforeEachImpl() {
- root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
+ root = await createUser({ username: 'root', usernameLower: 'root' });
}
async function afterEachImpl() {
diff --git a/packages/backend/test/unit/WebhookTestService.ts b/packages/backend/test/unit/WebhookTestService.ts
index be84ae9b84..736aac40b4 100644
--- a/packages/backend/test/unit/WebhookTestService.ts
+++ b/packages/backend/test/unit/WebhookTestService.ts
@@ -14,6 +14,7 @@ import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersReposi
import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js';
import { QueueService } from '@/core/QueueService.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
describe('WebhookTestService', () => {
let app: TestingModule;
@@ -57,6 +58,11 @@ describe('WebhookTestService', () => {
WebhookTestService,
IdService,
{
+ provide: CustomEmojiService, useFactory: () => ({
+ populateEmojis: jest.fn(),
+ }),
+ },
+ {
provide: QueueService, useFactory: () => ({
systemWebhookDeliver: jest.fn(),
userWebhookDeliver: jest.fn(),
@@ -88,8 +94,8 @@ describe('WebhookTestService', () => {
});
beforeEach(async () => {
- root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
- alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
+ root = await createUser({ username: 'root', usernameLower: 'root' });
+ alice = await createUser({ username: 'alice', usernameLower: 'alice' });
userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
{ id: 'dummy-webhook', active: true, userId: alice.id } as MiWebhook,
diff --git a/packages/backend/test/unit/ap-request.ts b/packages/backend/test/unit/ap-request.ts
index d3d39240dc..f8b2a697f2 100644
--- a/packages/backend/test/unit/ap-request.ts
+++ b/packages/backend/test/unit/ap-request.ts
@@ -8,6 +8,8 @@ import httpSignature from '@peertube/http-signature';
import { genRsaKeyPair } from '@/misc/gen-key-pair.js';
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
+import { assertActivityMatchesUrl, FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
+import { IObject } from '@/core/activitypub/type.js';
export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
return {
@@ -24,6 +26,10 @@ export const buildParsedSignature = (signingString: string, signature: string, a
};
};
+function cartesianProduct<T, U>(a: T[], b: U[]): [T, U][] {
+ return a.flatMap(a => b.map(b => [a, b] as [T, U]));
+}
+
describe('ap-request', () => {
test('createSignedPost with verify', async () => {
const keypair = await genRsaKeyPair();
@@ -58,4 +64,108 @@ describe('ap-request', () => {
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
assert.deepStrictEqual(result, true);
});
+
+ test('rejects non matching domain', () => {
+ assert.doesNotThrow(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/abc',
+ { id: 'https://alice.example.com/abc' } as IObject,
+ 'https://alice.example.com/abc',
+ FetchAllowSoftFailMask.Strict,
+ ), 'validation should pass base case');
+ assert.throws(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/abc',
+ { id: 'https://bob.example.com/abc' } as IObject,
+ 'https://alice.example.com/abc',
+ FetchAllowSoftFailMask.Any,
+ ), 'validation should fail no matter what if the response URL is inconsistent with the object ID');
+
+ assert.doesNotThrow(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/abc#test',
+ { id: 'https://alice.example.com/abc' } as IObject,
+ 'https://alice.example.com/abc',
+ FetchAllowSoftFailMask.Strict,
+ ), 'validation should pass with hash in request URL');
+
+ // fix issues like threads
+ // https://github.com/misskey-dev/misskey/issues/15039
+ const withOrWithoutWWW = [
+ 'https://alice.example.com/abc',
+ 'https://www.alice.example.com/abc',
+ ];
+
+ cartesianProduct(
+ cartesianProduct(
+ withOrWithoutWWW,
+ withOrWithoutWWW,
+ ),
+ withOrWithoutWWW,
+ ).forEach(([[a, b], c]) => {
+ assert.doesNotThrow(() => assertActivityMatchesUrl(
+ a,
+ { id: b } as IObject,
+ c,
+ FetchAllowSoftFailMask.Strict,
+ ), 'validation should pass with or without www. subdomain');
+ });
+ });
+
+ test('cross origin lookup', () => {
+ assert.doesNotThrow(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/abc',
+ { id: 'https://bob.example.com/abc' } as IObject,
+ 'https://bob.example.com/abc',
+ FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId,
+ ), 'validation should pass if the response is otherwise consistent and cross-origin is allowed');
+ assert.throws(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/abc',
+ { id: 'https://bob.example.com/abc' } as IObject,
+ 'https://bob.example.com/abc',
+ FetchAllowSoftFailMask.Strict,
+ ), 'validation should fail if the response is otherwise consistent and cross-origin is not allowed');
+ });
+
+ test('rejects non-canonical ID', () => {
+ assert.throws(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/@alice',
+ { id: 'https://alice.example.com/users/alice' } as IObject,
+ 'https://alice.example.com/users/alice',
+ FetchAllowSoftFailMask.Strict,
+ ), 'throws if the response ID did not exactly match the expected ID');
+ assert.doesNotThrow(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/@alice',
+ { id: 'https://alice.example.com/users/alice' } as IObject,
+ 'https://alice.example.com/users/alice',
+ FetchAllowSoftFailMask.NonCanonicalId,
+ ), 'does not throw if non-canonical ID is allowed');
+ });
+
+ test('origin relaxed alignment', () => {
+ assert.doesNotThrow(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/abc',
+ { id: 'https://ap.alice.example.com/abc' } as IObject,
+ 'https://ap.alice.example.com/abc',
+ FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
+ ), 'validation should pass if response is a subdomain of the expected origin');
+ assert.throws(() => assertActivityMatchesUrl(
+ 'https://alice.multi-tenant.example.com/abc',
+ { id: 'https://alice.multi-tenant.example.com/abc' } as IObject,
+ 'https://bob.multi-tenant.example.com/abc',
+ FetchAllowSoftFailMask.MisalignedOrigin | FetchAllowSoftFailMask.NonCanonicalId,
+ ), 'validation should fail if response is a disjoint domain of the expected origin');
+ assert.throws(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/abc',
+ { id: 'https://ap.alice.example.com/abc' } as IObject,
+ 'https://ap.alice.example.com/abc',
+ FetchAllowSoftFailMask.Strict,
+ ), 'throws if relaxed origin is forbidden');
+ });
+
+ test('resist HTTP downgrade', () => {
+ assert.throws(() => assertActivityMatchesUrl(
+ 'https://alice.example.com/abc',
+ { id: 'https://alice.example.com/abc' } as IObject,
+ 'http://alice.example.com/abc',
+ FetchAllowSoftFailMask.Strict,
+ ), 'throws if HTTP downgrade is detected');
+ });
});
diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts
index e4f42809f8..6b7eedff55 100644
--- a/packages/backend/test/unit/entities/UserEntityService.ts
+++ b/packages/backend/test/unit/entities/UserEntityService.ts
@@ -50,6 +50,7 @@ import { AccountMoveService } from '@/core/AccountMoveService.js';
import { ReactionService } from '@/core/ReactionService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
+import { ChatService } from '@/core/ChatService.js';
process.env.NODE_ENV = 'test';
@@ -172,6 +173,7 @@ describe('UserEntityService', () => {
ReactionService,
ReactionsBufferingService,
NotificationService,
+ ChatService,
];
app = await Test.createTestingModule({
diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
index d96e6b916a..07618e7762 100644
--- a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
+++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -316,7 +316,7 @@ describe('CheckModeratorsActivityProcessorService', () => {
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 }),
+ createUser({}, { email: 'root@example.com', emailVerified: true }),
]);
mockModeratorRole([user1, user2, user3, root]);
@@ -349,7 +349,7 @@ describe('CheckModeratorsActivityProcessorService', () => {
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 }),
+ createUser({}, { email: 'root@example.com', emailVerified: true }),
]);
mockModeratorRole([user1, user2, user3, root]);
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index cf97473d14..8306208477 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -35,7 +35,7 @@ export type SystemWebhookPayload = {
createdAt: string;
type: string;
body: any;
-}
+};
const config = loadConfig();
export const port = config.port;
@@ -45,10 +45,6 @@ export const host = new URL(config.url).host;
export const WEBHOOK_HOST = 'http://localhost:15080';
export const WEBHOOK_PORT = 15080;
-export const cookie = (me: UserToken): string => {
- return `token=${me.token};`;
-};
-
export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = {
endpoint: E,
parameters: P,
diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js
index e686da9cc3..fbe91f3660 100644
--- a/packages/frontend-embed/eslint.config.js
+++ b/packages/frontend-embed/eslint.config.js
@@ -52,6 +52,7 @@ export default [
'@typescript-eslint/no-empty-interface': ['error', {
allowSingleExtends: true,
}],
+ 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
'id-denylist': ['error', 'window', 'e'],
diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json
index 163e6096f8..e6bed380e7 100644
--- a/packages/frontend-embed/package.json
+++ b/packages/frontend-embed/package.json
@@ -13,59 +13,58 @@
"@discordapp/twemoji": "15.1.0",
"@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-json": "6.1.0",
- "@rollup/plugin-replace": "5.0.7",
- "@rollup/pluginutils": "5.1.3",
+ "@rollup/plugin-replace": "6.0.2",
+ "@rollup/pluginutils": "5.1.4",
"@transfem-org/sfm-js": "0.24.5",
"@twemoji/parser": "15.1.1",
- "@vitejs/plugin-vue": "5.2.0",
- "@vue/compiler-sfc": "3.5.12",
+ "@vitejs/plugin-vue": "5.2.3",
+ "@vue/compiler-sfc": "3.5.13",
"astring": "1.9.0",
"buraha": "0.0.1",
"estree-walker": "3.0.3",
"misskey-js": "workspace:*",
"frontend-shared": "workspace:*",
"punycode.js": "2.3.1",
- "rollup": "4.26.0",
- "sass": "1.79.4",
- "shiki": "1.22.2",
+ "rollup": "4.36.0",
+ "sass": "1.86.0",
+ "shiki": "3.2.1",
"tinycolor2": "1.6.0",
- "tsc-alias": "1.8.10",
+ "tsc-alias": "1.8.11",
"tsconfig-paths": "4.2.0",
- "typescript": "5.6.3",
- "uuid": "10.0.0",
+ "typescript": "5.8.2",
+ "uuid": "11.1.0",
"json5": "2.2.3",
- "vite": "5.4.11",
- "vue": "3.5.12"
+ "vite": "6.2.2",
+ "vue": "3.5.13"
},
"devDependencies": {
- "@misskey-dev/summaly": "5.1.0",
+ "@misskey-dev/summaly": "5.2.0",
"@testing-library/vue": "8.1.0",
"@types/estree": "1.0.6",
"@types/micromatch": "4.0.9",
- "@types/node": "22.9.0",
+ "@types/node": "22.13.11",
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6",
- "@types/uuid": "10.0.0",
- "@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.12",
- "acorn": "8.14.0",
+ "@types/ws": "8.18.0",
+ "@typescript-eslint/eslint-plugin": "8.27.0",
+ "@typescript-eslint/parser": "8.27.0",
+ "@vitest/coverage-v8": "3.0.9",
+ "@vue/runtime-core": "3.5.13",
+ "acorn": "8.14.1",
"cross-env": "7.0.3",
"eslint-plugin-import": "2.31.0",
- "eslint-plugin-vue": "9.31.0",
- "fast-glob": "3.3.2",
- "happy-dom": "10.0.3",
+ "eslint-plugin-vue": "10.0.0",
+ "fast-glob": "3.3.3",
+ "happy-dom": "17.4.4",
"intersection-observer": "0.12.2",
"micromatch": "4.0.8",
- "msw": "2.6.4",
- "nodemon": "3.1.7",
- "prettier": "3.3.3",
- "start-server-and-test": "2.0.8",
+ "msw": "2.7.3",
+ "nodemon": "3.1.9",
+ "prettier": "3.5.3",
+ "start-server-and-test": "2.0.11",
"vite-plugin-turbosnap": "1.0.3",
- "vue-component-type-helpers": "2.1.10",
- "vue-eslint-parser": "9.4.3",
- "vue-tsc": "2.1.10"
+ "vue-component-type-helpers": "2.2.8",
+ "vue-eslint-parser": "10.1.1",
+ "vue-tsc": "2.2.8"
}
}
diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
index bf976c71ae..0bff048ce4 100644
--- a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
+++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
@@ -33,13 +33,11 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
Math.min(navigator.hardwareConcurrency - 1, 4),
);
resolve(workers);
- if (_DEV_) console.log('WebGL2 in worker is supported!');
} else {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
- if (_DEV_) console.log('WebGL2 in worker is not supported...');
}
testWorker.terminate();
});
diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts
index 40189133d2..d377d492e0 100644
--- a/packages/frontend-embed/src/components/EmMfm.ts
+++ b/packages/frontend-embed/src/components/EmMfm.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { VNode, h, SetupContext, provide } from 'vue';
+import { h, provide } from 'vue';
+import type { VNode, SetupContext } from 'vue';
import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue
index 4e0ae005df..962a982fb7 100644
--- a/packages/frontend-embed/src/components/EmNotes.vue
+++ b/packages/frontend-embed/src/components/EmNotes.vue
@@ -22,7 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import EmNote from '@/components/EmNote.vue';
-import EmPagination, { Paging } from '@/components/EmPagination.vue';
+import EmPagination from '@/components/EmPagination.vue';
+import type { Paging } from '@/components/EmPagination.vue';
import { i18n } from '@/i18n.js';
import * as Misskey from 'misskey-js';
diff --git a/packages/frontend-embed/src/components/EmPagination.vue b/packages/frontend-embed/src/components/EmPagination.vue
index 5d5317a912..4cf156ba23 100644
--- a/packages/frontend-embed/src/components/EmPagination.vue
+++ b/packages/frontend-embed/src/components/EmPagination.vue
@@ -34,10 +34,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
-import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
+import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
-import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
+import { onScrollTop, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible, isHeadVisible } from '@@/js/scroll.js';
+import type { ComputedRef } from 'vue';
import { misskeyApi } from '@/misskey-api.js';
import { i18n } from '@/i18n.js';
@@ -62,8 +63,6 @@ export type Paging<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints>
reversed?: boolean;
offsetMode?: boolean;
-
- pageEl?: HTMLElement;
};
type MisskeyEntity = {
@@ -135,8 +134,7 @@ const isBackTop = ref(false);
const empty = computed(() => items.value.size === 0);
const error = ref(false);
-const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value);
-const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body);
+const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : document.body);
const visibility = useDocumentVisibility();
@@ -167,11 +165,11 @@ watch(rootEl, () => {
});
});
-watch([backed, contentEl], () => {
+watch([backed, rootEl], () => {
if (!backed.value) {
- if (!contentEl.value) return;
+ if (!rootEl.value) return;
- scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl.value, executeQueue, TOLERANCE);
+ scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(rootEl.value, executeQueue, TOLERANCE);
} else {
if (scrollRemove.value) scrollRemove.value();
scrollRemove.value = null;
@@ -344,7 +342,7 @@ const appearFetchMoreAhead = async (): Promise<void> => {
fetchMoreAppearTimeout();
};
-const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE);
+const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isTailVisible : isHeadVisible)(rootEl.value!, TOLERANCE);
watch(visibility, () => {
if (visibility.value === 'hidden') {
@@ -442,7 +440,7 @@ onDeactivated(() => {
});
function toBottom() {
- scrollToBottom(contentEl.value!);
+ scrollToBottom(rootEl.value!);
}
onBeforeMount(() => {
diff --git a/packages/frontend-embed/src/components/EmReactionsViewer.vue b/packages/frontend-embed/src/components/EmReactionsViewer.vue
index 014dd1c935..f5aa6bdc3f 100644
--- a/packages/frontend-embed/src/components/EmReactionsViewer.vue
+++ b/packages/frontend-embed/src/components/EmReactionsViewer.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { inject, watch, ref } from 'vue';
+import { watch, ref } from 'vue';
import XReaction from '@/components/EmReactionsViewer.reaction.vue';
const props = withDefaults(defineProps<{
@@ -22,12 +22,6 @@ const props = withDefaults(defineProps<{
maxNumber: Infinity,
});
-const mock = inject<boolean>('mock', false);
-
-const emit = defineEmits<{
- (ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
-}>();
-
const initialReactions = new Set(Object.keys(props.note.reactions));
const reactions = ref<[string, number][]>([]);
@@ -38,12 +32,8 @@ if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.m
}
function onMockToggleReaction(emoji: string, count: number) {
- if (!mock) return;
-
const i = reactions.value.findIndex((item) => item[0] === emoji);
if (i < 0) return;
-
- emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1]));
}
watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => {
diff --git a/packages/frontend-embed/src/pages/not-found.vue b/packages/frontend-embed/src/pages/not-found.vue
index bbb03b4e64..061254a39a 100644
--- a/packages/frontend-embed/src/pages/not-found.vue
+++ b/packages/frontend-embed/src/pages/not-found.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
<div class="_fullinfo">
- <img :src="notFoundImageUrl" class="_ghost"/>
+ <img :src="notFoundImageUrl" draggable="false"/>
<div>{{ i18n.ts.notFoundDescription }}</div>
</div>
</div>
@@ -20,5 +20,5 @@ import { i18n } from '@/i18n.js';
const serverMetadata = inject(DI.serverMetadata)!;
-const notFoundImageUrl = computed(() => serverMetadata?.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
+const notFoundImageUrl = computed(() => serverMetadata.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
</script>
diff --git a/packages/frontend-embed/src/post-message.ts b/packages/frontend-embed/src/post-message.ts
index fd8eb8a5d2..93b57c380b 100644
--- a/packages/frontend-embed/src/post-message.ts
+++ b/packages/frontend-embed/src/post-message.ts
@@ -21,7 +21,7 @@ export type MiPostMessageEvent<T extends PostMessageEventType = PostMessageEvent
type: T;
iframeId?: string;
payload?: PostMessageEventPayload[T];
-}
+};
let defaultIframeId: string | null = null;
diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json
index ff04a689bf..e0ee08188d 100644
--- a/packages/frontend-embed/tsconfig.json
+++ b/packages/frontend-embed/tsconfig.json
@@ -21,6 +21,7 @@
"allowSyntheticDefaultImports": true,
"isolatedModules": true,
"useDefineForClassFields": true,
+ "verbatimModuleSyntax": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
diff --git a/packages/frontend-shared/eslint.config.js b/packages/frontend-shared/eslint.config.js
index 689f7870c0..ff4d27443b 100644
--- a/packages/frontend-shared/eslint.config.js
+++ b/packages/frontend-shared/eslint.config.js
@@ -52,6 +52,7 @@ export default [
'@typescript-eslint/no-empty-interface': ['error', {
allowSingleExtends: true,
}],
+ 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
'id-denylist': ['error', 'window', 'e'],
@@ -99,6 +100,10 @@ export default [
},
{
ignores: [
+ // TODO: Error while loading rule '@typescript-eslint/naming-convention': Cannot use 'in' operator to search for 'type' in undefined のため一時的に無効化
+ // See https://github.com/misskey-dev/misskey/pull/15311
+ 'js/i18n.ts',
+ 'js-built/',
"**/lib/",
"**/temp/",
"**/built/",
diff --git a/packages/frontend-shared/js/config.ts b/packages/frontend-shared/js/config.ts
index 9a23e0e7f3..4c739e6e64 100644
--- a/packages/frontend-shared/js/config.ts
+++ b/packages/frontend-shared/js/config.ts
@@ -12,6 +12,7 @@ const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site
export const host = address.host;
export const hostname = address.hostname;
export const url = address.origin;
+export const port = address.port;
export const apiUrl = location.origin + '/api';
export const wsOrigin = location.origin;
export const lang = localStorage.getItem('lang') ?? 'en-US';
diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts
index 0dac166749..f20152d08e 100644
--- a/packages/frontend-shared/js/const.ts
+++ b/packages/frontend-shared/js/const.ts
@@ -129,6 +129,7 @@ export const notificationTypes = [
'achievementEarned',
'exportCompleted',
'login',
+ 'createToken',
'test',
'app',
'edited',
@@ -172,15 +173,10 @@ export const ROLE_POLICIES = [
'canImportFollowing',
'canImportMuting',
'canImportUserLists',
+ 'canChat',
] as const;
-// なんか動かない
-//export const CURRENT_STICKY_TOP = Symbol('CURRENT_STICKY_TOP');
-//export const CURRENT_STICKY_BOTTOM = Symbol('CURRENT_STICKY_BOTTOM');
-export const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP';
-export const CURRENT_STICKY_BOTTOM = 'CURRENT_STICKY_BOTTOM';
-
-export const DEFAULT_SERVER_ERROR_IMAGE_URL = '/client-assets/status/error.png';
+export const DEFAULT_SERVER_ERROR_IMAGE_URL = '/client-assets/status/error.jpg';
export const DEFAULT_NOT_FOUND_IMAGE_URL = '/client-assets/status/missingpage.webp';
export const DEFAULT_INFO_IMAGE_URL = '/client-assets/status/nothinghere.png';
diff --git a/packages/frontend-shared/js/embed-page.ts b/packages/frontend-shared/js/embed-page.ts
index d5555a98c3..2a9b7eb478 100644
--- a/packages/frontend-shared/js/embed-page.ts
+++ b/packages/frontend-shared/js/embed-page.ts
@@ -6,7 +6,7 @@
//#region Embed関連の定義
/** 埋め込みの対象となるエンティティ(/embed/xxx の xxx の部分と対応させる) */
-const embeddableEntities = [
+export const embeddableEntities = [
'notes',
'user-timeline',
'clips',
diff --git a/packages/frontend-shared/js/emojilist.ts b/packages/frontend-shared/js/emojilist.ts
index bde30a864f..f8bbf39177 100644
--- a/packages/frontend-shared/js/emojilist.ts
+++ b/packages/frontend-shared/js/emojilist.ts
@@ -9,10 +9,10 @@ export type UnicodeEmojiDef = {
name: string;
char: string;
category: typeof unicodeEmojiCategories[number];
-}
+};
// initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb
-import _emojilist from './emojilist.json';
+import _emojilist from './emojilist.json' with { type: 'json' };
export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({
name: x[1] as string,
diff --git a/packages/frontend-shared/js/i18n.ts b/packages/frontend-shared/js/i18n.ts
index 18232691fa..480cfcd642 100644
--- a/packages/frontend-shared/js/i18n.ts
+++ b/packages/frontend-shared/js/i18n.ts
@@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
+
import type { ILocale, ParameterizedString } from '../../../locales/index.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/packages/frontend-shared/js/scroll.ts b/packages/frontend-shared/js/scroll.ts
index 4f2e9105c3..6c61c582e1 100644
--- a/packages/frontend-shared/js/scroll.ts
+++ b/packages/frontend-shared/js/scroll.ts
@@ -38,7 +38,7 @@ export function getScrollPosition(el: HTMLElement | null): number {
export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknown, tolerance = 1, once = false) {
// とりあえず評価してみる
- const firstTopVisible = isTopVisible(el);
+ const firstTopVisible = isHeadVisible(el);
if (el.isConnected && firstTopVisible) {
cb(firstTopVisible);
if (once) return null;
@@ -53,7 +53,7 @@ export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknow
const onScroll = () => {
if (!document.body.contains(el)) return;
- const topVisible = isTopVisible(el, tolerance);
+ const topVisible = isHeadVisible(el, tolerance);
if (topVisible !== prevTopVisible) {
prevTopVisible = topVisible;
cb(topVisible);
@@ -71,7 +71,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1
const container = getScrollContainer(el);
// とりあえず評価してみる
- if (el.isConnected && isBottomVisible(el, tolerance, container)) {
+ if (el.isConnected && isTailVisible(el, tolerance, container)) {
cb();
if (once) return null;
}
@@ -79,7 +79,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1
const containerOrWindow = container ?? window;
const onScroll = () => {
if (!document.body.contains(el)) return;
- if (isBottomVisible(el, 1, container)) {
+ if (isTailVisible(el, 1, container)) {
cb();
if (once) removeListener();
}
@@ -132,13 +132,12 @@ export function scrollToBottom(
}
}
-export function isTopVisible(el: HTMLElement, tolerance = 1): boolean {
+export function isHeadVisible(el: HTMLElement, tolerance = 1): boolean {
const scrollTop = getScrollPosition(el);
- if (_DEV_) console.log(scrollTop, tolerance, scrollTop <= tolerance);
return scrollTop <= tolerance;
}
-export function isBottomVisible(el: HTMLElement, tolerance = 1, container = getScrollContainer(el)) {
+export function isTailVisible(el: HTMLElement, tolerance = 1, container = getScrollContainer(el)) {
if (container) return el.scrollHeight <= container.clientHeight + Math.abs(container.scrollTop) + tolerance;
return el.scrollHeight <= window.innerHeight + window.scrollY + tolerance;
}
diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json
index 6afd4e8a23..b9769cc670 100644
--- a/packages/frontend-shared/package.json
+++ b/packages/frontend-shared/package.json
@@ -21,14 +21,14 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
- "@types/node": "22.9.0",
- "@typescript-eslint/eslint-plugin": "7.17.0",
- "@typescript-eslint/parser": "7.17.0",
- "esbuild": "0.24.0",
- "eslint-plugin-vue": "9.31.0",
- "nodemon": "3.1.7",
- "typescript": "5.6.3",
- "vue-eslint-parser": "9.4.3"
+ "@types/node": "22.13.11",
+ "@typescript-eslint/eslint-plugin": "8.27.0",
+ "@typescript-eslint/parser": "8.27.0",
+ "esbuild": "0.25.1",
+ "eslint-plugin-vue": "10.0.0",
+ "nodemon": "3.1.9",
+ "typescript": "5.8.2",
+ "vue-eslint-parser": "10.1.1"
},
"files": [
"js-built"
@@ -36,6 +36,6 @@
"dependencies": {
"misskey-js": "workspace:*",
"nodemon": "3.1.7",
- "vue": "3.5.12"
+ "vue": "3.5.13"
}
}
diff --git a/packages/frontend-shared/themes/_dark.json5 b/packages/frontend-shared/themes/_dark.json5
index b3f1ab824b..1f7620762c 100644
--- a/packages/frontend-shared/themes/_dark.json5
+++ b/packages/frontend-shared/themes/_dark.json5
@@ -80,14 +80,6 @@
codeBoolean: '#c59eff',
deckBg: '#000',
htmlThemeColor: '@bg',
- X3: 'rgba(255, 255, 255, 0.05)',
- X4: 'rgba(255, 255, 255, 0.1)',
- X5: 'rgba(255, 255, 255, 0.05)',
- X6: 'rgba(255, 255, 255, 0.15)',
- X7: 'rgba(255, 255, 255, 0.05)',
- X11: 'rgba(0, 0, 0, 0.3)',
- X12: 'rgba(255, 255, 255, 0.1)',
- X13: 'rgba(255, 255, 255, 0.15)',
},
codeHighlighter: {
diff --git a/packages/frontend-shared/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5
index 89c632b057..87d921d43b 100644
--- a/packages/frontend-shared/themes/_light.json5
+++ b/packages/frontend-shared/themes/_light.json5
@@ -80,14 +80,6 @@
codeBoolean: '#62b70c',
deckBg: ':darken<3<@bg',
htmlThemeColor: '@bg',
- X3: 'rgba(0, 0, 0, 0.05)',
- X4: 'rgba(0, 0, 0, 0.1)',
- X5: 'rgba(0, 0, 0, 0.05)',
- X6: 'rgba(0, 0, 0, 0.25)',
- X7: 'rgba(0, 0, 0, 0.05)',
- X11: 'rgba(0, 0, 0, 0.1)',
- X12: 'rgba(0, 0, 0, 0.1)',
- X13: 'rgba(0, 0, 0, 0.15)',
},
codeHighlighter: {
diff --git a/packages/frontend-shared/themes/d-astro.json5 b/packages/frontend-shared/themes/d-astro.json5
index 4422526a33..e8864df336 100644
--- a/packages/frontend-shared/themes/d-astro.json5
+++ b/packages/frontend-shared/themes/d-astro.json5
@@ -54,13 +54,5 @@
wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
panelHeaderDivider: 'rgba(0, 0, 0, 0)',
scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
- X3: 'rgba(255, 255, 255, 0.05)',
- X4: 'rgba(255, 255, 255, 0.1)',
- X5: 'rgba(255, 255, 255, 0.05)',
- X6: 'rgba(255, 255, 255, 0.15)',
- X7: 'rgba(255, 255, 255, 0.05)',
- X11: 'rgba(0, 0, 0, 0.3)',
- X12: 'rgba(255, 255, 255, 0.1)',
- X13: 'rgba(255, 255, 255, 0.15)',
},
}
diff --git a/packages/frontend-shared/themes/d-u0.json5 b/packages/frontend-shared/themes/d-u0.json5
index fb707c74c3..0223b1fb5c 100644
--- a/packages/frontend-shared/themes/d-u0.json5
+++ b/packages/frontend-shared/themes/d-u0.json5
@@ -3,17 +3,9 @@
base: 'dark',
name: 'Mi U0 Dark',
props: {
- X3: 'rgba(255, 255, 255, 0.05)',
- X4: 'rgba(255, 255, 255, 0.1)',
- X5: 'rgba(255, 255, 255, 0.05)',
- X6: 'rgba(255, 255, 255, 0.15)',
- X7: 'rgba(255, 255, 255, 0.05)',
bg: '#172426',
fg: '#dadada',
X10: ':alpha<0.4<@accent',
- X11: 'rgba(0, 0, 0, 0.3)',
- X12: 'rgba(255, 255, 255, 0.1)',
- X13: 'rgba(255, 255, 255, 0.15)',
X14: ':alpha<0.5<@navBg',
X15: ':alpha<0<@panel',
X16: ':alpha<0.7<@panel',
diff --git a/packages/frontend-shared/themes/l-u0.json5 b/packages/frontend-shared/themes/l-u0.json5
index 7062e7fe5b..f6023af819 100644
--- a/packages/frontend-shared/themes/l-u0.json5
+++ b/packages/frontend-shared/themes/l-u0.json5
@@ -3,17 +3,9 @@
base: 'light',
name: 'Mi U0 Light',
props: {
- X3: 'rgba(255, 255, 255, 0.05)',
- X4: 'rgba(255, 255, 255, 0.1)',
- X5: 'rgba(255, 255, 255, 0.05)',
- X6: 'rgba(255, 255, 255, 0.15)',
- X7: 'rgba(255, 255, 255, 0.05)',
bg: '#e7e7eb',
fg: '#5f5f5f',
X10: ':alpha<0.4<@accent',
- X11: 'rgba(0, 0, 0, 0.3)',
- X12: 'rgba(255, 255, 255, 0.1)',
- X13: 'rgba(255, 255, 255, 0.15)',
X14: ':alpha<0.5<@navBg',
X15: ':alpha<0<@panel',
X16: ':alpha<0.7<@panel',
diff --git a/packages/frontend-shared/themes/l-vivid.json5 b/packages/frontend-shared/themes/l-vivid.json5
index 39768d4ac6..058c9c32e5 100644
--- a/packages/frontend-shared/themes/l-vivid.json5
+++ b/packages/frontend-shared/themes/l-vivid.json5
@@ -57,13 +57,5 @@
fgTransparentWeak: ':alpha<0.75<@fg',
panelHeaderDivider: '@divider',
scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)',
- X3: 'rgba(0, 0, 0, 0.05)',
- X4: 'rgba(0, 0, 0, 0.1)',
- X5: 'rgba(0, 0, 0, 0.05)',
- X6: 'rgba(0, 0, 0, 0.25)',
- X7: 'rgba(0, 0, 0, 0.05)',
- X11: 'rgba(0, 0, 0, 0.1)',
- X12: 'rgba(0, 0, 0, 0.1)',
- X13: 'rgba(0, 0, 0, 0.15)',
},
}
diff --git a/packages/frontend-shared/tsconfig.json b/packages/frontend-shared/tsconfig.json
index 48228d4e48..8f76763e10 100644
--- a/packages/frontend-shared/tsconfig.json
+++ b/packages/frontend-shared/tsconfig.json
@@ -16,6 +16,7 @@
"experimentalDecorators": true,
"noImplicitReturns": true,
"esModuleInterop": true,
+ "verbatimModuleSyntax": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
diff --git a/packages/frontend/.storybook/charts.ts b/packages/frontend/.storybook/charts.ts
index 5015012a82..31bb9e51c5 100644
--- a/packages/frontend/.storybook/charts.ts
+++ b/packages/frontend/.storybook/charts.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { DefaultBodyType, HttpResponse, HttpResponseResolver, JsonBodyType, PathParams, http } from 'msw';
+import { HttpResponse, http } from 'msw';
+import type { DefaultBodyType, HttpResponseResolver, JsonBodyType, PathParams } from 'msw';
import seedrandom from 'seedrandom';
import { action } from '@storybook/addon-actions';
diff --git a/packages/frontend/.storybook/fake-utils.ts b/packages/frontend/.storybook/fake-utils.ts
index c777cbbe72..44e2263ca0 100644
--- a/packages/frontend/.storybook/fake-utils.ts
+++ b/packages/frontend/.storybook/fake-utils.ts
@@ -131,7 +131,7 @@ export function imageDataUrl(options?: {
alpha?: number,
}
}, seed?: string): string {
- const canvas = document.createElement('canvas');
+ const canvas = window.document.createElement('canvas');
canvas.width = options?.size?.width ?? 100;
canvas.height = options?.size?.height ?? 100;
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index 8830523810..89d4214141 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -17,8 +17,52 @@ interface SatisfiesExpression extends estree.BaseExpression {
reference: estree.Identifier;
}
+interface ImportDeclaration extends estree.ImportDeclaration {
+ kind?: 'type';
+}
+
const generator = {
...GENERATOR,
+ ImportDeclaration(node: ImportDeclaration, state: State) {
+ state.write('import ');
+ if (node.kind === 'type') state.write('type ');
+ const { specifiers } = node;
+ if (specifiers.length > 0) {
+ let i = 0;
+ for (; i < specifiers.length; i++) {
+ if (i > 0) {
+ state.write(', ');
+ }
+ const specifier = specifiers[i]!;
+ if (specifier.type === 'ImportDefaultSpecifier') {
+ state.write(specifier.local.name, specifier);
+ } else if (specifier.type === 'ImportNamespaceSpecifier') {
+ state.write(`* as ${specifier.local.name}`, specifier);
+ } else {
+ break;
+ }
+ }
+ if (i < specifiers.length) {
+ state.write('{');
+ for (; i < specifiers.length; i++) {
+ const specifier = specifiers[i]! as estree.ImportSpecifier;
+ const { name } = specifier.imported as estree.Identifier;
+ state.write(name, specifier);
+ if (name !== specifier.local.name) {
+ state.write(` as ${specifier.local.name}`);
+ }
+ if (i < specifiers.length - 1) {
+ state.write(', ');
+ }
+ }
+ state.write('}');
+ }
+ state.write(' from ');
+ }
+ this.Literal(node.source, state);
+
+ state.write(';');
+ },
SatisfiesExpression(node: SatisfiesExpression, state: State) {
switch (node.expression.type) {
case 'ArrowFunctionExpression': {
@@ -62,7 +106,7 @@ type ToKebab<T extends readonly string[]> = T extends readonly [
: T extends readonly [
infer XH extends string,
...infer XR extends readonly string[]
- ]
+ ]
? `${XH}${XR extends readonly string[] ? `-${ToKebab<XR>}` : ''}`
: '';
@@ -132,7 +176,7 @@ function toStories(component: string): Promise<string> {
kind={'init' as const}
shorthand
/> as estree.Property,
- ]
+ ]
: []),
]}
/> as estree.ObjectExpression;
@@ -155,7 +199,8 @@ function toStories(component: string): Promise<string> {
/> as estree.ImportSpecifier,
]),
]}
- /> as estree.ImportDeclaration,
+ kind={'type'}
+ /> as ImportDeclaration,
...(hasMsw
? [
<import-declaration
@@ -165,8 +210,8 @@ function toStories(component: string): Promise<string> {
local={<identifier name='msw' /> as estree.Identifier}
/> as estree.ImportNamespaceSpecifier,
]}
- /> as estree.ImportDeclaration,
- ]
+ /> as ImportDeclaration,
+ ]
: []),
...(hasImplStories
? []
@@ -176,8 +221,8 @@ function toStories(component: string): Promise<string> {
specifiers={[
<import-default-specifier local={identifier} /> as estree.ImportDefaultSpecifier,
]}
- /> as estree.ImportDeclaration,
- ]),
+ /> as ImportDeclaration,
+ ]),
...(hasMetaStories
? [
<import-declaration
@@ -187,7 +232,7 @@ function toStories(component: string): Promise<string> {
local={<identifier name='storiesMeta' /> as estree.Identifier}
/> as estree.ImportNamespaceSpecifier,
]}
- /> as estree.ImportDeclaration,
+ /> as ImportDeclaration,
]
: []),
<variable-declaration
@@ -414,6 +459,7 @@ function toStories(component: string): Promise<string> {
glob('src/components/MkSignupServerRules.vue'),
glob('src/components/MkUserSetupDialog.vue'),
glob('src/components/MkUserSetupDialog.*.vue'),
+ glob('src/components/MkImgPreviewDialog.vue'),
glob('src/components/MkInstanceCardMini.vue'),
glob('src/components/MkInviteCode.vue'),
glob('src/components/MkTagItem.vue'),
diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts
index 9f318cf449..c1119c2523 100644
--- a/packages/frontend/.storybook/main.ts
+++ b/packages/frontend/.storybook/main.ts
@@ -39,6 +39,10 @@ const config = {
if (~replacePluginForIsChromatic) {
config.plugins?.splice(replacePluginForIsChromatic, 1);
}
+
+ //pluginsからcreateSearchIndexを削除、複数あるかもしれないので全て削除
+ config.plugins = config.plugins?.filter((plugin: Plugin) => plugin && plugin.name !== 'createSearchIndex') ?? [];
+
return mergeConfig(config, {
plugins: [
{
diff --git a/packages/frontend/.storybook/preview-head.html b/packages/frontend/.storybook/preview-head.html
index ae42fd49bc..2431a71ddc 100644
--- a/packages/frontend/.storybook/preview-head.html
+++ b/packages/frontend/.storybook/preview-head.html
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous">
<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous">
-<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@3.3.0/dist/tabler-icons.min.css">
+<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@3.31.0/dist/tabler-icons.min.css">
<link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
<style>
html {
diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts
index d000a28232..269bc4fb9a 100644
--- a/packages/frontend/.storybook/preview.ts
+++ b/packages/frontend/.storybook/preview.ts
@@ -21,11 +21,11 @@ let moduleInitialized = false;
let unobserve = () => {};
let misskeyOS = null;
-function loadTheme(applyTheme: typeof import('../src/scripts/theme')['applyTheme']) {
+function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) {
unobserve();
- const theme = themes[document.documentElement.dataset.misskeyTheme];
+ const theme = themes[window.document.documentElement.dataset.misskeyTheme];
if (theme) {
- applyTheme(themes[document.documentElement.dataset.misskeyTheme]);
+ applyTheme(themes[window.document.documentElement.dataset.misskeyTheme]);
} else {
applyTheme(themes['l-light']);
}
@@ -42,7 +42,7 @@ function loadTheme(applyTheme: typeof import('../src/scripts/theme')['applyTheme
}
}
});
- observer.observe(document.documentElement, {
+ observer.observe(window.document.documentElement, {
attributes: true,
attributeFilter: ['data-misskey-theme'],
});
@@ -64,13 +64,13 @@ initialize({
initLocalStorage();
queueMicrotask(() => {
Promise.all([
- import('../src/components'),
- import('../src/directives'),
- import('../src/widgets'),
- import('../src/scripts/theme'),
- import('../src/store'),
- import('../src/os'),
- ]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { defaultStore }, os]) => {
+ import('../src/components/index.js'),
+ import('../src/directives/index.js'),
+ import('../src/widgets/index.js'),
+ import('../src/theme.js'),
+ import('../src/preferences.js'),
+ import('../src/os.js'),
+ ]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => {
setup((app) => {
moduleInitialized = true;
if (app[appInitialized]) {
@@ -83,7 +83,7 @@ queueMicrotask(() => {
widgets(app);
misskeyOS = os;
if (isChromatic()) {
- defaultStore.set('animation', false);
+ prefer.set('animation', false);
}
});
});
@@ -104,9 +104,9 @@ const preview = {
}
}).catch(() => {})
: Promise.resolve();
- const resetDefaultStorePromise = import('../src/store').then(({ defaultStore }) => {
+ const resetDefaultStorePromise = import('../src/store').then(({ store }) => {
// @ts-expect-error
- defaultStore.init();
+ store.init();
}).catch(() => {});
Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => {
initLocalStorage();
diff --git a/packages/frontend/@types/theme.d.ts b/packages/frontend/@types/theme.d.ts
index 70afc356c1..6ac1037493 100644
--- a/packages/frontend/@types/theme.d.ts
+++ b/packages/frontend/@types/theme.d.ts
@@ -4,7 +4,7 @@
*/
declare module '@@/themes/*.json5' {
- import { Theme } from '@/scripts/theme.js';
+ import { Theme } from '@/theme.js';
const theme: Theme;
diff --git a/packages/frontend/assets/bell_3d.png b/packages/frontend/assets/bell_3d.png
new file mode 100644
index 0000000000..2598cdd82b
--- /dev/null
+++ b/packages/frontend/assets/bell_3d.png
Binary files differ
diff --git a/packages/frontend/assets/cloud_3d.png b/packages/frontend/assets/cloud_3d.png
new file mode 100644
index 0000000000..a3a1de12dd
--- /dev/null
+++ b/packages/frontend/assets/cloud_3d.png
Binary files differ
diff --git a/packages/frontend/assets/desktop_computer_3d.png b/packages/frontend/assets/desktop_computer_3d.png
new file mode 100644
index 0000000000..85e92a02c0
--- /dev/null
+++ b/packages/frontend/assets/desktop_computer_3d.png
Binary files differ
diff --git a/packages/frontend/assets/electric_plug_3d.png b/packages/frontend/assets/electric_plug_3d.png
new file mode 100644
index 0000000000..431ef68c85
--- /dev/null
+++ b/packages/frontend/assets/electric_plug_3d.png
Binary files differ
diff --git a/packages/frontend/assets/gear_3d.png b/packages/frontend/assets/gear_3d.png
new file mode 100644
index 0000000000..050340b76c
--- /dev/null
+++ b/packages/frontend/assets/gear_3d.png
Binary files differ
diff --git a/packages/frontend/assets/link_3d.png b/packages/frontend/assets/link_3d.png
new file mode 100644
index 0000000000..b1cb23080a
--- /dev/null
+++ b/packages/frontend/assets/link_3d.png
Binary files differ
diff --git a/packages/frontend/assets/locked_with_key_3d.png b/packages/frontend/assets/locked_with_key_3d.png
new file mode 100644
index 0000000000..aae99b982d
--- /dev/null
+++ b/packages/frontend/assets/locked_with_key_3d.png
Binary files differ
diff --git a/packages/frontend/assets/mens_room_3d.png b/packages/frontend/assets/mens_room_3d.png
new file mode 100644
index 0000000000..8b85ca8782
--- /dev/null
+++ b/packages/frontend/assets/mens_room_3d.png
Binary files differ
diff --git a/packages/frontend/assets/musical_note_3d.png b/packages/frontend/assets/musical_note_3d.png
new file mode 100644
index 0000000000..0b520311f6
--- /dev/null
+++ b/packages/frontend/assets/musical_note_3d.png
Binary files differ
diff --git a/packages/frontend/assets/package_3d.png b/packages/frontend/assets/package_3d.png
new file mode 100644
index 0000000000..582134fd2f
--- /dev/null
+++ b/packages/frontend/assets/package_3d.png
Binary files differ
diff --git a/packages/frontend/assets/prohibited_3d.png b/packages/frontend/assets/prohibited_3d.png
new file mode 100644
index 0000000000..1f071edd06
--- /dev/null
+++ b/packages/frontend/assets/prohibited_3d.png
Binary files differ
diff --git a/packages/frontend/assets/speaker_high_volume_3d.png b/packages/frontend/assets/speaker_high_volume_3d.png
new file mode 100644
index 0000000000..b25aaa91d6
--- /dev/null
+++ b/packages/frontend/assets/speaker_high_volume_3d.png
Binary files differ
diff --git a/packages/frontend/assets/unlocked_3d.png b/packages/frontend/assets/unlocked_3d.png
new file mode 100644
index 0000000000..c6ff7a0dc2
--- /dev/null
+++ b/packages/frontend/assets/unlocked_3d.png
Binary files differ
diff --git a/packages/frontend/eslint.config.js b/packages/frontend/eslint.config.js
index 932655d9bd..1532a7e67b 100644
--- a/packages/frontend/eslint.config.js
+++ b/packages/frontend/eslint.config.js
@@ -54,9 +54,49 @@ export default [
'@typescript-eslint/no-empty-interface': ['error', {
allowSingleExtends: true,
}],
- // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
- // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
- 'id-denylist': ['error', 'window', 'e'],
+ // defineExposeが誤検知されてしまう
+ '@typescript-eslint/no-unused-expressions': 'off',
+ 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
+ // window ... グローバルスコープと衝突し、予期せぬ結果を招くため
+ // e ... error や event など、複数のキーワードの頭文字であり分かりにくいため
+ // close ... window.closeと衝突 or 紛らわしい
+ // open ... window.openと衝突 or 紛らわしい
+ // fetch ... window.fetchと衝突 or 紛らわしい
+ // location ... window.locationと衝突 or 紛らわしい
+ // document ... window.documentと衝突 or 紛らわしい
+ // history ... window.historyと衝突 or 紛らわしい
+ 'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history'],
+ 'no-restricted-globals': [
+ 'error',
+ {
+ 'name': 'open',
+ 'message': 'Use `window.open`.',
+ },
+ {
+ 'name': 'close',
+ 'message': 'Use `window.close`.',
+ },
+ {
+ 'name': 'fetch',
+ 'message': 'Use `window.fetch`.',
+ },
+ {
+ 'name': 'location',
+ 'message': 'Use `window.location`.',
+ },
+ {
+ 'name': 'document',
+ 'message': 'Use `window.document`.',
+ },
+ {
+ 'name': 'history',
+ 'message': 'Use `window.history`.',
+ },
+ {
+ 'name': 'name',
+ 'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
+ },
+ ],
'no-shadow': ['warn'],
'vue/attributes-order': ['error', {
alphabetical: false,
diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
index 5d8cf05fff..ccfa08575b 100644
--- a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
+++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
@@ -58,7 +58,7 @@ describe(normalizeClass.name, () => {
it('Composition API (standard)', () => {
const ast = parse(`
-import { c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
+import { c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
import { M as MkContainer } from './MkContainer-!~{03M}~.js';
import { b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode } from './vue-!~{002}~.js';
import './photoswipe-!~{003}~.js';
@@ -74,7 +74,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
let fetching = ref(true);
let images = ref([]);
function thumbnail(image) {
- return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
+ return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
}
onMounted(() => {
const image = [
@@ -173,7 +173,7 @@ export { index_photos as default };
`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' });
unwindCssModuleClassName(ast);
expect(generate(ast)).toBe(`
-import {c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc} from './app-!~{001}~.js';
+import {c as api, d as store, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc} from './app-!~{001}~.js';
import {M as MkContainer} from './MkContainer-!~{03M}~.js';
import {b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode} from './vue-!~{002}~.js';
import './photoswipe-!~{003}~.js';
@@ -190,7 +190,7 @@ const index_photos = defineComponent({
let fetching = ref(true);
let images = ref([]);
function thumbnail(image) {
- return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
+ return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
}
onMounted(() => {
const image = ["image/jpeg", "image/webp", "image/avif", "image/png", "image/gif", "image/apng", "image/vnd.mozilla.apng"];
@@ -268,7 +268,7 @@ export {index_photos as default};
it('Composition API (with `useCssModule()`)', () => {
const ast = parse(`
import { a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup } from './!~{002}~.js';
-import { d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js';
+import { d as store, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js';
function isDebuggerEnabled(id) {
try {
@@ -393,7 +393,7 @@ const _sfc_main = defineComponent({
el.style.left = "";
}
return () => h(
- defaultStore.state.animation ? TransitionGroup : "div",
+ prefer.s.animation ? TransitionGroup : "div",
{
class: {
[$style["date-separated-list"]]: true,
@@ -402,7 +402,7 @@ const _sfc_main = defineComponent({
[$style["direction-down"]]: props.direction === "down",
[$style["direction-up"]]: props.direction === "up"
},
- ...defaultStore.state.animation ? {
+ ...prefer.s.animation ? {
name: "list",
tag: "div",
onBeforeLeave,
@@ -441,7 +441,7 @@ export { MkDateSeparatedList as M };
unwindCssModuleClassName(ast);
expect(generate(ast)).toBe(`
import {a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup} from './!~{002}~.js';
-import {d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc} from './app-!~{001}~.js';
+import {d as store, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc} from './app-!~{001}~.js';
function isDebuggerEnabled(id) {
try {
return localStorage.getItem(\`DEBUG_\${id}\`) !== null;
@@ -555,7 +555,7 @@ const _sfc_main = defineComponent({
el.style.top = "";
el.style.left = "";
}
- return () => h(defaultStore.state.animation ? TransitionGroup : "div", {
+ return () => h(prefer.s.animation ? TransitionGroup : "div", {
class: {
[$style["date-separated-list"]]: true,
[$style["date-separated-list-nogap"]]: props.noGap,
@@ -563,7 +563,7 @@ const _sfc_main = defineComponent({
[$style["direction-down"]]: props.direction === "down",
[$style["direction-up"]]: props.direction === "up"
},
- ...defaultStore.state.animation ? {
+ ...prefer.s.animation ? {
name: "list",
tag: "div",
onBeforeLeave,
diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts
index 0ed2e14d2a..7ecb1e9179 100644
--- a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts
+++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts
@@ -4,8 +4,8 @@
*/
import { generate } from 'astring';
-import * as estree from 'estree';
import { walk } from '../node_modules/estree-walker/src/index.js';
+import type * as estree from 'estree';
import type * as estreeWalker from 'estree-walker';
import type { Plugin } from 'vite';
diff --git a/packages/frontend/lib/vite-plugin-create-search-index.ts b/packages/frontend/lib/vite-plugin-create-search-index.ts
new file mode 100644
index 0000000000..d506e84bb6
--- /dev/null
+++ b/packages/frontend/lib/vite-plugin-create-search-index.ts
@@ -0,0 +1,1521 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { parse as vueSfcParse } from 'vue/compiler-sfc';
+import type { LogOptions, Plugin } from 'vite';
+import fs from 'node:fs';
+import { glob } from 'glob';
+import JSON5 from 'json5';
+import MagicString from 'magic-string';
+import path from 'node:path'
+import { hash, toBase62 } from '../vite.config';
+import { createLogger } from 'vite';
+
+interface VueAstNode {
+ type: number;
+ tag?: string;
+ loc?: {
+ start: { offset: number, line: number, column: number },
+ end: { offset: number, line: number, column: number },
+ source?: string
+ };
+ props?: Array<{
+ name: string;
+ type: number;
+ value?: { content?: string };
+ arg?: { content?: string };
+ exp?: { content?: string; loc?: any };
+ }>;
+ children?: VueAstNode[];
+ content?: any;
+ __markerId?: string;
+ __children?: string[];
+}
+
+export type AnalysisResult = {
+ filePath: string;
+ usage: SearchIndexItem[];
+}
+
+export type SearchIndexItem = {
+ id: string;
+ path?: string;
+ label: string;
+ keywords: string | string[];
+ icon?: string;
+ inlining?: string[];
+ children?: SearchIndexItem[];
+};
+
+export type Options = {
+ targetFilePaths: string[],
+ exportFilePath: string,
+ verbose?: boolean,
+};
+
+// 関連するノードタイプの定数化
+const NODE_TYPES = {
+ ELEMENT: 1,
+ EXPRESSION: 2,
+ TEXT: 3,
+ INTERPOLATION: 5, // Mustache
+};
+
+// マーカー関係を表す型
+interface MarkerRelation {
+ parentId?: string;
+ markerId: string;
+ node: VueAstNode;
+}
+
+// ロガー
+let logger = {
+ info: (msg: string, options?: LogOptions) => { },
+ warn: (msg: string, options?: LogOptions) => { },
+ error: (msg: string, options?: LogOptions) => { },
+};
+let loggerInitialized = false;
+
+function initLogger(options: Options) {
+ if (loggerInitialized) return;
+ loggerInitialized = true;
+ const viteLogger = createLogger(options.verbose ? 'info' : 'warn');
+
+ logger.info = (msg, options) => {
+ msg = `[create-search-index] ${msg}`;
+ viteLogger.info(msg, options);
+ }
+
+ logger.warn = (msg, options) => {
+ msg = `[create-search-index] ${msg}`;
+ viteLogger.warn(msg, options);
+ }
+
+ logger.error = (msg, options) => {
+ msg = `[create-search-index] ${msg}`;
+ viteLogger.error(msg, options);
+ }
+}
+
+/**
+ * 解析結果をTypeScriptファイルとして出力する
+ */
+function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisResult[]): void {
+ logger.info(`Processing ${analysisResults.length} files for output`);
+
+ // 新しいツリー構造を構築
+ const allMarkers = new Map<string, SearchIndexItem>();
+
+ // 1. すべてのマーカーを一旦フラットに収集
+ for (const file of analysisResults) {
+ logger.info(`Processing file: ${file.filePath} with ${file.usage.length} markers`);
+
+ for (const marker of file.usage) {
+ if (marker.id) {
+ // キーワードとchildren処理を共通化
+ const processedMarker = {
+ ...marker,
+ keywords: processMarkerProperty(marker.keywords, 'keywords'),
+ children: processMarkerProperty(marker.children || [], 'children')
+ };
+
+ allMarkers.set(marker.id, processedMarker);
+ }
+ }
+ }
+
+ logger.info(`Collected total ${allMarkers.size} unique markers`);
+
+ // 2. 子マーカーIDの収集
+ const childIds = collectChildIds(allMarkers);
+ logger.info(`Found ${childIds.size} child markers`);
+
+ // 3. ルートマーカーの特定(他の誰かの子でないマーカー)
+ const rootMarkers = identifyRootMarkers(allMarkers, childIds);
+ logger.info(`Found ${rootMarkers.length} root markers`);
+
+ // 4. 子マーカーの参照を解決
+ const resolvedRootMarkers = resolveChildReferences(rootMarkers, allMarkers);
+
+ // 5. デバッグ情報を生成
+ const { totalMarkers, totalChildren } = countMarkers(resolvedRootMarkers);
+ logger.info(`Total markers in tree: ${totalMarkers} (${resolvedRootMarkers.length} roots + ${totalChildren} nested children)`);
+
+ // 6. 結果をTS形式で出力
+ writeOutputFile(outputPath, resolvedRootMarkers);
+}
+
+/**
+ * マーカーのプロパティ(keywordsやchildren)を処理する
+ */
+function processMarkerProperty(propValue: any, propType: 'keywords' | 'children'): any {
+ // 文字列の配列表現を解析
+ if (typeof propValue === 'string' && propValue.startsWith('[') && propValue.endsWith(']')) {
+ try {
+ // JSON5解析を試みる
+ return JSON5.parse(propValue.replace(/'/g, '"'));
+ } catch (e) {
+ // 解析に失敗した場合
+ logger.warn(`Could not parse ${propType}: ${propValue}, using ${propType === 'children' ? 'empty array' : 'as is'}`);
+ return propType === 'children' ? [] : propValue;
+ }
+ }
+
+ return propValue;
+}
+
+/**
+ * 全マーカーから子IDを収集する
+ */
+function collectChildIds(allMarkers: Map<string, SearchIndexItem>): Set<string> {
+ const childIds = new Set<string>();
+
+ allMarkers.forEach((marker, id) => {
+ // 通常のchildren処理
+ const children = marker.children;
+ if (Array.isArray(children)) {
+ children.forEach(childId => {
+ if (typeof childId === 'string') {
+ if (!allMarkers.has(childId)) {
+ logger.warn(`Warning: Child marker ID ${childId} referenced but not found`);
+ } else {
+ childIds.add(childId);
+ }
+ }
+ });
+ }
+
+ // inlining処理を追加
+ if (marker.inlining) {
+ let inliningIds: string[] = [];
+
+ // 文字列の場合は配列に変換
+ if (typeof marker.inlining === 'string') {
+ try {
+ const inliningStr = (marker.inlining as string).trim();
+ if (inliningStr.startsWith('[') && inliningStr.endsWith(']')) {
+ inliningIds = JSON5.parse(inliningStr.replace(/'/g, '"'));
+ logger.info(`Parsed inlining string to array: ${inliningStr} -> ${JSON.stringify(inliningIds)}`);
+ } else {
+ inliningIds = [inliningStr];
+ }
+ } catch (e) {
+ logger.error(`Failed to parse inlining string: ${marker.inlining}`, e);
+ }
+ }
+ // 既に配列の場合
+ else if (Array.isArray(marker.inlining)) {
+ inliningIds = marker.inlining;
+ }
+
+ // inliningで指定されたIDを子セットに追加
+ for (const inlineId of inliningIds) {
+ if (typeof inlineId === 'string') {
+ if (!allMarkers.has(inlineId)) {
+ logger.warn(`Warning: Inlining marker ID ${inlineId} referenced but not found`);
+ } else {
+ // inliningで参照されているマーカーも子として扱う
+ childIds.add(inlineId);
+ logger.info(`Added inlined marker ${inlineId} as child in collectChildIds`);
+ }
+ }
+ }
+ }
+ });
+
+ return childIds;
+}
+
+/**
+ * ルートマーカー(他の子でないマーカー)を特定する
+ */
+function identifyRootMarkers(
+ allMarkers: Map<string, SearchIndexItem>,
+ childIds: Set<string>
+): SearchIndexItem[] {
+ const rootMarkers: SearchIndexItem[] = [];
+
+ allMarkers.forEach((marker, id) => {
+ if (!childIds.has(id)) {
+ rootMarkers.push(marker);
+ logger.info(`Added root marker to output: ${id} with label ${marker.label}`);
+ }
+ });
+
+ return rootMarkers;
+}
+
+/**
+ * 子マーカーの参照をIDから実際のオブジェクトに解決する
+ */
+function resolveChildReferences(
+ rootMarkers: SearchIndexItem[],
+ allMarkers: Map<string, SearchIndexItem>
+): SearchIndexItem[] {
+ function resolveChildrenForMarker(marker: SearchIndexItem): SearchIndexItem {
+ // マーカーのディープコピーを作成
+ const resolvedMarker = { ...marker };
+ // 明示的に子マーカー配列を作成
+ const resolvedChildren: SearchIndexItem[] = [];
+
+ // 通常のchildren処理
+ if (Array.isArray(marker.children)) {
+ for (const childId of marker.children) {
+ if (typeof childId === 'string') {
+ const childMarker = allMarkers.get(childId);
+ if (childMarker) {
+ // 子マーカーの子も再帰的に解決
+ const resolvedChild = resolveChildrenForMarker(childMarker);
+ resolvedChildren.push(resolvedChild);
+ logger.info(`Resolved regular child ${childId} for parent ${marker.id}`);
+ }
+ }
+ }
+ }
+
+ // inlining属性の処理
+ let inliningIds: string[] = [];
+
+ // 文字列の場合は配列に変換。例: "['2fa']" -> ['2fa']
+ if (typeof marker.inlining === 'string') {
+ try {
+ // 文字列形式の配列を実際の配列に変換
+ const inliningStr = (marker.inlining as string).trim();
+ if (inliningStr.startsWith('[') && inliningStr.endsWith(']')) {
+ inliningIds = JSON5.parse(inliningStr.replace(/'/g, '"'));
+ logger.info(`Converted string inlining to array: ${inliningStr} -> ${JSON.stringify(inliningIds)}`);
+ } else {
+ // 単一値の場合は配列に
+ inliningIds = [inliningStr];
+ logger.info(`Converted single string inlining to array: ${inliningStr}`);
+ }
+ } catch (e) {
+ logger.error(`Failed to parse inlining string: ${marker.inlining}`, e);
+ }
+ }
+ // 既に配列の場合はそのまま使用
+ else if (Array.isArray(marker.inlining)) {
+ inliningIds = marker.inlining;
+ }
+
+ // インライン指定されたマーカーを子として追加
+ for (const inlineId of inliningIds) {
+ if (typeof inlineId === 'string') {
+ const inlineMarker = allMarkers.get(inlineId);
+ if (inlineMarker) {
+ // インライン指定されたマーカーを再帰的に解決
+ const resolvedInline = resolveChildrenForMarker(inlineMarker);
+ delete resolvedInline.path
+ resolvedChildren.push(resolvedInline);
+ logger.info(`Added inlined marker ${inlineId} as child to ${marker.id}`);
+ } else {
+ logger.warn(`Inlining target not found: ${inlineId} referenced by ${marker.id}`);
+ }
+ }
+ }
+
+ // 解決した子が存在する場合のみchildrenプロパティを設定
+ if (resolvedChildren.length > 0) {
+ resolvedMarker.children = resolvedChildren;
+ } else {
+ delete resolvedMarker.children;
+ }
+
+ return resolvedMarker;
+ }
+
+ // すべてのルートマーカーの子を解決
+ return rootMarkers.map(marker => resolveChildrenForMarker(marker));
+}
+
+/**
+ * マーカー数を数える(デバッグ用)
+ */
+function countMarkers(markers: SearchIndexItem[]): { totalMarkers: number, totalChildren: number } {
+ let totalMarkers = markers.length;
+ let totalChildren = 0;
+
+ function countNested(items: SearchIndexItem[]): void {
+ for (const marker of items) {
+ if (marker.children && Array.isArray(marker.children)) {
+ totalChildren += marker.children.length;
+ totalMarkers += marker.children.length;
+ countNested(marker.children as SearchIndexItem[]);
+ }
+ }
+ }
+
+ countNested(markers);
+ return { totalMarkers, totalChildren };
+}
+
+/**
+ * 最終的なTypeScriptファイルを出力
+ */
+function writeOutputFile(outputPath: string, resolvedRootMarkers: SearchIndexItem[]): void {
+ try {
+ const tsOutput = generateTypeScriptCode(resolvedRootMarkers);
+ fs.writeFileSync(outputPath, tsOutput, 'utf-8');
+ // 強制的に出力させるためにViteロガーを使わない
+ console.log(`Successfully wrote search index to ${outputPath} with ${resolvedRootMarkers.length} root entries`);
+ } catch (error) {
+ logger.error('[create-search-index]: error writing output: ', error);
+ }
+}
+
+/**
+ * TypeScriptコード生成
+ */
+function generateTypeScriptCode(resolvedRootMarkers: SearchIndexItem[]): string {
+ return `
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+// This file was automatically generated by create-search-index.
+// Do not edit this file.
+
+import { i18n } from '@/i18n.js';
+
+export type SearchIndexItem = {
+ id: string;
+ path?: string;
+ label: string;
+ keywords: string[];
+ icon?: string;
+ children?: SearchIndexItem[];
+};
+
+export const searchIndexes: SearchIndexItem[] = ${customStringify(resolvedRootMarkers)} as const;
+
+export type SearchIndex = typeof searchIndexes;
+`;
+}
+
+/**
+ * オブジェクトを特殊な形式の文字列に変換する
+ * i18n参照を保持しつつ適切な形式に変換
+ */
+function customStringify(obj: any, depth = 0): string {
+ const INDENT_STR = '\t';
+
+ // 配列の処理
+ if (Array.isArray(obj)) {
+ if (obj.length === 0) return '[]';
+ const indent = INDENT_STR.repeat(depth);
+ const childIndent = INDENT_STR.repeat(depth + 1);
+
+ // 配列要素の処理
+ const items = obj.map(item => {
+ // オブジェクト要素
+ if (typeof item === 'object' && item !== null) {
+ return `${childIndent}${customStringify(item, depth + 1)}`;
+ }
+
+ // i18n参照を含む文字列要素
+ if (typeof item === 'string' && item.includes('i18n.ts.')) {
+ return `${childIndent}${item}`; // クォートなしでそのまま出力
+ }
+
+ // その他の要素
+ return `${childIndent}${JSON5.stringify(item)}`;
+ }).join(',\n');
+
+ return `[\n${items},\n${indent}]`;
+ }
+
+ // null または非オブジェクト
+ if (obj === null || typeof obj !== 'object') {
+ return JSON5.stringify(obj);
+ }
+
+ // オブジェクトの処理
+ const indent = INDENT_STR.repeat(depth);
+ const childIndent = INDENT_STR.repeat(depth + 1);
+
+ const entries = Object.entries(obj)
+ // 不要なプロパティを除去
+ .filter(([key, value]) => {
+ if (value === undefined) return false;
+ if (key === 'children' && Array.isArray(value) && value.length === 0) return false;
+ if (key === 'inlining') return false;
+ return true;
+ })
+ // 各プロパティを変換
+ .map(([key, value]) => {
+ // 子要素配列の特殊処理
+ if (key === 'children' && Array.isArray(value) && value.length > 0) {
+ return `${childIndent}${key}: ${customStringify(value, depth + 1)}`;
+ }
+
+ // ラベルやその他プロパティを処理
+ return `${childIndent}${key}: ${formatSpecialProperty(key, value)}`;
+ });
+
+ if (entries.length === 0) return '{}';
+ return `{\n${entries.join(',\n')},\n${indent}}`;
+}
+
+/**
+ * 特殊プロパティの書式設定
+ */
+function formatSpecialProperty(key: string, value: any): string {
+ // 値がundefinedの場合は空文字列を返す
+ if (value === undefined) {
+ return '""';
+ }
+
+ // childrenが配列の場合は特別に処理
+ if (key === 'children' && Array.isArray(value)) {
+ return customStringify(value);
+ }
+
+ // keywordsが配列の場合、特別に処理
+ if (key === 'keywords' && Array.isArray(value)) {
+ return `[${formatArrayForOutput(value)}]`;
+ }
+
+ // 文字列値の場合の特別処理
+ if (typeof value === 'string') {
+ // i18n.ts 参照を含む場合 - クォートなしでそのまま出力
+ if (isI18nReference(value)) {
+ logger.info(`Preserving i18n reference in output: ${value}`);
+ return value;
+ }
+
+ // keywords が配列リテラルの形式の場合
+ if (key === 'keywords' && value.startsWith('[') && value.endsWith(']')) {
+ return value;
+ }
+ }
+
+ // 上記以外は通常の JSON5 文字列として返す
+ return JSON5.stringify(value);
+}
+
+/**
+ * 配列式の文字列表現を生成
+ */
+function formatArrayForOutput(items: any[]): string {
+ return items.map(item => {
+ // i18n.ts. 参照の文字列はそのままJavaScript式として出力
+ if (typeof item === 'string' && isI18nReference(item)) {
+ logger.info(`Preserving i18n reference in array: ${item}`);
+ return item; // クォートなしでそのまま
+ }
+
+ // その他の値はJSON5形式で文字列化
+ return JSON5.stringify(item);
+ }).join(', ');
+}
+
+/**
+ * 要素ノードからテキスト内容を抽出する
+ * 各抽出方法を分離して可読性を向上
+ */
+function extractElementText(node: VueAstNode): string | null {
+ if (!node) return null;
+
+ logger.info(`Extracting text from node type=${node.type}, tag=${node.tag || 'unknown'}`);
+
+ // 1. 直接コンテンツの抽出を試行
+ const directContent = extractDirectContent(node);
+ if (directContent) return directContent;
+
+ // 子要素がない場合は終了
+ if (!node.children || !Array.isArray(node.children)) {
+ return null;
+ }
+
+ // 2. インターポレーションノードを検索
+ const interpolationContent = extractInterpolationContent(node.children);
+ if (interpolationContent) return interpolationContent;
+
+ // 3. 式ノードを検索
+ const expressionContent = extractExpressionContent(node.children);
+ if (expressionContent) return expressionContent;
+
+ // 4. テキストノードを検索
+ const textContent = extractTextContent(node.children);
+ if (textContent) return textContent;
+
+ // 5. 再帰的に子ノードを探索
+ return extractNestedContent(node.children);
+}
+/**
+ * ノードから直接コンテンツを抽出
+ */
+function extractDirectContent(node: VueAstNode): string | null {
+ if (!node.content) return null;
+
+ const content = typeof node.content === 'string'
+ ? node.content.trim()
+ : (node.content.content ? node.content.content.trim() : null);
+
+ if (!content) return null;
+
+ logger.info(`Direct node content found: ${content}`);
+
+ // Mustache構文のチェック
+ const mustachePattern = /^\s*{{\s*(.*?)\s*}}\s*$/;
+ const mustacheMatch = content.match(mustachePattern);
+
+ if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) {
+ const extractedContent = mustacheMatch[1].trim();
+ logger.info(`Extracted i18n reference from mustache: ${extractedContent}`);
+ return extractedContent;
+ }
+
+ // 直接i18n参照を含む場合
+ if (isI18nReference(content)) {
+ logger.info(`Direct i18n reference found: ${content}`);
+ return content;
+ }
+
+ // その他のコンテンツ
+ return content;
+}
+
+/**
+ * インターポレーションノード(Mustache)からコンテンツを抽出
+ */
+function extractInterpolationContent(children: VueAstNode[]): string | null {
+ for (const child of children) {
+ if (child.type === NODE_TYPES.INTERPOLATION) {
+ logger.info(`Found interpolation node (Mustache): ${JSON.stringify(child.content).substring(0, 100)}...`);
+
+ if (child.content && child.content.type === 4 && child.content.content) {
+ const content = child.content.content.trim();
+ logger.info(`Interpolation content: ${content}`);
+
+ if (isI18nReference(content)) {
+ return content;
+ }
+ } else if (child.content && typeof child.content === 'object') {
+ // オブジェクト形式のcontentを探索
+ logger.info(`Complex interpolation node: ${JSON.stringify(child.content).substring(0, 100)}...`);
+
+ if (child.content.content) {
+ const content = child.content.content.trim();
+
+ if (isI18nReference(content)) {
+ logger.info(`Found i18n reference in complex interpolation: ${content}`);
+ return content;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+}
+
+/**
+ * 式ノードからコンテンツを抽出
+ */
+function extractExpressionContent(children: VueAstNode[]): string | null {
+ // i18n.ts. 参照パターンを持つものを優先
+ for (const child of children) {
+ if (child.type === NODE_TYPES.EXPRESSION && child.content) {
+ const expr = child.content.trim();
+
+ if (isI18nReference(expr)) {
+ logger.info(`Found i18n reference in expression node: ${expr}`);
+ return expr;
+ }
+ }
+ }
+
+ // その他の式
+ for (const child of children) {
+ if (child.type === NODE_TYPES.EXPRESSION && child.content) {
+ const expr = child.content.trim();
+ logger.info(`Found expression: ${expr}`);
+ return expr;
+ }
+ }
+
+ return null;
+}
+
+/**
+ * テキストノードからコンテンツを抽出
+ */
+function extractTextContent(children: VueAstNode[]): string | null {
+ for (const child of children) {
+ if (child.type === NODE_TYPES.TEXT && child.content) {
+ const text = child.content.trim();
+
+ if (text) {
+ logger.info(`Found text node: ${text}`);
+
+ // Mustache構文のチェック
+ const mustachePattern = /^\s*{{\s*(.*?)\s*}}\s*$/;
+ const mustacheMatch = text.match(mustachePattern);
+
+ if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) {
+ logger.info(`Extracted i18n ref from text mustache: ${mustacheMatch[1]}`);
+ return mustacheMatch[1].trim();
+ }
+
+ return text;
+ }
+ }
+ }
+
+ return null;
+}
+
+/**
+ * 子ノードを再帰的に探索してコンテンツを抽出
+ */
+function extractNestedContent(children: VueAstNode[]): string | null {
+ for (const child of children) {
+ if (child.children && Array.isArray(child.children) && child.children.length > 0) {
+ const nestedContent = extractElementText(child);
+
+ if (nestedContent) {
+ logger.info(`Found nested content: ${nestedContent}`);
+ return nestedContent;
+ }
+ } else if (child.type === NODE_TYPES.ELEMENT) {
+ // childrenがなくても内部を調査
+ const nestedContent = extractElementText(child);
+
+ if (nestedContent) {
+ logger.info(`Found content in childless element: ${nestedContent}`);
+ return nestedContent;
+ }
+ }
+ }
+
+ return null;
+}
+
+
+/**
+ * SearchLabelとSearchKeywordを探して抽出する関数
+ */
+function extractLabelsAndKeywords(nodes: VueAstNode[]): { label: string | null, keywords: any[] } {
+ let label: string | null = null;
+ const keywords: any[] = [];
+
+ logger.info(`Extracting labels and keywords from ${nodes.length} nodes`);
+
+ // 再帰的にSearchLabelとSearchKeywordを探索(ネストされたSearchMarkerは処理しない)
+ function findComponents(nodes: VueAstNode[]) {
+ for (const node of nodes) {
+ if (node.type === NODE_TYPES.ELEMENT) {
+ logger.info(`Checking element: ${node.tag}`);
+
+ // SearchMarkerの場合は、その子要素は別スコープなのでスキップ
+ if (node.tag === 'SearchMarker') {
+ logger.info(`Found nested SearchMarker - skipping its content to maintain scope isolation`);
+ continue; // このSearchMarkerの中身は処理しない (スコープ分離)
+ }
+
+ // SearchLabelの処理
+ if (node.tag === 'SearchLabel') {
+ logger.info(`Found SearchLabel node, structure: ${JSON.stringify(node).substring(0, 200)}...`);
+
+ // まず完全なノード内容の抽出を試みる
+ const content = extractElementText(node);
+ if (content) {
+ label = content;
+ logger.info(`SearchLabel content extracted: ${content}`);
+ } else {
+ logger.info(`SearchLabel found but extraction failed, trying direct children inspection`);
+
+ // バックアップ: 子直接確認 - type=5のMustacheインターポレーションを重点的に確認
+ if (node.children && Array.isArray(node.children)) {
+ for (const child of node.children) {
+ // Mustacheインターポレーション
+ if (child.type === NODE_TYPES.INTERPOLATION && child.content) {
+ // content内の式を取り出す
+ const expression = child.content.content ||
+ (child.content.type === 4 ? child.content.content : null) ||
+ JSON.stringify(child.content);
+
+ logger.info(`Interpolation expression: ${expression}`);
+ if (typeof expression === 'string' && isI18nReference(expression)) {
+ label = expression.trim();
+ logger.info(`Found i18n in interpolation: ${label}`);
+ break;
+ }
+ }
+ // 式ノード
+ else if (child.type === NODE_TYPES.EXPRESSION && child.content && isI18nReference(child.content)) {
+ label = child.content.trim();
+ logger.info(`Found i18n in expression: ${label}`);
+ break;
+ }
+ // テキストノードでもMustache構文を探す
+ else if (child.type === NODE_TYPES.TEXT && child.content) {
+ const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/);
+ if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) {
+ label = mustacheMatch[1].trim();
+ logger.info(`Found i18n in text mustache: ${label}`);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ // SearchKeywordの処理
+ else if (node.tag === 'SearchKeyword') {
+ logger.info(`Found SearchKeyword node`);
+
+ // まず完全なノード内容の抽出を試みる
+ const content = extractElementText(node);
+ if (content) {
+ keywords.push(content);
+ logger.info(`SearchKeyword content extracted: ${content}`);
+ } else {
+ logger.info(`SearchKeyword found but extraction failed, trying direct children inspection`);
+
+ // バックアップ: 子直接確認 - type=5のMustacheインターポレーションを重点的に確認
+ if (node.children && Array.isArray(node.children)) {
+ for (const child of node.children) {
+ // Mustacheインターポレーション
+ if (child.type === NODE_TYPES.INTERPOLATION && child.content) {
+ // content内の式を取り出す
+ const expression = child.content.content ||
+ (child.content.type === 4 ? child.content.content : null) ||
+ JSON.stringify(child.content);
+
+ logger.info(`Keyword interpolation: ${expression}`);
+ if (typeof expression === 'string' && isI18nReference(expression)) {
+ const keyword = expression.trim();
+ keywords.push(keyword);
+ logger.info(`Found i18n keyword in interpolation: ${keyword}`);
+ break;
+ }
+ }
+ // 式ノード
+ else if (child.type === NODE_TYPES.EXPRESSION && child.content && isI18nReference(child.content)) {
+ const keyword = child.content.trim();
+ keywords.push(keyword);
+ logger.info(`Found i18n keyword in expression: ${keyword}`);
+ break;
+ }
+ // テキストノードでもMustache構文を探す
+ else if (child.type === NODE_TYPES.TEXT && child.content) {
+ const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/);
+ if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) {
+ const keyword = mustacheMatch[1].trim();
+ keywords.push(keyword);
+ logger.info(`Found i18n keyword in text mustache: ${keyword}`);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // 子要素を再帰的に調査(ただしSearchMarkerは除外)
+ if (node.children && Array.isArray(node.children)) {
+ findComponents(node.children);
+ }
+ }
+ }
+ }
+
+ findComponents(nodes);
+
+ // デバッグ情報
+ logger.info(`Extraction completed: label=${label}, keywords=[${keywords.join(', ')}]`);
+ return { label, keywords };
+}
+
+
+function extractUsageInfoFromTemplateAst(
+ templateAst: any,
+ id: string,
+): SearchIndexItem[] {
+ const allMarkers: SearchIndexItem[] = [];
+ const markerMap = new Map<string, SearchIndexItem>();
+ const childrenIds = new Set<string>();
+ const normalizedId = id.replace(/\\/g, '/');
+
+ if (!templateAst) return allMarkers;
+
+ // マーカーの基本情報を収集
+ function collectMarkers(node: VueAstNode, parentId: string | null = null) {
+ if (node.type === 1 && node.tag === 'SearchMarker') {
+ // マーカーID取得
+ const markerIdProp = node.props?.find((p: any) => p.name === 'markerId');
+ const markerId = markerIdProp?.value?.content ||
+ node.__markerId;
+
+ // SearchMarkerにマーカーIDがない場合はエラー
+ if (markerId == null) {
+ logger.error(`Marker ID not found for node: ${JSON.stringify(node)}`);
+ throw new Error(`Marker ID not found in file ${id}`);
+ }
+
+ // マーカー基本情報
+ const markerInfo: SearchIndexItem = {
+ id: markerId,
+ children: [],
+ label: '', // デフォルト値
+ keywords: [],
+ };
+
+ // 静的プロパティを取得
+ if (node.props && Array.isArray(node.props)) {
+ for (const prop of node.props) {
+ if (prop.type === 6 && prop.name && prop.name !== 'markerId') {
+ if (prop.name === 'path') markerInfo.path = prop.value?.content || '';
+ else if (prop.name === 'icon') markerInfo.icon = prop.value?.content || '';
+ else if (prop.name === 'label') markerInfo.label = prop.value?.content || '';
+ }
+ }
+ }
+
+ // バインドプロパティを取得
+ const bindings = extractNodeBindings(node);
+ if (bindings.path) markerInfo.path = bindings.path;
+ if (bindings.icon) markerInfo.icon = bindings.icon;
+ if (bindings.label) markerInfo.label = bindings.label;
+ if (bindings.children) markerInfo.children = bindings.children;
+ if (bindings.inlining) {
+ markerInfo.inlining = bindings.inlining;
+ logger.info(`Added inlining ${JSON.stringify(bindings.inlining)} to marker ${markerId}`);
+ }
+ if (bindings.keywords) {
+ if (Array.isArray(bindings.keywords)) {
+ markerInfo.keywords = bindings.keywords;
+ } else {
+ markerInfo.keywords = bindings.keywords || [];
+ }
+ }
+
+ //pathがない場合はファイルパスを設定
+ if (markerInfo.path == null && parentId == null) {
+ markerInfo.path = normalizedId.match(/.*(\/(admin|settings)\/[^\/]+)\.vue$/)?.[1];
+ }
+
+ // SearchLabelとSearchKeywordを抽出 (AST全体を探索)
+ if (node.children && Array.isArray(node.children)) {
+ logger.info(`Processing marker ${markerId} for labels and keywords`);
+ const extracted = extractLabelsAndKeywords(node.children);
+
+ // SearchLabelからのラベル取得は最優先で適用
+ if (extracted.label) {
+ markerInfo.label = extracted.label;
+ logger.info(`Using extracted label for ${markerId}: ${extracted.label}`);
+ } else if (markerInfo.label) {
+ logger.info(`Using existing label for ${markerId}: ${markerInfo.label}`);
+ } else {
+ markerInfo.label = 'Unnamed marker';
+ logger.info(`No label found for ${markerId}, using default`);
+ }
+
+ // SearchKeywordからのキーワード取得を追加
+ if (extracted.keywords.length > 0) {
+ const existingKeywords = Array.isArray(markerInfo.keywords) ?
+ [...markerInfo.keywords] :
+ (markerInfo.keywords ? [markerInfo.keywords] : []);
+
+ // i18n参照のキーワードは最優先で追加
+ const combinedKeywords = [...existingKeywords];
+ for (const kw of extracted.keywords) {
+ combinedKeywords.push(kw);
+ logger.info(`Added extracted keyword to ${markerId}: ${kw}`);
+ }
+
+ markerInfo.keywords = combinedKeywords;
+ }
+ }
+
+ // マーカーを登録
+ markerMap.set(markerId, markerInfo);
+ allMarkers.push(markerInfo);
+
+ // 親子関係を記録
+ if (parentId) {
+ const parent = markerMap.get(parentId);
+ if (parent) {
+ childrenIds.add(markerId);
+ }
+ }
+
+ // 子ノードを処理
+ if (node.children && Array.isArray(node.children)) {
+ node.children.forEach((child: VueAstNode) => {
+ collectMarkers(child, markerId);
+ });
+ }
+
+ return markerId;
+ }
+ // SearchMarkerでない場合は再帰的に子ノードを処理
+ else if (node.children && Array.isArray(node.children)) {
+ node.children.forEach((child: VueAstNode) => {
+ collectMarkers(child, parentId);
+ });
+ }
+
+ return null;
+ }
+
+ // AST解析開始
+ collectMarkers(templateAst);
+ return allMarkers;
+}
+
+// バインドプロパティの処理を修正する関数
+function extractNodeBindings(node: VueAstNode): Record<keyof SearchIndexItem, any> {
+ const bindings: Record<string, any> = {};
+
+ if (!node.props || !Array.isArray(node.props)) return bindings;
+
+ // バインド式を収集
+ for (const prop of node.props) {
+ if (prop.type === 7 && prop.name === 'bind' && prop.arg?.content) {
+ const propName = prop.arg.content;
+ const propContent = prop.exp?.content || '';
+
+ logger.info(`Processing bind prop ${propName}: ${propContent}`);
+
+ // inliningプロパティの処理を追加
+ if (propName === 'inlining') {
+ try {
+ const content = propContent.trim();
+
+ // 配列式の場合
+ if (content.startsWith('[') && content.endsWith(']')) {
+ // 配列要素を解析
+ const elements = parseArrayExpression(content);
+ if (elements.length > 0) {
+ bindings.inlining = elements;
+ logger.info(`Parsed inlining array: ${JSON5.stringify(elements)}`);
+ } else {
+ bindings.inlining = [];
+ }
+ }
+ // 文字列の場合は配列に変換
+ else if (content) {
+ bindings.inlining = [content]; // 単一の値を配列に
+ logger.info(`Converting inlining to array: [${content}]`);
+ }
+ } catch (e) {
+ logger.error(`Failed to parse inlining binding: ${propContent}`, e);
+ }
+ }
+ // keywordsの特殊処理
+ if (propName === 'keywords') {
+ try {
+ const content = propContent.trim();
+
+ // 配列式の場合
+ if (content.startsWith('[') && content.endsWith(']')) {
+ // i18n参照や特殊な式を保持するため、各要素を個別に解析
+ const elements = parseArrayExpression(content);
+ if (elements.length > 0) {
+ bindings.keywords = elements;
+ logger.info(`Parsed keywords array: ${JSON5.stringify(elements)}`);
+ } else {
+ bindings.keywords = [];
+ logger.info('Empty keywords array');
+ }
+ }
+ // その他の式(非配列)
+ else if (content) {
+ bindings.keywords = content; // 式をそのまま保持
+ logger.info(`Keeping keywords as expression: ${content}`);
+ } else {
+ bindings.keywords = [];
+ logger.info('No keywords provided');
+ }
+ } catch (e) {
+ logger.error(`Failed to parse keywords binding: ${propContent}`, e);
+ // エラーが起きても何らかの値を設定
+ bindings.keywords = propContent || [];
+ }
+ }
+ // その他のプロパティ
+ else if (propName === 'label') {
+ // ラベルの場合も式として保持
+ bindings[propName] = propContent;
+ logger.info(`Set label from bind expression: ${propContent}`);
+ }
+ else {
+ bindings[propName] = propContent;
+ }
+ }
+ }
+
+ return bindings;
+}
+
+// 配列式をパースする補助関数(文字列リテラル処理を改善)
+function parseArrayExpression(expr: string): any[] {
+ try {
+ // 単純なケースはJSON5でパースを試みる
+ return JSON5.parse(expr.replace(/'/g, '"'));
+ } catch (e) {
+ // 複雑なケース(i18n.ts.xxx などの式を含む場合)は手動パース
+ logger.info(`Complex array expression, trying manual parsing: ${expr}`);
+
+ // "["と"]"を取り除く
+ const content = expr.substring(1, expr.length - 1).trim();
+ if (!content) return [];
+
+ const result: any[] = [];
+ let currentItem = '';
+ let depth = 0;
+ let inString = false;
+ let stringChar = '';
+
+ // カンマで区切る(ただし文字列内や入れ子の配列内のカンマは無視)
+ for (let i = 0; i < content.length; i++) {
+ const char = content[i];
+
+ if (inString) {
+ if (char === stringChar && content[i - 1] !== '\\') {
+ inString = false;
+ }
+ currentItem += char;
+ } else if (char === '"' || char === "'") {
+ inString = true;
+ stringChar = char;
+ currentItem += char;
+ } else if (char === '[') {
+ depth++;
+ currentItem += char;
+ } else if (char === ']') {
+ depth--;
+ currentItem += char;
+ } else if (char === ',' && depth === 0) {
+ // 項目の区切りを検出
+ const trimmed = currentItem.trim();
+
+ // 純粋な文字列リテラルの場合、実際の値に変換
+ if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
+ (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
+ try {
+ result.push(JSON5.parse(trimmed));
+ } catch (err) {
+ result.push(trimmed);
+ }
+ } else {
+ // それ以外の式はそのまま(i18n.ts.xxx など)
+ result.push(trimmed);
+ }
+
+ currentItem = '';
+ } else {
+ currentItem += char;
+ }
+ }
+
+ // 最後の項目を処理
+ if (currentItem.trim()) {
+ const trimmed = currentItem.trim();
+
+ // 純粋な文字列リテラルの場合、実際の値に変換
+ if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
+ (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
+ try {
+ result.push(JSON5.parse(trimmed));
+ } catch (err) {
+ result.push(trimmed);
+ }
+ } else {
+ // それ以外の式はそのまま(i18n.ts.xxx など)
+ result.push(trimmed);
+ }
+ }
+
+ logger.info(`Parsed complex array expression: ${expr} -> ${JSON.stringify(result)}`);
+ return result;
+ }
+}
+
+export async function analyzeVueProps(options: Options & {
+ transformedCodeCache: Record<string, string>,
+}): Promise<void> {
+ initLogger(options);
+
+ const allMarkers: SearchIndexItem[] = [];
+
+ // 対象ファイルパスを glob で展開
+ const filePaths = options.targetFilePaths.reduce<string[]>((acc, filePathPattern) => {
+ const matchedFiles = glob.sync(filePathPattern);
+ return [...acc, ...matchedFiles];
+ }, []);
+
+ logger.info(`Found ${filePaths.length} matching files to analyze`);
+
+ for (const filePath of filePaths) {
+ const absolutePath = path.join(process.cwd(), filePath);
+ const id = absolutePath.replace(/\\/g, '/'); // 絶対パスに変換
+ const code = options.transformedCodeCache[id]; // options 経由でキャッシュ参照
+ if (!code) { // キャッシュミスの場合
+ logger.error(`Error: No cached code found for: ${id}.`); // エラーログ
+ throw new Error(`No cached code found for: ${id}.`); // エラーを投げる
+ }
+
+ try {
+ const { descriptor, errors } = vueSfcParse(options.transformedCodeCache[id], {
+ filename: filePath,
+ });
+
+ if (errors.length > 0) {
+ logger.error(`Compile Error: ${filePath}, ${errors}`);
+ continue; // エラーが発生したファイルはスキップ
+ }
+
+ const fileMarkers = extractUsageInfoFromTemplateAst(descriptor.template?.ast, id);
+
+ if (fileMarkers && fileMarkers.length > 0) {
+ allMarkers.push(...fileMarkers); // すべてのマーカーを収集
+ logger.info(`Successfully extracted ${fileMarkers.length} markers from ${filePath}`);
+ } else {
+ logger.info(`No markers found in ${filePath}`);
+ }
+ } catch (error) {
+ logger.error(`Error analyzing file ${filePath}:`, error);
+ }
+ }
+
+ // 収集したすべてのマーカー情報を使用
+ const analysisResult: AnalysisResult[] = [
+ {
+ filePath: "combined-markers", // すべてのファイルのマーカーを1つのエントリとして扱う
+ usage: allMarkers,
+ }
+ ];
+
+ outputAnalysisResultAsTS(options.exportFilePath, analysisResult); // すべてのマーカー情報を渡す
+}
+
+interface MarkerRelation {
+ parentId?: string;
+ markerId: string;
+ node: VueAstNode;
+}
+
+async function processVueFile(
+ code: string,
+ id: string,
+ options: Options,
+ transformedCodeCache: Record<string, string>
+): Promise<{
+ code: string,
+ map: any,
+ transformedCodeCache: Record<string, string>
+}> {
+ const normalizedId = id.replace(/\\/g, '/'); // ファイルパスを正規化
+
+ // 開発モード時はコード内容に変更があれば常に再処理する
+ // コード内容が同じ場合のみキャッシュを使用
+ const isDevMode = process.env.NODE_ENV === 'development';
+
+ const s = new MagicString(code); // magic-string のインスタンスを作成
+
+ if (!isDevMode && transformedCodeCache[normalizedId] && transformedCodeCache[normalizedId].includes('markerId=')) {
+ logger.info(`Using cached version for ${id}`);
+ return {
+ code: transformedCodeCache[normalizedId],
+ map: s.generateMap({ source: id, includeContent: true }),
+ transformedCodeCache
+ };
+ }
+
+ // すでに処理済みのファイルでコードに変更がない場合はキャッシュを返す
+ if (transformedCodeCache[normalizedId] === code) {
+ logger.info(`Code unchanged for ${id}, using cached version`);
+ return {
+ code: transformedCodeCache[normalizedId],
+ map: s.generateMap({ source: id, includeContent: true }),
+ transformedCodeCache
+ };
+ }
+
+ const parsed = vueSfcParse(code, { filename: id });
+ if (!parsed.descriptor.template) {
+ return {
+ code,
+ map: s.generateMap({ source: id, includeContent: true }),
+ transformedCodeCache
+ };
+ }
+ const ast = parsed.descriptor.template.ast; // テンプレート AST を取得
+ const markerRelations: MarkerRelation[] = []; // MarkerRelation 配列を初期化
+
+ if (ast) {
+ function traverse(node: any, currentParent?: any) {
+ if (node.type === 1 && node.tag === 'SearchMarker') {
+ // 行番号はコード先頭からの改行数で取得
+ const lineNumber = code.slice(0, node.loc.start.offset).split('\n').length;
+ // ファイルパスと行番号からハッシュ値を生成
+ // この際実行環境で差が出ないようにファイルパスを正規化
+ const idKey = id.replace(/\\/g, '/').split('packages/frontend/')[1]
+ const generatedMarkerId = toBase62(hash(`${idKey}:${lineNumber}`));
+
+ const props = node.props || [];
+ const hasMarkerIdProp = props.some((prop: any) => prop.type === 6 && prop.name === 'markerId');
+ const nodeMarkerId = hasMarkerIdProp
+ ? props.find((prop: any) => prop.type === 6 && prop.name === 'markerId')?.value?.content as string
+ : generatedMarkerId;
+ node.__markerId = nodeMarkerId;
+
+ // 子マーカーの場合、親ノードに __children を設定しておく
+ if (currentParent && currentParent.type === 1 && currentParent.tag === 'SearchMarker') {
+ currentParent.__children = currentParent.__children || [];
+ currentParent.__children.push(nodeMarkerId);
+ }
+
+ const parentMarkerId = currentParent && currentParent.__markerId;
+ markerRelations.push({
+ parentId: parentMarkerId,
+ markerId: nodeMarkerId,
+ node: node,
+ });
+
+ if (!hasMarkerIdProp) {
+ const nodeStart = node.loc.start.offset;
+ let endOfStartTag;
+
+ if (node.children && node.children.length > 0) {
+ // 子要素がある場合、最初の子要素の開始位置を基準にする
+ endOfStartTag = code.lastIndexOf('>', node.children[0].loc.start.offset);
+ } else if (node.loc.end.offset > nodeStart) {
+ // 子要素がない場合、自身の終了位置から逆算
+ const nodeSource = code.substring(nodeStart, node.loc.end.offset);
+ // 自己終了タグか通常の終了タグかを判断
+ if (nodeSource.includes('/>')) {
+ endOfStartTag = code.indexOf('/>', nodeStart) - 1;
+ } else {
+ endOfStartTag = code.indexOf('>', nodeStart);
+ }
+ }
+
+ if (endOfStartTag !== undefined && endOfStartTag !== -1) {
+ // markerId が既に存在しないことを確認
+ const tagText = code.substring(nodeStart, endOfStartTag + 1);
+ const markerIdRegex = /\s+markerId\s*=\s*["'][^"']*["']/;
+
+ if (!markerIdRegex.test(tagText)) {
+ s.appendRight(endOfStartTag, ` markerId="${generatedMarkerId}" data-in-app-search-marker-id="${generatedMarkerId}"`);
+ logger.info(`Adding markerId="${generatedMarkerId}" to ${id}:${lineNumber}`);
+ } else {
+ logger.info(`markerId already exists in ${id}:${lineNumber}`);
+ }
+ }
+ }
+ }
+
+ const newParent = node.type === 1 && node.tag === 'SearchMarker' ? node : currentParent;
+ if (node.children && Array.isArray(node.children)) {
+ node.children.forEach(child => traverse(child, newParent));
+ }
+ }
+
+ traverse(ast); // AST を traverse (1段階目: ID 生成と親子関係記録)
+
+ // 2段階目: :children 属性の追加
+ // 最初に親マーカーごとに子マーカーIDを集約する処理を追加
+ const parentChildrenMap = new Map<string, string[]>();
+
+ // 1. まず親ごとのすべての子マーカーIDを収集
+ markerRelations.forEach(relation => {
+ if (relation.parentId) {
+ if (!parentChildrenMap.has(relation.parentId)) {
+ parentChildrenMap.set(relation.parentId, []);
+ }
+ parentChildrenMap.get(relation.parentId)?.push(relation.markerId);
+ }
+ });
+
+ // 2. 親ごとにまとめて :children 属性を処理
+ for (const [parentId, childIds] of parentChildrenMap.entries()) {
+ const parentRelation = markerRelations.find(r => r.markerId === parentId);
+ if (!parentRelation || !parentRelation.node) continue;
+
+ const parentNode = parentRelation.node;
+ const childrenProp = parentNode.props?.find((prop: any) => prop.type === 7 && prop.name === 'bind' && prop.arg?.content === 'children');
+
+ // 親ノードの開始位置を特定
+ const parentNodeStart = parentNode.loc!.start.offset;
+ const endOfParentStartTag = parentNode.children && parentNode.children.length > 0
+ ? code.lastIndexOf('>', parentNode.children[0].loc!.start.offset)
+ : code.indexOf('>', parentNodeStart);
+
+ if (endOfParentStartTag === -1) continue;
+
+ // 親タグのテキストを取得
+ const parentTagText = code.substring(parentNodeStart, endOfParentStartTag + 1);
+
+ if (childrenProp) {
+ // AST で :children 属性が検出された場合、それを更新
+ try {
+ const childrenStart = code.indexOf('[', childrenProp.exp!.loc.start.offset);
+ const childrenEnd = code.indexOf(']', childrenProp.exp!.loc.start.offset);
+ if (childrenStart !== -1 && childrenEnd !== -1) {
+ const childrenArrayStr = code.slice(childrenStart, childrenEnd + 1);
+ let childrenArray = JSON5.parse(childrenArrayStr.replace(/'/g, '"'));
+
+ // 新しいIDを追加(重複は除外)
+ const newIds = childIds.filter(id => !childrenArray.includes(id));
+ if (newIds.length > 0) {
+ childrenArray = [...childrenArray, ...newIds];
+ const updatedChildrenArrayStr = JSON5.stringify(childrenArray).replace(/"/g, "'");
+ s.overwrite(childrenStart, childrenEnd + 1, updatedChildrenArrayStr);
+ logger.info(`Added ${newIds.length} child markerIds to existing :children in ${id}`);
+ }
+ }
+ } catch (e) {
+ logger.error('Error updating :children attribute:', e);
+ }
+ } else {
+ // AST では検出されなかった場合、タグテキストを調べる
+ const childrenRegex = /:children\s*=\s*["']\[(.*?)\]["']/;
+ const childrenMatch = parentTagText.match(childrenRegex);
+
+ if (childrenMatch) {
+ // テキストから :children 属性値を解析して更新
+ try {
+ const childrenContent = childrenMatch[1];
+ const childrenArrayStr = `[${childrenContent}]`;
+ const childrenArray = JSON5.parse(childrenArrayStr.replace(/'/g, '"'));
+
+ // 新しいIDを追加(重複は除外)
+ const newIds = childIds.filter(id => !childrenArray.includes(id));
+ if (newIds.length > 0) {
+ childrenArray.push(...newIds);
+
+ // :children="[...]" の位置を特定して上書き
+ const attrStart = parentTagText.indexOf(':children=');
+ if (attrStart > -1) {
+ const attrValueStart = parentTagText.indexOf('[', attrStart);
+ const attrValueEnd = parentTagText.indexOf(']', attrValueStart) + 1;
+ if (attrValueStart > -1 && attrValueEnd > -1) {
+ const absoluteStart = parentNodeStart + attrValueStart;
+ const absoluteEnd = parentNodeStart + attrValueEnd;
+ const updatedArrayStr = JSON5.stringify(childrenArray).replace(/"/g, "'");
+ s.overwrite(absoluteStart, absoluteEnd, updatedArrayStr);
+ logger.info(`Updated existing :children in tag text for ${id}`);
+ }
+ }
+ }
+ } catch (e) {
+ logger.error('Error updating :children in tag text:', e);
+ }
+ } else {
+ // :children 属性がまだない場合、新規作成
+ s.appendRight(endOfParentStartTag, ` :children="${JSON5.stringify(childIds).replace(/"/g, "'")}"`);
+ logger.info(`Created new :children attribute with ${childIds.length} markerIds in ${id}`);
+ }
+ }
+ }
+ }
+
+ const transformedCode = s.toString(); // 変換後のコードを取得
+ transformedCodeCache[normalizedId] = transformedCode; // 変換後のコードをキャッシュに保存
+
+ return {
+ code: transformedCode, // 変更後のコードを返す
+ map: s.generateMap({ source: id, includeContent: true }), // ソースマップも生成 (sourceMap: true が必要)
+ transformedCodeCache // キャッシュも返す
+ };
+}
+
+export async function generateSearchIndex(options: Options, transformedCodeCache: Record<string, string> = {}) {
+ const filePaths = options.targetFilePaths.reduce<string[]>((acc, filePathPattern) => {
+ const matchedFiles = glob.sync(filePathPattern);
+ return [...acc, ...matchedFiles];
+ }, []);
+
+ for (const filePath of filePaths) {
+ const id = path.resolve(filePath); // 絶対パスに変換
+ const code = fs.readFileSync(filePath, 'utf-8'); // ファイル内容を読み込む
+ const { transformedCodeCache: newCache } = await processVueFile(code, id, options, transformedCodeCache); // processVueFile 関数を呼び出す
+ transformedCodeCache = newCache; // キャッシュを更新
+ }
+
+ await analyzeVueProps({ ...options, transformedCodeCache }); // 開発サーバー起動時にも analyzeVueProps を実行
+
+ return transformedCodeCache; // キャッシュを返す
+}
+
+// Rollup プラグインとして export
+export default function pluginCreateSearchIndex(options: Options): Plugin {
+ let transformedCodeCache: Record<string, string> = {}; // キャッシュオブジェクトをプラグインスコープで定義
+ const isDevServer = process.env.NODE_ENV === 'development'; // 開発サーバーかどうか
+
+ initLogger(options); // ロガーを初期化
+
+ return {
+ name: 'createSearchIndex',
+ enforce: 'pre',
+
+ async buildStart() {
+ if (!isDevServer) {
+ return;
+ }
+
+ transformedCodeCache = await generateSearchIndex(options, transformedCodeCache);
+ },
+
+ async transform(code, id) {
+ if (!id.endsWith('.vue')) {
+ return;
+ }
+
+ // targetFilePaths にマッチするファイルのみ処理を行う
+ // glob パターンでマッチング
+ let isMatch = false; // isMatch の初期値を false に設定
+ for (const pattern of options.targetFilePaths) { // パターンごとにマッチング確認
+ const globbedFiles = glob.sync(pattern);
+ for (const globbedFile of globbedFiles) {
+ const normalizedGlobbedFile = path.resolve(globbedFile); // glob 結果を絶対パスに
+ const normalizedId = path.resolve(id); // id を絶対パスに
+ if (normalizedGlobbedFile === normalizedId) { // 絶対パス同士で比較
+ isMatch = true;
+ break; // マッチしたらループを抜ける
+ }
+ }
+ if (isMatch) break; // いずれかのパターンでマッチしたら、outer loop も抜ける
+ }
+
+ if (!isMatch) {
+ return;
+ }
+
+ // ファイルの内容が変更された場合は再処理を行う
+ const normalizedId = id.replace(/\\/g, '/');
+ const hasContentChanged = !transformedCodeCache[normalizedId] || transformedCodeCache[normalizedId] !== code;
+
+ const transformed = await processVueFile(code, id, options, transformedCodeCache);
+ transformedCodeCache = transformed.transformedCodeCache; // キャッシュを更新
+
+ if (isDevServer && hasContentChanged) {
+ await analyzeVueProps({ ...options, transformedCodeCache }); // ファイルが変更されたときのみ分析を実行
+ }
+
+ return transformed;
+ },
+
+ async writeBundle() {
+ await analyzeVueProps({ ...options, transformedCodeCache }); // ビルド時にも analyzeVueProps を実行
+ },
+ };
+}
+
+// i18n参照を検出するためのヘルパー関数を追加
+function isI18nReference(text: string | null | undefined): boolean {
+ if (!text) return false;
+ // ドット記法(i18n.ts.something)
+ const dotPattern = /i18n\.ts\.\w+/;
+ // ブラケット記法(i18n.ts['something'])
+ const bracketPattern = /i18n\.ts\[['"][^'"]+['"]\]/;
+ return dotPattern.test(text) || bracketPattern.test(text);
+}
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 325934122b..ce9d5db69d 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -5,6 +5,7 @@
"scripts": {
"watch": "vite",
"build": "vite build",
+ "build-search-index": "vite-node --config \"./vite-node.config.ts\" \"./scripts/generate-search-index.ts\"",
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
"build-storybook": "pnpm build-storybook-pre && storybook build --webpack-stats-json storybook-static",
@@ -16,34 +17,36 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"dependencies": {
+ "@analytics/google-analytics": "1.1.0",
"@discordapp/twemoji": "15.1.0",
"@github/webauthn-json": "2.1.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@misskey-dev/browser-image-resizer": "2024.1.0",
"@phosphor-icons/web": "^2.0.3",
"@rollup/plugin-json": "6.1.0",
- "@rollup/plugin-replace": "5.0.7",
- "@rollup/pluginutils": "5.1.3",
+ "@rollup/plugin-replace": "6.0.2",
+ "@rollup/pluginutils": "5.1.4",
"@ruffle-rs/ruffle": "0.1.0-nightly.2024.10.15",
"@syuilo/aiscript": "0.19.0",
"@transfem-org/sfm-js": "0.24.6",
"@twemoji/parser": "15.1.1",
- "@vitejs/plugin-vue": "5.2.0",
- "@vue/compiler-sfc": "3.5.12",
+ "@vitejs/plugin-vue": "5.2.3",
+ "@vue/compiler-sfc": "3.5.13",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
+ "analytics": "0.8.16",
"astring": "1.9.0",
"broadcast-channel": "7.0.0",
"buraha": "0.0.1",
"canvas-confetti": "1.9.3",
- "chart.js": "4.4.6",
+ "chart.js": "4.4.8",
"chartjs-adapter-date-fns": "3.0.0",
- "chartjs-chart-matrix": "2.0.1",
+ "chartjs-chart-matrix": "2.1.1",
"chartjs-plugin-gradient": "0.6.1",
- "chartjs-plugin-zoom": "2.0.1",
- "chromatic": "11.18.1",
+ "chartjs-plugin-zoom": "2.2.0",
+ "chromatic": "11.27.0",
"compare-versions": "6.1.1",
- "cropperjs": "2.0.0-rc.2",
- "date-fns": "2.30.0",
+ "cropperjs": "2.0.0",
+ "date-fns": "4.1.0",
"estree-walker": "3.0.3",
"eventemitter3": "5.0.1",
"frontend-shared": "workspace:*",
@@ -52,6 +55,7 @@
"is-file-animated": "1.0.2",
"json5": "2.2.3",
"katex": "0.16.10",
+ "magic-string": "0.30.17",
"matter-js": "0.20.0",
"misskey-bubble-game": "workspace:*",
"misskey-js": "workspace:*",
@@ -59,88 +63,89 @@
"moment": "^2.30.1",
"photoswipe": "5.4.4",
"punycode.js": "2.3.1",
- "rollup": "4.26.0",
- "sanitize-html": "2.13.1",
- "sass": "1.79.3",
- "shiki": "1.22.2",
+ "rollup": "4.36.0",
+ "sanitize-html": "2.15.0",
+ "sass": "1.86.0",
+ "shiki": "3.2.1",
"strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0",
- "three": "0.169.0",
+ "three": "0.174.0",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
- "tsc-alias": "1.8.10",
+ "tsc-alias": "1.8.11",
"tsconfig-paths": "4.2.0",
- "typescript": "5.6.3",
- "uuid": "10.0.0",
+ "typescript": "5.8.2",
+ "uuid": "11.1.0",
"v-code-diff": "1.13.1",
- "vite": "5.4.11",
- "vue": "3.5.12",
- "vuedraggable": "next"
+ "vite": "6.2.2",
+ "vue": "3.5.13",
+ "vuedraggable": "next",
+ "wanakana": "5.3.1"
},
"optionalDependencies": {
"cypress": "13.15.2"
},
"devDependencies": {
- "@misskey-dev/summaly": "5.1.0",
- "@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",
+ "@misskey-dev/summaly": "5.2.0",
+ "@storybook/addon-actions": "8.6.7",
+ "@storybook/addon-essentials": "8.6.7",
+ "@storybook/addon-interactions": "8.6.7",
+ "@storybook/addon-links": "8.6.7",
+ "@storybook/addon-mdx-gfm": "8.6.7",
+ "@storybook/addon-storysource": "8.6.7",
+ "@storybook/blocks": "8.6.7",
+ "@storybook/components": "8.6.7",
+ "@storybook/core-events": "8.6.7",
+ "@storybook/manager-api": "8.6.7",
+ "@storybook/preview-api": "8.6.7",
+ "@storybook/react": "8.6.7",
+ "@storybook/react-vite": "8.6.7",
+ "@storybook/test": "8.6.7",
+ "@storybook/theming": "8.6.7",
+ "@storybook/types": "8.6.7",
+ "@storybook/vue3": "8.6.7",
+ "@storybook/vue3-vite": "8.6.7",
"@testing-library/vue": "8.1.0",
- "@types/canvas-confetti": "^1.6.4",
+ "@types/canvas-confetti": "1.9.0",
"@types/estree": "1.0.6",
"@types/katex": "^0.16.7",
- "@types/matter-js": "0.19.7",
+ "@types/matter-js": "0.19.8",
"@types/micromatch": "4.0.9",
- "@types/node": "22.9.0",
+ "@types/node": "22.13.11",
"@types/punycode.js": "npm:@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.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.12",
- "acorn": "8.14.0",
+ "@types/ws": "8.18.0",
+ "@typescript-eslint/eslint-plugin": "8.27.0",
+ "@typescript-eslint/parser": "8.27.0",
+ "@vitest/coverage-v8": "3.0.9",
+ "@vue/runtime-core": "3.5.13",
+ "acorn": "8.14.1",
"cross-env": "7.0.3",
"eslint-plugin-import": "2.31.0",
- "eslint-plugin-vue": "9.31.0",
- "fast-glob": "3.3.2",
- "happy-dom": "10.0.3",
+ "eslint-plugin-vue": "10.0.0",
+ "fast-glob": "3.3.3",
+ "happy-dom": "17.4.4",
"intersection-observer": "0.12.2",
"micromatch": "4.0.8",
- "msw": "2.6.4",
+ "msw": "2.7.3",
"msw-storybook-addon": "2.0.4",
- "nodemon": "3.1.7",
- "prettier": "3.3.3",
- "react": "18.3.1",
- "react-dom": "18.3.1",
+ "nodemon": "3.1.9",
+ "prettier": "3.5.3",
+ "react": "19.0.0",
+ "react-dom": "19.0.0",
"seedrandom": "3.0.5",
- "start-server-and-test": "2.0.8",
- "storybook": "8.4.4",
+ "start-server-and-test": "2.0.11",
+ "storybook": "8.6.7",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
+ "vite-node": "3.0.9",
"vite-plugin-turbosnap": "1.0.3",
- "vitest": "1.6.0",
- "vitest-fetch-mock": "0.2.2",
- "vue-component-type-helpers": "2.1.10",
- "vue-eslint-parser": "9.4.3",
- "vue-tsc": "2.1.10"
+ "vitest": "3.0.9",
+ "vitest-fetch-mock": "0.4.5",
+ "vue-component-type-helpers": "2.2.8",
+ "vue-eslint-parser": "10.1.1",
+ "vue-tsc": "2.2.8"
}
}
diff --git a/packages/frontend/scripts/generate-search-index.ts b/packages/frontend/scripts/generate-search-index.ts
new file mode 100644
index 0000000000..cbb4bb8c51
--- /dev/null
+++ b/packages/frontend/scripts/generate-search-index.ts
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { searchIndexes } from '../vite.config.js';
+import { generateSearchIndex } from '../lib/vite-plugin-create-search-index.js';
+
+async function main() {
+ for (const searchIndex of searchIndexes) {
+ await generateSearchIndex(searchIndex);
+ }
+}
+
+main();
diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts
index 5c39955c28..68f998c7f3 100644
--- a/packages/frontend/src/_boot_.ts
+++ b/packages/frontend/src/_boot_.ts
@@ -10,9 +10,9 @@ import '@/style.scss';
import { mainBoot } from '@/boot/main-boot.js';
import { subBoot } from '@/boot/sub-boot.js';
-const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete'];
+const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/install-extensions'];
-if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
+if (subBootPaths.some(i => window.location.pathname === i || window.location.pathname.startsWith(i + '/'))) {
subBoot();
} else {
mainBoot();
diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
deleted file mode 100644
index b3fa151a22..0000000000
--- a/packages/frontend/src/account.ts
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-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 { del, get, set } from '@/scripts/idb-proxy.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';
-
-// TODO: 他のタブと永続化されたstateを同期
-
-type Account = Misskey.entities.MeDetailed & { token: string };
-
-const accountData = miLocalStorage.getItem('account');
-
-// TODO: 外部からはreadonlyに
-export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
-
-export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
-export const iAmAdmin = $i != null && $i.isAdmin;
-
-export function signinRequired() {
- if ($i == null) throw new Error('signin required');
- return $i;
-}
-
-export let notesCount = $i == null ? 0 : $i.notesCount;
-export function incNotesCount() {
- notesCount++;
-}
-
-export async function signout() {
- if (!$i) return;
-
- waiting();
- miLocalStorage.removeItem('account');
- await removeAccount($i.id);
- document.cookie = `token=; path=/; max-age=0${ location.protocol === 'https:' ? '; Secure' : ''}`;
- const accounts = await getAccounts();
-
- //#region Remove service worker registration
- try {
- if (navigator.serviceWorker.controller) {
- const registration = await navigator.serviceWorker.ready;
- const push = await registration.pushManager.getSubscription();
- if (push) {
- await window.fetch(`${apiUrl}/sw/unregister`, {
- method: 'POST',
- body: JSON.stringify({
- i: $i.token,
- endpoint: push.endpoint,
- }),
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- }
- }
-
- if (accounts.length === 0) {
- await navigator.serviceWorker.getRegistrations()
- .then(registrations => {
- return Promise.all(registrations.map(registration => registration.unregister()));
- });
- }
- } catch (err) {}
- //#endregion
-
- if (accounts.length > 0) login(accounts[0].token);
- else unisonReload('/');
-}
-
-export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> {
- return (await get('accounts')) || [];
-}
-
-export async function addAccount(id: Account['id'], token: Account['token']) {
- const accounts = await getAccounts();
- if (!accounts.some(x => x.id === id)) {
- await set('accounts', accounts.concat([{ id, token }]));
- }
-}
-
-export async function removeAccount(idOrToken: Account['id']) {
- const accounts = await getAccounts();
- const i = accounts.findIndex(x => x.id === idOrToken || x.token === idOrToken);
- if (i !== -1) accounts.splice(i, 1);
-
- if (accounts.length > 0) {
- await set('accounts', accounts);
- } else {
- await del('accounts');
- }
-}
-
-function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise<Account> {
- document.cookie = `token=; path=/; max-age=0${ location.protocol === 'https:' ? '; Secure' : ''}`;
- document.cookie = `token=${token}; path=/queue; max-age=86400${ location.protocol === 'https:' ? '; SameSite=Strict; Secure' : ''}`; // bull dashboardの認証とかで使う
-
- return new Promise((done, fail) => {
- window.fetch(`${apiUrl}/i`, {
- method: 'POST',
- body: JSON.stringify({
- i: token,
- }),
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => {
- if (res.status >= 500 && res.status < 600) {
- // サーバーエラー(5xx)の場合をrejectとする
- // (認証エラーなど4xxはresolve)
- return fail2(res);
- }
- res.json().then(done2, fail2);
- }))
- .then(async res => {
- if ('error' in res) {
- if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
- // SUSPENDED
- if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
- await showSuspendedDialog();
- }
- } else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') {
- // USER_IS_DELETED
- // アカウントが削除されている
- if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
- await alert({
- type: 'error',
- title: i18n.ts.accountDeleted,
- text: i18n.ts.accountDeletedDescription,
- });
- }
- } else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') {
- // AUTHENTICATION_FAILED
- // トークンが無効化されていたりアカウントが削除されたりしている
- if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
- await alert({
- type: 'error',
- title: i18n.ts.tokenRevoked,
- text: i18n.ts.tokenRevokedDescription,
- });
- }
- } else if (res.error.id === 'd5826d14-3982-4d2e-8011-b9e9f02499ef') {
- // rate limited
- const timeToWait = res.error.info?.resetMs ?? 1000;
- window.setTimeout(() => {
- fetchAccount(token, id, forceShowDialog).then(done, fail);
- }, timeToWait);
- return;
- } else {
- await alert({
- type: 'error',
- title: i18n.ts.failedToFetchAccountInformation,
- text: JSON.stringify(res.error),
- });
- }
-
- // rejectかつ理由がtrueの場合、削除対象であることを示す
- fail(true);
- } else {
- (res as Account).token = token;
- done(res as Account);
- }
- })
- .catch(fail);
- });
-}
-
-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;
- }
- miLocalStorage.setItem('account', JSON.stringify($i));
-}
-
-export async function refreshAccount() {
- if (!$i) return;
- return fetchAccount($i.token, $i.id)
- .then(updateAccount, reason => {
- if (reason === true) return signout();
- return;
- });
-}
-
-export async function login(token: Account['token'], redirect?: string) {
- const showing = ref(true);
- const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), {
- success: false,
- showing: showing,
- }, {
- closed: () => dispose(),
- });
- if (_DEV_) console.log('logging as token ', token);
- const me = await fetchAccount(token, undefined, true)
- .catch(reason => {
- if (reason === true) {
- // 削除対象の場合
- removeAccount(token);
- }
-
- showing.value = false;
- throw reason;
- });
- miLocalStorage.setItem('account', JSON.stringify(me));
- await addAccount(me.id, token);
-
- if (redirect) {
- // 他のタブは再読み込みするだけ
- reloadChannel.postMessage(null);
- // このページはredirectで指定された先に移動
- location.href = redirect;
- return;
- }
-
- unisonReload();
-}
-
-export async function openAccountMenu(opts: {
- includeCurrentAccount?: boolean;
- withExtraOperation: boolean;
- active?: Misskey.entities.UserDetailed['id'];
- onChoose?: (account: Misskey.entities.UserDetailed) => void;
-}, ev: MouseEvent) {
- if (!$i) return;
-
- async function switchAccount(account: Misskey.entities.UserDetailed) {
- const storedAccounts = await getAccounts();
- const found = storedAccounts.find(x => x.id === account.id);
- if (found == null) return;
- switchAccountWithToken(found.token);
- }
-
- function switchAccountWithToken(token: string) {
- login(token);
- }
-
- const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id));
- const accountsPromise = misskeyApi('users/show', { userIds: storedAccounts.map(x => x.id) });
-
- function createItem(account: Misskey.entities.UserDetailed) {
- return {
- type: 'user' as const,
- user: account,
- active: opts.active != null ? opts.active === account.id : false,
- action: () => {
- if (opts.onChoose) {
- opts.onChoose(account);
- } else {
- switchAccount(account);
- }
- },
- };
- }
-
- const accountItemPromises = storedAccounts.map(a => new Promise<ReturnType<typeof createItem> | MenuButton>(res => {
- accountsPromise.then(accounts => {
- const account = accounts.find(x => x.id === a.id);
- if (account == null) return res({
- type: 'button' as const,
- text: a.id,
- action: () => {
- switchAccountWithToken(a.token);
- },
- });
-
- res(createItem(account));
- });
- }));
-
- const menuItems: MenuItem[] = [];
-
- if (opts.withExtraOperation) {
- menuItems.push({
- type: 'link',
- text: i18n.ts.profile,
- to: `/@${$i.username}`,
- avatar: $i,
- }, {
- type: 'divider',
- });
-
- if (opts.includeCurrentAccount) {
- menuItems.push(createItem($i));
- }
-
- menuItems.push(...accountItemPromises);
-
- menuItems.push({
- type: 'parent',
- icon: 'ti ti-plus',
- text: i18n.ts.addAccount,
- children: [{
- text: i18n.ts.existingAccount,
- action: () => {
- getAccountWithSigninDialog().then(res => {
- if (res != null) {
- success();
- }
- });
- },
- }, {
- text: i18n.ts.createAccount,
- action: () => {
- getAccountWithSignupDialog().then(res => {
- if (res != null) {
- switchAccountWithToken(res.token);
- }
- });
- },
- }],
- }, {
- type: 'link',
- icon: 'ti ti-users',
- text: i18n.ts.manageAccounts,
- to: '/settings/accounts',
- }, {
- type: 'button' as const,
- icon: 'ph-power ph-bold ph-lg',
- text: i18n.ts.logout,
- action: () => { signout(); },
- });
- } else {
- if (opts.includeCurrentAccount) {
- menuItems.push(createItem($i));
- }
-
- menuItems.push(...accountItemPromises);
- }
-
- popupMenu(menuItems, ev.currentTarget ?? ev.target, {
- align: 'left',
- });
-}
-
-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/accounts.ts b/packages/frontend/src/accounts.ts
new file mode 100644
index 0000000000..a25f3c51d1
--- /dev/null
+++ b/packages/frontend/src/accounts.ts
@@ -0,0 +1,339 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { defineAsyncComponent, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import { apiUrl, host } from '@@/js/config.js';
+import type { MenuItem } from '@/types/menu.js';
+import { showSuspendedDialog } from '@/utility/show-suspended-dialog.js';
+import { i18n } from '@/i18n.js';
+import { miLocalStorage } from '@/local-storage.js';
+import { waiting, popup, popupMenu, success, alert } from '@/os.js';
+import { unisonReload, reloadChannel } from '@/utility/unison-reload.js';
+import { prefer } from '@/preferences.js';
+import { store } from '@/store.js';
+import { $i } from '@/i.js';
+import { signout } from '@/signout.js';
+
+type AccountWithToken = Misskey.entities.MeDetailed & { token: string };
+
+export async function getAccounts(): Promise<{
+ host: string;
+ user: Misskey.entities.User;
+ token: string | null;
+}[]> {
+ const tokens = store.s.accountTokens;
+ const accounts = prefer.s.accounts;
+ return accounts.map(([host, user]) => ({
+ host,
+ user,
+ token: tokens[host + '/' + user.id] ?? null,
+ }));
+}
+
+async function addAccount(host: string, user: Misskey.entities.User, token: AccountWithToken['token']) {
+ if (!prefer.s.accounts.some(x => x[0] === host && x[1].id === user.id)) {
+ store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + user.id]: token });
+ prefer.commit('accounts', [...prefer.s.accounts, [host, user]]);
+ }
+}
+
+export async function removeAccount(host: string, id: AccountWithToken['id']) {
+ const tokens = JSON.parse(JSON.stringify(store.s.accountTokens));
+ delete tokens[host + '/' + id];
+ store.set('accountTokens', tokens);
+ prefer.commit('accounts', prefer.s.accounts.filter(x => x[0] !== host || x[1].id !== id));
+}
+
+const isAccountDeleted = Symbol('isAccountDeleted');
+
+function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise<Misskey.entities.MeDetailed> {
+ return new Promise((done, fail) => {
+ window.fetch(`${apiUrl}/i`, {
+ method: 'POST',
+ body: JSON.stringify({
+ i: token,
+ }),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(res => new Promise<Misskey.entities.MeDetailed | { error: Record<string, any> }>((done2, fail2) => {
+ if (res.status >= 500 && res.status < 600) {
+ // サーバーエラー(5xx)の場合をrejectとする
+ // (認証エラーなど4xxはresolve)
+ return fail2(res);
+ }
+ res.json().then(done2, fail2);
+ }))
+ .then(async res => {
+ if ('error' in res) {
+ if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
+ // SUSPENDED
+ if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
+ await showSuspendedDialog();
+ }
+ } else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') {
+ // USER_IS_DELETED
+ // アカウントが削除されている
+ if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
+ await alert({
+ type: 'error',
+ title: i18n.ts.accountDeleted,
+ text: i18n.ts.accountDeletedDescription,
+ });
+ }
+ } else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') {
+ // AUTHENTICATION_FAILED
+ // トークンが無効化されていたりアカウントが削除されたりしている
+ if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
+ await alert({
+ type: 'error',
+ title: i18n.ts.tokenRevoked,
+ text: i18n.ts.tokenRevokedDescription,
+ });
+ }
+ } else {
+ await alert({
+ type: 'error',
+ title: i18n.ts.failedToFetchAccountInformation,
+ text: JSON.stringify(res.error),
+ });
+ }
+
+ fail(isAccountDeleted);
+ } else {
+ done(res);
+ }
+ })
+ .catch(fail);
+ });
+}
+
+export function updateCurrentAccount(accountData: Misskey.entities.MeDetailed) {
+ if (!$i) return;
+ const token = $i.token;
+ for (const key of Object.keys($i)) {
+ delete $i[key];
+ }
+ for (const [key, value] of Object.entries(accountData)) {
+ $i[key] = value;
+ }
+ prefer.commit('accounts', prefer.s.accounts.map(([host, user]) => {
+ // TODO: $iのホストも比較したいけど通常null
+ if (user.id === $i.id) {
+ return [host, $i];
+ } else {
+ return [host, user];
+ }
+ }));
+ $i.token = token;
+ miLocalStorage.setItem('account', JSON.stringify($i));
+}
+
+export function updateCurrentAccountPartial(accountData: Partial<Misskey.entities.MeDetailed>) {
+ if (!$i) return;
+ for (const [key, value] of Object.entries(accountData)) {
+ $i[key] = value;
+ }
+ prefer.commit('accounts', prefer.s.accounts.map(([host, user]) => {
+ // TODO: $iのホストも比較したいけど通常null
+ if (user.id === $i.id) {
+ const newUser = JSON.parse(JSON.stringify($i));
+ for (const [key, value] of Object.entries(accountData)) {
+ newUser[key] = value;
+ }
+ return [host, newUser];
+ }
+ return [host, user];
+ }));
+ miLocalStorage.setItem('account', JSON.stringify($i));
+}
+
+export async function refreshCurrentAccount() {
+ if (!$i) return;
+ return fetchAccount($i.token, $i.id).then(updateCurrentAccount).catch(reason => {
+ if (reason === isAccountDeleted) {
+ removeAccount(host, $i.id);
+ if (Object.keys(store.s.accountTokens).length > 0) {
+ login(Object.values(store.s.accountTokens)[0]);
+ } else {
+ signout();
+ }
+ }
+ });
+}
+
+export async function login(token: AccountWithToken['token'], redirect?: string) {
+ const showing = ref(true);
+ const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkWaitingDialog.vue')), {
+ success: false,
+ showing: showing,
+ }, {
+ closed: () => dispose(),
+ });
+
+ const me = await fetchAccount(token, undefined, true).catch(reason => {
+ showing.value = false;
+ throw reason;
+ });
+
+ miLocalStorage.setItem('account', JSON.stringify({
+ ...me,
+ token,
+ }));
+
+ await addAccount(host, me, token);
+
+ if (redirect) {
+ // 他のタブは再読み込みするだけ
+ reloadChannel.postMessage(null);
+ // このページはredirectで指定された先に移動
+ window.location.href = redirect;
+ return;
+ }
+
+ unisonReload();
+}
+
+export async function switchAccount(host: string, id: string) {
+ const token = store.s.accountTokens[host + '/' + id];
+ if (token) {
+ login(token);
+ } else {
+ const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
+ done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => {
+ store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + res.id]: res.i });
+ login(res.i);
+ },
+ closed: () => {
+ dispose();
+ },
+ });
+ }
+}
+
+export async function openAccountMenu(opts: {
+ includeCurrentAccount?: boolean;
+ withExtraOperation: boolean;
+ active?: Misskey.entities.User['id'];
+ onChoose?: (account: Misskey.entities.User) => void;
+}, ev: MouseEvent) {
+ if (!$i) return;
+
+ function createItem(host: string, account: Misskey.entities.User): MenuItem {
+ return {
+ type: 'user' as const,
+ user: account,
+ active: opts.active != null ? opts.active === account.id : false,
+ action: async () => {
+ if (opts.onChoose) {
+ opts.onChoose(account);
+ } else {
+ switchAccount(host, account.id);
+ }
+ },
+ };
+ }
+
+ const menuItems: MenuItem[] = [];
+
+ // TODO: $iのホストも比較したいけど通常null
+ const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.user.id !== $i.id))).map(a => createItem(a.host, a.user));
+
+ if (opts.withExtraOperation) {
+ menuItems.push({
+ type: 'link',
+ text: i18n.ts.profile,
+ to: `/@${$i.username}`,
+ avatar: $i,
+ }, {
+ type: 'divider',
+ });
+
+ if (opts.includeCurrentAccount) {
+ menuItems.push(createItem(host, $i));
+ }
+
+ menuItems.push(...accountItems);
+
+ menuItems.push({
+ type: 'parent',
+ icon: 'ti ti-plus',
+ text: i18n.ts.addAccount,
+ children: [{
+ text: i18n.ts.existingAccount,
+ action: () => {
+ getAccountWithSigninDialog().then(res => {
+ if (res != null) {
+ success();
+ }
+ });
+ },
+ }, {
+ text: i18n.ts.createAccount,
+ action: () => {
+ getAccountWithSignupDialog().then(res => {
+ if (res != null) {
+ switchAccount(host, res.id);
+ }
+ });
+ },
+ }],
+ }, {
+ type: 'link',
+ icon: 'ti ti-users',
+ text: i18n.ts.manageAccounts,
+ to: '/settings/accounts',
+ });
+ } else {
+ if (opts.includeCurrentAccount) {
+ menuItems.push(createItem(host, $i));
+ }
+
+ menuItems.push(...accountItems);
+ }
+
+ popupMenu(menuItems, ev.currentTarget ?? ev.target, {
+ align: 'left',
+ });
+}
+
+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 }) => {
+ const user = await fetchAccount(res.i, res.id, true);
+ await addAccount(host, user, 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) => {
+ const user = JSON.parse(JSON.stringify(res));
+ delete user.token;
+ await addAccount(host, user, res.token);
+ resolve({ id: res.id, token: res.token });
+ },
+ cancelled: () => {
+ resolve(null);
+ },
+ closed: () => {
+ dispose();
+ },
+ });
+ });
+}
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/aiscript/api.ts
index e203c51bba..e7e396023d 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/aiscript/api.ts
@@ -8,8 +8,8 @@ import * as Misskey from 'misskey-js';
import { url, lang } from '@@/js/config.js';
import { assertStringAndIsIn } from './common.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { $i } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { $i } from '@/i.js';
import { miLocalStorage } from '@/local-storage.js';
import { customEmojis } from '@/custom-emojis.js';
@@ -76,7 +76,7 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string })
// バグがあればundefinedもあり得るため念のため
if (typeof token.value !== 'string') throw new Error('invalid token');
}
- const actualToken: string|null = token?.value ?? opts.token ?? null;
+ const actualToken: string | null = token?.value ?? opts.token ?? null;
if (param == null) {
throw new errors.AiScriptRuntimeError('expected param');
}
diff --git a/packages/frontend/src/scripts/aiscript/common.ts b/packages/frontend/src/aiscript/common.ts
index de6fa1d633..ba5dfb8368 100644
--- a/packages/frontend/src/scripts/aiscript/common.ts
+++ b/packages/frontend/src/aiscript/common.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { errors, utils, type values } from '@syuilo/aiscript';
+import { errors, utils } from '@syuilo/aiscript';
+import type { values } from '@syuilo/aiscript';
export function assertStringAndIsIn<A extends readonly string[]>(value: values.Value | undefined, expects: A): asserts value is values.VStr & { value: A[number] } {
utils.assertString(value);
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/aiscript/ui.ts
index ca92b27ff5..46e193f7c1 100644
--- a/packages/frontend/src/scripts/aiscript/ui.ts
+++ b/packages/frontend/src/aiscript/ui.ts
@@ -5,7 +5,8 @@
import { utils, values } from '@syuilo/aiscript';
import { v4 as uuid } from 'uuid';
-import { ref, Ref } from 'vue';
+import { ref } from 'vue';
+import type { Ref } from 'vue';
import * as Misskey from 'misskey-js';
import { assertStringAndIsIn } from './common.js';
diff --git a/packages/frontend/src/analytics.ts b/packages/frontend/src/analytics.ts
new file mode 100644
index 0000000000..e07a4e9258
--- /dev/null
+++ b/packages/frontend/src/analytics.ts
@@ -0,0 +1,107 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import type { AnalyticsInstance, AnalyticsPlugin } from 'analytics';
+
+/**
+ * analytics moduleを読み込まなくても動作するようにするためのラッパー
+ */
+class AnalyticsProxy implements AnalyticsInstance {
+ private analytics?: AnalyticsInstance;
+
+ constructor(analytics?: AnalyticsInstance) {
+ if (analytics) {
+ this.analytics = analytics;
+ }
+ }
+
+ public setAnalytics(analytics: AnalyticsInstance) {
+ if (this.analytics) {
+ throw new Error('Analytics instance already exists.');
+ }
+ this.analytics = analytics;
+ }
+
+ public identify(...args: Parameters<AnalyticsInstance['identify']>) {
+ return this.analytics?.identify(...args) ?? Promise.resolve();
+ }
+
+ public track(...args: Parameters<AnalyticsInstance['track']>) {
+ return this.analytics?.track(...args) ?? Promise.resolve();
+ }
+
+ public page(...args: Parameters<AnalyticsInstance['page']>) {
+ return this.analytics?.page(...args) ?? Promise.resolve();
+ }
+
+ public user(...args: Parameters<AnalyticsInstance['user']>) {
+ return this.analytics?.user(...args) ?? Promise.resolve();
+ }
+
+ public reset(...args: Parameters<AnalyticsInstance['reset']>) {
+ return this.analytics?.reset(...args) ?? Promise.resolve();
+ }
+
+ public ready(...args: Parameters<AnalyticsInstance['ready']>) {
+ return this.analytics?.ready(...args) ?? function () { void 0; };
+ }
+
+ public on(...args: Parameters<AnalyticsInstance['on']>) {
+ return this.analytics?.on(...args) ?? function () { void 0; };
+ }
+
+ public once(...args: Parameters<AnalyticsInstance['once']>) {
+ return this.analytics?.once(...args) ?? function () { void 0; };
+ }
+
+ public getState(...args: Parameters<AnalyticsInstance['getState']>) {
+ return this.analytics?.getState(...args) ?? Promise.resolve();
+ }
+
+ public get storage() {
+ return this.analytics?.storage ?? {
+ getItem: () => null,
+ setItem: () => void 0,
+ removeItem: () => void 0,
+ };
+ }
+
+ public get plugins() {
+ return this.analytics?.plugins ?? {
+ enable: (p, c) => Promise.resolve(c ? c() : void 0),
+ disable: (p, c) => Promise.resolve(c ? c() : void 0),
+ };
+ }
+}
+
+export const analytics = new AnalyticsProxy();
+
+export async function initAnalytics(instance: Misskey.entities.MetaDetailed) {
+ // アナリティクスプロバイダに関する設定がひとつもない場合は、アナリティクスモジュールを読み込まない
+ if (!instance.googleAnalyticsMeasurementId) {
+ return;
+ }
+
+ const { default: Analytics } = await import('analytics');
+ const plugins: AnalyticsPlugin[] = [];
+
+ // Google Analytics
+ if (instance.googleAnalyticsMeasurementId) {
+ const { default: googleAnalytics } = await import('@analytics/google-analytics');
+
+ plugins.push(googleAnalytics({
+ measurementIds: [instance.googleAnalyticsMeasurementId],
+ debug: _DEV_,
+ }));
+ }
+
+ analytics.setAnalytics(Analytics({
+ app: 'misskey',
+ version: _VERSION_,
+ debug: _DEV_,
+ plugins,
+ }));
+}
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 46ec4533ec..62cc74ea09 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -3,27 +3,31 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { computed, watch, version as vueVersion, App } from 'vue';
+import { computed, watch, version as vueVersion } from 'vue';
import { compareVersions } from 'compare-versions';
import { version, lang, langsVersion, updateLocale, locale } from '@@/js/config.js';
+import defaultLightTheme from '@@/themes/l-light.json5';
+import defaultDarkTheme from '@@/themes/d-green-lime.json5';
+import type { App } from 'vue';
import widgets from '@/widgets/index.js';
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 { applyTheme } from '@/theme.js';
+import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js';
import { updateI18n, i18n } from '@/i18n.js';
-import { $i, refreshAccount, login } from '@/account.js';
-import { defaultStore, ColdDeviceStorage } from '@/store.js';
+import { refreshCurrentAccount, login } from '@/accounts.js';
+import { store } from '@/store.js';
import { fetchInstance, instance } from '@/instance.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';
+import { deviceKind, updateDeviceKind } from '@/utility/device-kind.js';
+import { reloadChannel } from '@/utility/unison-reload.js';
+import { getUrlWithoutLoginId } from '@/utility/login-id.js';
+import { getAccountFromId } from '@/utility/get-account-from-id.js';
import { deckStore } from '@/ui/deck/deck-store.js';
+import { analytics, initAnalytics } from '@/analytics.js';
import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
-import { setupRouter } from '@/router/main.js';
-import { createMainRouter } from '@/router/definition.js';
+import { prefer } from '@/preferences.js';
+import { $i } from '@/i.js';
export async function common(createVue: () => App<Element>) {
console.info(`Sharkey v${version}`);
@@ -33,11 +37,6 @@ export async function common(createVue: () => App<Element>) {
console.info(`vue ${vueVersion}`);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (window as any).$i = $i;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (window as any).$store = defaultStore;
-
window.addEventListener('error', event => {
console.error(event);
/*
@@ -97,32 +96,32 @@ export async function common(createVue: () => App<Element>) {
//#endregion
// タッチデバイスでCSSの:hoverを機能させる
- document.addEventListener('touchend', () => {}, { passive: true });
+ window.document.addEventListener('touchend', () => {}, { passive: true });
// URLに#pswpを含む場合は取り除く
- if (location.hash === '#pswp') {
- history.replaceState(null, '', location.href.replace('#pswp', ''));
+ if (window.location.hash === '#pswp') {
+ window.history.replaceState(null, '', window.location.href.replace('#pswp', ''));
}
// 一斉リロード
reloadChannel.addEventListener('message', path => {
- if (path !== null) location.href = path;
- else location.reload();
+ if (path !== null) window.location.href = path;
+ else window.location.reload();
});
// If mobile, insert the viewport meta tag
if (['smartphone', 'tablet'].includes(deviceKind)) {
- const viewport = document.getElementsByName('viewport').item(0);
+ const viewport = window.document.getElementsByName('viewport').item(0);
viewport.setAttribute('content',
`${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`);
}
//#region Set lang attr
- const html = document.documentElement;
+ const html = window.document.documentElement;
html.setAttribute('lang', lang);
//#endregion
- await defaultStore.ready;
+ await store.ready;
await deckStore.ready;
const fetchInstanceMetaPromise = fetchInstance();
@@ -132,11 +131,11 @@ export async function common(createVue: () => App<Element>) {
});
//#region loginId
- const params = new URLSearchParams(location.search);
+ const params = new URLSearchParams(window.location.search);
const loginId = params.get('loginId');
if (loginId) {
- const target = getUrlWithoutLoginId(location.href);
+ const target = getUrlWithoutLoginId(window.location.href);
if (!$i || $i.id !== loginId) {
const account = await getAccountFromId(loginId);
@@ -145,71 +144,78 @@ export async function common(createVue: () => App<Element>) {
}
}
- history.replaceState({ misskey: 'loginId' }, '', target);
+ window.history.replaceState({ misskey: 'loginId' }, '', target);
}
//#endregion
// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
- watch(defaultStore.reactiveState.darkMode, (darkMode) => {
- applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'));
+ watch(store.r.darkMode, (darkMode) => {
+ applyTheme(darkMode
+ ? (prefer.s.darkTheme ?? defaultDarkTheme)
+ : (prefer.s.lightTheme ?? defaultLightTheme),
+ );
}, { immediate: miLocalStorage.getItem('theme') == null });
- document.documentElement.dataset.colorScheme = defaultStore.state.darkMode ? 'dark' : 'light';
+ window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
- const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
- const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
+ const darkTheme = prefer.model('darkTheme');
+ const lightTheme = prefer.model('lightTheme');
watch(darkTheme, (theme) => {
- if (defaultStore.state.darkMode) {
- applyTheme(theme);
+ if (store.s.darkMode) {
+ applyTheme(theme ?? defaultDarkTheme);
}
});
watch(lightTheme, (theme) => {
- if (!defaultStore.state.darkMode) {
- applyTheme(theme);
+ if (!store.s.darkMode) {
+ applyTheme(theme ?? defaultLightTheme);
}
});
//#region Sync dark mode
- if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
- defaultStore.set('darkMode', isDeviceDarkmode());
+ if (prefer.s.syncDeviceDarkMode) {
+ store.set('darkMode', isDeviceDarkmode());
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => {
- if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
- defaultStore.set('darkMode', mql.matches);
+ if (prefer.s.syncDeviceDarkMode) {
+ store.set('darkMode', mql.matches);
}
});
//#endregion
+ if (prefer.s.darkTheme && store.s.darkMode) {
+ if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
+ } else if (prefer.s.lightTheme && !store.s.darkMode) {
+ if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
+ }
+
fetchInstanceMetaPromise.then(() => {
- if (defaultStore.state.themeInitial) {
- 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);
- }
+ // TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
+ if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme));
+ if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme));
});
- watch(defaultStore.reactiveState.overridedDeviceKind, (kind) => {
+ watch(prefer.r.overridedDeviceKind, (kind) => {
updateDeviceKind(kind);
}, { immediate: true });
- watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
- document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
+ watch(prefer.r.useBlurEffectForModal, v => {
+ window.document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
}, { immediate: true });
- watch(defaultStore.reactiveState.useBlurEffect, v => {
+ watch(prefer.r.useBlurEffect, v => {
if (v) {
- document.documentElement.style.removeProperty('--MI-blur');
+ window.document.documentElement.style.removeProperty('--MI-blur');
} else {
- document.documentElement.style.setProperty('--MI-blur', 'none');
+ window.document.documentElement.style.setProperty('--MI-blur', 'none');
}
}, { immediate: true });
// Keep screen on
- const onVisibilityChange = () => document.addEventListener('visibilitychange', () => {
- if (document.visibilityState === 'visible') {
+ const onVisibilityChange = () => window.document.addEventListener('visibilitychange', () => {
+ if (window.document.visibilityState === 'visible') {
try {
navigator.wakeLock.request('screen');
} catch (err) {
@@ -217,13 +223,13 @@ export async function common(createVue: () => App<Element>) {
}
}
});
- if (defaultStore.state.keepScreenOn && 'wakeLock' in navigator) {
+ if (prefer.s.keepScreenOn && 'wakeLock' in navigator) {
navigator.wakeLock.request('screen')
.then(onVisibilityChange)
.catch(() => {
// On WebKit-based browsers, user activation is required to send wake lock request
// https://webkit.org/blog/13862/the-user-activation-api/
- document.addEventListener(
+ window.document.addEventListener(
'click',
() => navigator.wakeLock.request('screen').then(onVisibilityChange),
{ once: true },
@@ -231,13 +237,17 @@ export async function common(createVue: () => App<Element>) {
});
}
+ if (prefer.s.makeEveryTextElementsSelectable) {
+ window.document.documentElement.classList.add('forceSelectableAll');
+ }
+
//#region Fetch user
if ($i && $i.token) {
if (_DEV_) {
console.log('account cache found. refreshing...');
}
- refreshAccount();
+ refreshCurrentAccount();
}
//#endregion
@@ -245,9 +255,20 @@ export async function common(createVue: () => App<Element>) {
await fetchCustomEmojis();
} catch (err) { /* empty */ }
- const app = createVue();
+ // analytics
+ fetchInstanceMetaPromise.then(async () => {
+ await initAnalytics(instance);
- setupRouter(app, createMainRouter);
+ if ($i) {
+ analytics.identify($i.id);
+ }
+
+ analytics.page({
+ path: window.location.pathname,
+ });
+ });
+
+ const app = createVue();
if (_DEV_) {
app.config.performance = true;
@@ -262,16 +283,16 @@ export async function common(createVue: () => App<Element>) {
const rootEl = ((): HTMLElement => {
const MISSKEY_MOUNT_DIV_ID = 'sharkey_app';
- const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID);
+ const currentRoot = window.document.getElementById(MISSKEY_MOUNT_DIV_ID);
if (currentRoot) {
console.warn('multiple import detected');
return currentRoot;
}
- const root = document.createElement('div');
+ const root = window.document.createElement('div');
root.id = MISSKEY_MOUNT_DIV_ID;
- document.body.appendChild(root);
+ window.document.body.appendChild(root);
return root;
})();
@@ -284,34 +305,37 @@ 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/' }));
+ if (!_DEV_) {
+ 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,
+ lastVersion,
app,
};
}
function removeSplash() {
- const splash = document.getElementById('splash');
+ const splash = window.document.getElementById('splash');
if (splash) {
splash.style.opacity = '0';
splash.style.pointerEvents = 'none';
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index 6c544feb2a..38471cd86a 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -5,36 +5,43 @@
import { createApp, defineAsyncComponent, markRaw } from 'vue';
import { ui } from '@@/js/config.js';
+import * as Misskey from 'misskey-js';
+import { compareVersions } from 'compare-versions';
import { common } from './common.js';
-import type * as Misskey from 'misskey-js';
import type { Component } from 'vue';
+import type { Keymap } from '@/utility/hotkey.js';
import { i18n } from '@/i18n.js';
-import { alert, confirm, popup, post, toast } from '@/os.js';
+import { alert, confirm, popup, post } from '@/os.js';
import { useStream } from '@/stream.js';
-import * as sound from '@/scripts/sound.js';
-import { $i, signout, updateAccountPartial } from '@/account.js';
+import * as sound from '@/utility/sound.js';
+import { $i } from '@/i.js';
import { instance } from '@/instance.js';
-import { ColdDeviceStorage, defaultStore } from '@/store.js';
-import { reactionPicker } from '@/scripts/reaction-picker.js';
+import { store } from '@/store.js';
+import { reactionPicker } from '@/utility/reaction-picker.js';
import { miLocalStorage } from '@/local-storage.js';
-import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
-import { initializeSw } from '@/scripts/initialize-sw.js';
+import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
+import { initializeSw } from '@/utility/initialize-sw.js';
import { deckStore } from '@/ui/deck/deck-store.js';
-import { emojiPicker } from '@/scripts/emoji-picker.js';
-import { mainRouter } from '@/router/main.js';
-import { setFavIconDot } from '@/scripts/favicon-dot.js';
-import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
+import { emojiPicker } from '@/utility/emoji-picker.js';
+import { mainRouter } from '@/router.js';
+import { setFavIconDot } from '@/utility/favicon-dot.js';
+import { type Keymap, makeHotkey } from '@/utility/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
+import { prefer } from '@/preferences.js';
+import { launchPlugins } from '@/plugin.js';
+import { updateCurrentAccountPartial } from '@/accounts.js';
+import { signout } from '@/signout.js';
+import { migrateOldSettings } from '@/pref-migrate.js';
export async function mainBoot() {
- const { isClientUpdated } = await common(() => {
+ const { isClientUpdated, lastVersion } = await common(() => {
let uiStyle = ui;
const searchParams = new URLSearchParams(window.location.search);
if (!$i) uiStyle = 'visitor';
if (searchParams.has('zen')) uiStyle = 'zen';
- if (uiStyle === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') uiStyle = 'zen';
+ if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && window.location.pathname !== '/') uiStyle = 'zen';
if (searchParams.has('ui')) uiStyle = searchParams.get('ui');
@@ -67,13 +74,23 @@ export async function mainBoot() {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {
closed: () => dispose(),
});
+
+ // prefereces migration
+ // TODO: そのうち消す
+ if (lastVersion && (compareVersions('2025.3.2-alpha.0', lastVersion) === 1)) {
+ console.log('Preferences migration');
+
+ migrateOldSettings();
+ }
}
const stream = useStream();
let reloadDialogShowing = false;
stream.on('_disconnected_', async () => {
- if (defaultStore.state.serverDisconnectedBehavior === 'dialog') {
+ if (prefer.s.serverDisconnectedBehavior === 'reload') {
+ window.location.reload();
+ } else if (prefer.s.serverDisconnectedBehavior === 'dialog') {
if (reloadDialogShowing) return;
reloadDialogShowing = true;
const { canceled } = await confirm({
@@ -83,7 +100,7 @@ export async function mainBoot() {
});
reloadDialogShowing = false;
if (!canceled) {
- location.reload();
+ window.location.reload();
}
}
});
@@ -100,30 +117,24 @@ export async function mainBoot() {
removeCustomEmojis(emojiData.emojis);
});
- for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) {
- import('@/plugin.js').then(async ({ install }) => {
- // Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740
- await new Promise(r => setTimeout(r, 0));
- install(plugin);
- });
- }
+ launchPlugins();
try {
- if (defaultStore.state.enableSeasonalScreenEffect) {
+ if (prefer.s.enableSeasonalScreenEffect) {
const month = new Date().getMonth() + 1;
- if (defaultStore.state.hemisphere === 'S') {
+ if (prefer.s.hemisphere === 'S') {
// ▼南半球
if (month === 7 || month === 8) {
- const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
+ const SnowfallEffect = (await import('@/utility/snowfall-effect.js')).SnowfallEffect;
new SnowfallEffect({}).render();
}
} else {
// ▼北半球
if (month === 12 || month === 1) {
- const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
+ const SnowfallEffect = (await import('@/utility/snowfall-effect.js')).SnowfallEffect;
new SnowfallEffect({}).render();
} else if (month === 3 || month === 4) {
- const SakuraEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
+ const SakuraEffect = (await import('@/utility/snowfall-effect.js')).SnowfallEffect;
new SakuraEffect({
sakura: true,
}).render();
@@ -136,8 +147,8 @@ export async function mainBoot() {
}
if ($i) {
- defaultStore.loaded.then(() => {
- if (defaultStore.state.accountSetupWizard !== -1) {
+ store.loaded.then(async () => {
+ if (store.s.accountSetupWizard !== -1) {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {
closed: () => dispose(),
});
@@ -152,7 +163,7 @@ export async function mainBoot() {
});
}
- function onAnnouncementCreated (ev: { announcement: Misskey.entities.Announcement }) {
+ function onAnnouncementCreated(ev: { announcement: Misskey.entities.Announcement }) {
const announcement = ev.announcement;
if (announcement.display === 'dialog') {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkAnnouncementDialog.vue')), {
@@ -260,7 +271,7 @@ export async function mainBoot() {
let lastVisibilityChangedAt = Date.now();
function claimPlainLucky() {
- if (document.visibilityState !== 'visible') {
+ if (window.document.visibilityState !== 'visible') {
if (justPlainLuckyTimer != null) window.clearTimeout(justPlainLuckyTimer);
return;
}
@@ -275,7 +286,7 @@ export async function mainBoot() {
window.addEventListener('visibilitychange', () => {
const now = Date.now();
- if (document.visibilityState === 'visible') {
+ if (window.document.visibilityState === 'visible') {
// タブを高速で切り替えたら取得処理が何度も走るのを防ぐ
if ((now - lastVisibilityChangedAt) < 1000 * 10) {
justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10);
@@ -320,7 +331,7 @@ export async function mainBoot() {
const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
- if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
+ if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !window.location.pathname.startsWith('/miauth')) {
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {
closed: () => dispose(),
@@ -354,13 +365,13 @@ export async function mainBoot() {
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
- updateAccountPartial(i);
+ updateCurrentAccountPartial(i);
});
main.on('readAllNotifications', () => {
setFavIconDot(false);
- updateAccountPartial({
+ updateCurrentAccountPartial({
hasUnreadNotification: false,
unreadNotificationsCount: 0,
});
@@ -370,39 +381,24 @@ export async function mainBoot() {
attemptShowNotificationDot();
const unreadNotificationsCount = ($i?.unreadNotificationsCount ?? 0) + 1;
- updateAccountPartial({
+ updateCurrentAccountPartial({
hasUnreadNotification: true,
unreadNotificationsCount,
});
});
- main.on('unreadMention', () => {
- updateAccountPartial({ hasUnreadMentions: true });
- });
-
- main.on('readAllUnreadMentions', () => {
- updateAccountPartial({ hasUnreadMentions: false });
- });
-
- main.on('unreadSpecifiedNote', () => {
- updateAccountPartial({ hasUnreadSpecifiedNotes: true });
- });
-
- main.on('readAllUnreadSpecifiedNotes', () => {
- updateAccountPartial({ hasUnreadSpecifiedNotes: false });
- });
-
- main.on('readAllAntennas', () => {
- updateAccountPartial({ hasUnreadAntenna: false });
- });
-
main.on('unreadAntenna', () => {
- updateAccountPartial({ hasUnreadAntenna: true });
+ updateCurrentAccountPartial({ hasUnreadAntenna: true });
sound.playMisskeySfx('antenna');
});
+ main.on('newChatMessage', () => {
+ updateCurrentAccountPartial({ hasUnreadChatMessages: true });
+ sound.playMisskeySfx('chat');
+ });
+
main.on('readAllAnnouncements', () => {
- updateAccountPartial({ hasUnreadAnnouncement: false });
+ updateCurrentAccountPartial({ hasUnreadAnnouncement: false });
});
// 個人宛てお知らせが発行されたとき
@@ -422,13 +418,13 @@ export async function mainBoot() {
post();
},
'd': () => {
- defaultStore.set('darkMode', !defaultStore.state.darkMode);
+ store.set('darkMode', !store.s.darkMode);
},
's': () => {
mainRouter.push('/search');
},
} as const satisfies Keymap;
- document.addEventListener('keydown', makeHotkey(keymap), { passive: false });
+ window.document.addEventListener('keydown', makeHotkey(keymap), { passive: false });
initializeSw();
}
diff --git a/packages/frontend/src/boot/sub-boot.ts b/packages/frontend/src/boot/sub-boot.ts
index 35c84d5568..e24c324dfb 100644
--- a/packages/frontend/src/boot/sub-boot.ts
+++ b/packages/frontend/src/boot/sub-boot.ts
@@ -5,7 +5,7 @@
import { createApp, defineAsyncComponent } from 'vue';
import { common } from './common.js';
-import { emojiPicker } from '@/scripts/emoji-picker.js';
+import { emojiPicker } from '@/utility/emoji-picker.js';
export async function subBoot() {
const { isClientUpdated } = await common(() => createApp(
diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts
index bfe8fbe0e4..70078b410d 100644
--- a/packages/frontend/src/cache.ts
+++ b/packages/frontend/src/cache.ts
@@ -4,8 +4,8 @@
*/
import * as Misskey from 'misskey-js';
-import { Cache } from '@/scripts/cache.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { Cache } from '@/utility/cache.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
export const clipsCache = new Cache<Misskey.entities.Clip[]>(1000 * 60 * 30, () => misskeyApi('clips/list'));
export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list'));
diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue
index d59c5b2c57..43eb2e5f80 100644
--- a/packages/frontend/src/components/MkAbuseReport.vue
+++ b/packages/frontend/src/components/MkAbuseReport.vue
@@ -88,9 +88,9 @@ 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';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { createRouter } from '@/router.js';
const props = defineProps<{
report: Misskey.entities.AdminAbuseUserReportsResponse[number];
@@ -100,10 +100,9 @@ const emit = defineEmits<{
(ev: 'resolved', reportId: string): void;
}>();
-const routerFactory = useRouterFactory();
-const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`);
+const targetRouter = createRouter(`/admin/user/${props.report.targetUserId}`);
targetRouter.init();
-const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`);
+const reporterRouter = createRouter(`/admin/user/${props.report.reporterId}`);
reporterRouter.init();
const moderationNote = ref(props.report.moderationNote ?? '');
@@ -135,7 +134,7 @@ function forward() {
function showMenu(ev: MouseEvent) {
os.popupMenu([{
- icon: 'ti ti-id',
+ icon: 'ti ti-hash',
text: 'Copy ID',
action: () => {
copyToClipboard(props.report.id);
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
index 9df957f3ec..b62096bbe9 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
+++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue
index a634a748e9..dbac5e9dd7 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.vue
+++ b/packages/frontend/src/components/MkAbuseReportWindow.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { ref, shallowRef } from 'vue';
+import { ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkWindow from '@/components/MkWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -47,7 +47,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const uiWindow = shallowRef<InstanceType<typeof MkWindow>>();
+const uiWindow = useTemplateRef('uiWindow');
const comment = ref(props.initialComment ?? '');
function send() {
diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
index cad26de6e2..b907b5b25a 100644
--- a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
+++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue
index 0839955d9d..cb8032c019 100644
--- a/packages/frontend/src/components/MkAccountMoved.vue
+++ b/packages/frontend/src/components/MkAccountMoved.vue
@@ -17,7 +17,7 @@ import * as Misskey from 'misskey-js';
import MkMention from './MkMention.vue';
import { i18n } from '@/i18n.js';
import { host as localHost } from '@@/js/config.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
const user = ref<Misskey.entities.UserLite>();
diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts
index 7614da51da..d838997616 100644
--- a/packages/frontend/src/components/MkAchievements.stories.impl.ts
+++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts
@@ -4,12 +4,12 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAchievements from './MkAchievements.vue';
-import { ACHIEVEMENT_TYPES } from '@/scripts/achievements.js';
+import { ACHIEVEMENT_TYPES } from '@/utility/achievements.js';
export const Empty = {
render(args) {
return {
diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue
index 087ad51fe3..c4ea0a2142 100644
--- a/packages/frontend/src/components/MkAchievements.vue
+++ b/packages/frontend/src/components/MkAchievements.vue
@@ -55,9 +55,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'misskey-js';
import { onMounted, ref, computed } from 'vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements.js';
+import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/utility/achievements.js';
const props = withDefaults(defineProps<{
user: Misskey.entities.User;
diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
index 270ca40825..a01d91ad20 100644
--- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import MkAnalogClock from './MkAnalogClock.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue
index c8fa6246e0..eac1ea9534 100644
--- a/packages/frontend/src/components/MkAnalogClock.vue
+++ b/packages/frontend/src/components/MkAnalogClock.vue
@@ -82,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { globalEvents } from '@/events.js';
-import { defaultIdlingRenderScheduler } from '@/scripts/idle-render.js';
+import { defaultIdlingRenderScheduler } from '@/utility/idle-render.js';
// https://stackoverflow.com/questions/1878907/how-can-i-find-the-difference-between-two-angles
const angleDiff = (a: number, b: number) => {
@@ -192,7 +192,7 @@ function tick() {
tick();
function calcColors() {
- const computedStyle = getComputedStyle(document.documentElement);
+ const computedStyle = getComputedStyle(window.document.documentElement);
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)';
diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue
index 4bf6125af5..e57fbcdee3 100644
--- a/packages/frontend/src/components/MkAnimBg.vue
+++ b/packages/frontend/src/components/MkAnimBg.vue
@@ -4,14 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<canvas ref="canvasEl" style="width: 100%; height: 100%; pointer-events: none;"></canvas>
+<canvas ref="canvasEl" style="display: block; width: 100%; height: 100%; pointer-events: none;"></canvas>
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, shallowRef } from 'vue';
+import { onMounted, onUnmounted, useTemplateRef } from 'vue';
import isChromatic from 'chromatic/isChromatic';
-const canvasEl = shallowRef<HTMLCanvasElement>();
+const canvasEl = useTemplateRef('canvasEl');
const props = withDefaults(defineProps<{
scale?: number;
diff --git a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
index bf3ddb935b..627cb0c4ff 100644
--- a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAnnouncementDialog from './MkAnnouncementDialog.vue';
diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue
index 6c335d71d9..56fd422c56 100644
--- a/packages/frontend/src/components/MkAnnouncementDialog.vue
+++ b/packages/frontend/src/components/MkAnnouncementDialog.vue
@@ -22,22 +22,23 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { $i, updateAccountPartial } from '@/account.js';
+import { $i } from '@/i.js';
+import { updateCurrentAccountPartial } from '@/accounts.js';
const props = withDefaults(defineProps<{
announcement: Misskey.entities.Announcement;
}>(), {
});
-const rootEl = shallowRef<HTMLDivElement>();
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const rootEl = useTemplateRef('rootEl');
+const modal = useTemplateRef('modal');
async function ok() {
if (props.announcement.needConfirmationToRead) {
@@ -51,7 +52,7 @@ async function ok() {
modal.value?.close();
misskeyApi('i/read-announcement', { announcementId: props.announcement.id });
- updateAccountPartial({
+ updateCurrentAccountPartial({
unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id),
});
}
diff --git a/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts b/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts
index 1749e07a4e..4d921a4c48 100644
--- a/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts
+++ b/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAntennaEditor from './MkAntennaEditor.vue';
diff --git a/packages/frontend/src/components/MkAntennaEditor.vue b/packages/frontend/src/components/MkAntennaEditor.vue
index e622d57f1e..ac71618ee2 100644
--- a/packages/frontend/src/components/MkAntennaEditor.vue
+++ b/packages/frontend/src/components/MkAntennaEditor.vue
@@ -59,10 +59,10 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { deepMerge } from '@/scripts/merge.js';
-import type { DeepPartial } from '@/scripts/merge.js';
+import { deepMerge } from '@/utility/merge.js';
+import type { DeepPartial } from '@/utility/merge.js';
type PartialAllowedAntenna = Omit<Misskey.entities.Antenna, 'id' | 'createdAt' | 'updatedAt'> & {
id?: string;
diff --git a/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts b/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts
index 1c6ca83b47..5878b52fb9 100644
--- a/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAntennaEditorDialog from './MkAntennaEditorDialog.vue';
diff --git a/packages/frontend/src/components/MkAntennaEditorDialog.vue b/packages/frontend/src/components/MkAntennaEditorDialog.vue
index 6d815d29f3..0ebf5abf4c 100644
--- a/packages/frontend/src/components/MkAntennaEditorDialog.vue
+++ b/packages/frontend/src/components/MkAntennaEditorDialog.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import XAntennaEditor from '@/components/MkAntennaEditor.vue';
@@ -40,7 +40,7 @@ const emit = defineEmits<{
(ev: 'closed'): void,
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
function onAntennaCreated(newAntenna: Misskey.entities.Antenna) {
emit('created', newAntenna);
diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index 564d1fe7e3..86eddbca51 100644
--- a/packages/frontend/src/components/MkAsUi.vue
+++ b/packages/frontend/src/components/MkAsUi.vue
@@ -63,14 +63,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { Ref, ref, computed } from 'vue';
+import { ref, computed } from 'vue';
+import type { Ref } from 'vue';
import * as os from '@/os.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSelect from '@/components/MkSelect.vue';
-import { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/scripts/aiscript/ui.js';
+import type { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/aiscript/ui.js';
import MkFolder from '@/components/MkFolder.vue';
import MkPostForm from '@/components/MkPostForm.vue';
diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue
index f78d2d38f0..00bf8e68d9 100644
--- a/packages/frontend/src/components/MkAuthConfirm.vue
+++ b/packages/frontend/src/components/MkAuthConfirm.vue
@@ -117,14 +117,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<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 { $i } from '@/i.js';
+import { getAccounts, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/accounts.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';
+import { getProxiedImageUrl } from '@/utility/media-proxy.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
const props = defineProps<{
name?: string;
@@ -158,7 +157,7 @@ async function init() {
const accounts = await getAccounts();
- const accountIdsToFetch = accounts.map(a => a.id).filter(id => !users.value.has(id));
+ const accountIdsToFetch = accounts.map(a => a.user.id).filter(id => !users.value.has(id));
if (accountIdsToFetch.length > 0) {
const usersRes = await misskeyApi('users/show', {
@@ -170,7 +169,7 @@ async function init() {
users.value.set(user.id, {
...user,
- token: accounts.find(a => a.id === user.id)!.token,
+ token: accounts.find(a => a.user.id === user.id)!.token,
});
}
}
diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
index ec24b8c240..64ccb708aa 100644
--- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
+++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
@@ -6,13 +6,13 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAutocomplete from './MkAutocomplete.vue';
import MkInput from './MkInput.vue';
-import { tick } from '@/scripts/test-utils.js';
+import { tick } from '@/utility/test-utils.js';
const common = {
render(args) {
return {
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index a0cd066c06..c73cf840ac 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -44,26 +44,28 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
-import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
+import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import sanitizeHtml from 'sanitize-html';
import { emojilist, getEmojiName } from '@@/js/emojilist.js';
-import contains from '@/scripts/contains.js';
import { char2twemojiFilePath, char2fluentEmojiFilePath, char2tossfaceFilePath } from '@@/js/emoji-base.js';
+import { MFM_TAGS, MFM_PARAMS } from '@@/js/const.js';
+import type { EmojiDef } from '@/utility/search-emoji.js';
+import contains from '@/utility/contains.js';
import { acct } from '@/filters/user.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
import { customEmojis } from '@/custom-emojis.js';
-import { MFM_TAGS, MFM_PARAMS } from '@@/js/const.js';
-import { searchEmoji, EmojiDef } from '@/scripts/search-emoji.js';
+import { searchEmoji } from '@/utility/search-emoji.js';
+import { prefer } from '@/preferences.js';
const lib = emojilist.filter(x => x.category !== 'flags');
const emojiDb = computed(() => {
//#region Unicode Emoji
- const char2path = defaultStore.reactiveState.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : defaultStore.reactiveState.emojiStyle.value === 'tossface' ? char2tossfaceFilePath : char2fluentEmojiFilePath;
+ const char2path = prefer.r.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : prefer.r.emojiStyle.value === 'tossface' ? char2tossfaceFilePath : char2fluentEmojiFilePath;
const unicodeEmojiDB: EmojiDef[] = lib.map(x => ({
emoji: x.char,
@@ -71,7 +73,7 @@ const emojiDb = computed(() => {
url: char2path(x.char),
}));
- for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
+ for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
for (const [emoji, keywords] of Object.entries(index)) {
for (const k of keywords) {
unicodeEmojiDB.push({
@@ -137,7 +139,7 @@ const emit = defineEmits<{
}>();
const suggests = ref<Element>();
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
const fetching = ref(true);
const users = ref<any[]>([]);
@@ -153,10 +155,10 @@ function complete(type: string, value: any) {
emit('done', { type, value });
emit('closed');
if (type === 'emoji') {
- let recents = defaultStore.state.recentlyUsedEmojis;
+ let recents = store.s.recentlyUsedEmojis;
recents = recents.filter((emoji: any) => emoji !== value);
recents.unshift(value);
- defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
+ store.set('recentlyUsedEmojis', recents.splice(0, 32));
}
}
@@ -197,8 +199,10 @@ function exec() {
users.value = JSON.parse(cache);
fetching.value = false;
} else {
+ const [username, host] = props.q.toString().split('@');
misskeyApi('users/search-by-username-and-host', {
- username: props.q,
+ username: username,
+ host: host,
limit: 10,
detail: false,
}).then(searchedUsers => {
@@ -234,7 +238,7 @@ function exec() {
} else if (props.type === 'emoji') {
if (!props.q || props.q === '') {
// 最近使った絵文字をサジェスト
- emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[];
+ emojis.value = store.s.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[];
return;
}
@@ -355,7 +359,7 @@ onMounted(() => {
props.textarea.addEventListener('keydown', onKeydown);
- document.body.addEventListener('mousedown', onMousedown);
+ window.document.body.addEventListener('mousedown', onMousedown);
nextTick(() => {
exec();
@@ -371,7 +375,7 @@ onMounted(() => {
onBeforeUnmount(() => {
props.textarea.removeEventListener('keydown', onKeydown);
- document.body.removeEventListener('mousedown', onMousedown);
+ window.document.body.removeEventListener('mousedown', onMousedown);
});
</script>
@@ -407,7 +411,7 @@ onBeforeUnmount(() => {
text-overflow: ellipsis;
&:hover {
- background: var(--MI_THEME-X3);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
&[data-selected='true'] {
diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts
index d2a4a9f03b..6e20294438 100644
--- a/packages/frontend/src/components/MkAvatars.stories.impl.ts
+++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue
index 8236d0ddb9..1c44ed60d8 100644
--- a/packages/frontend/src/components/MkAvatars.vue
+++ b/packages/frontend/src/components/MkAvatars.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
const props = withDefaults(defineProps<{
userIds: string[];
diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts
index e8802e4f8f..0a569b3beb 100644
--- a/packages/frontend/src/components/MkButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkButton.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkButton from './MkButton.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index a6e5651d63..d37f7f39f8 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<button
v-if="!link"
ref="el" class="_button"
- :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]"
+ :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike, [$style.iconOnly]: iconOnly, [$style.wait]: wait }]"
:type="type"
:name="name"
:value="value"
- :disabled="disabled"
+ :disabled="disabled || wait"
@click="emit('click', $event)"
@mousedown="onMousedown"
>
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</button>
<MkA
v-else class="_button"
- :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]"
+ :class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike, [$style.iconOnly]: iconOnly, [$style.wait]: wait }]"
:to="to ?? '#'"
:behavior="linkBehavior"
@mousedown="onMousedown"
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { nextTick, onMounted, shallowRef } from 'vue';
+import { nextTick, onMounted, useTemplateRef } from 'vue';
const props = defineProps<{
type?: 'button' | 'submit' | 'reset';
@@ -57,14 +57,15 @@ const props = defineProps<{
name?: string;
value?: string;
disabled?: boolean;
+ iconOnly?: boolean;
}>();
const emit = defineEmits<{
(ev: 'click', payload: MouseEvent): void;
}>();
-const el = shallowRef<HTMLElement | null>(null);
-const ripples = shallowRef<HTMLElement | null>(null);
+const el = useTemplateRef('el');
+const ripples = useTemplateRef('ripples');
onMounted(() => {
if (props.autofocus) {
@@ -91,7 +92,7 @@ function onMousedown(evt: MouseEvent): void {
const target = evt.target! as HTMLElement;
const rect = target.getBoundingClientRect();
- const ripple = document.createElement('div');
+ const ripple = window.document.createElement('div');
ripple.classList.add(ripples.value!.dataset.childrenClass!);
ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
@@ -147,6 +148,11 @@ function onMousedown(evt: MouseEvent): void {
background: var(--MI_THEME-buttonHoverBg);
}
+ &.iconOnly {
+ padding: 7px;
+ min-width: auto;
+ }
+
&.small {
font-size: 90%;
padding: 6px 12px;
@@ -220,28 +226,28 @@ function onMousedown(evt: MouseEvent): void {
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
&:not(:disabled):hover {
- 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)));
+ background: linear-gradient(90deg, hsl(from var(--MI_THEME-buttonGradateA) h s calc(l + 5)), hsl(from var(--MI_THEME-buttonGradateB) h s calc(l + 5)));
}
&:not(:disabled):active {
- 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)));
+ background: linear-gradient(90deg, hsl(from var(--MI_THEME-buttonGradateA) h s calc(l + 5)), hsl(from var(--MI_THEME-buttonGradateB) h s calc(l + 5)));
}
}
&.danger {
font-weight: bold;
- color: #ff2a2a;
+ color: var(--MI_THEME-error);
&.primary {
color: #fff;
- background: #ff2a2a;
+ background: var(--MI_THEME-error);
&:not(:disabled):hover {
- background: #ff4242;
+ background: hsl(from var(--MI_THEME-error) h s calc(l + 10));
}
&:not(:disabled):active {
- background: #d42e2e;
+ background: hsl(from var(--MI_THEME-error) h s calc(l - 10));
}
}
}
@@ -250,6 +256,10 @@ function onMousedown(evt: MouseEvent): void {
opacity: 0.5;
}
+ &.wait {
+ cursor: wait !important;
+ }
+
&:focus-visible {
outline-offset: 2px;
}
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index aeed90722f..b7aceb3570 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -26,8 +26,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
-import { defaultStore } from '@/store.js';
+import { ref, useTemplateRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
+import { store } from '@/store.js';
// APIs provided by Captcha services
// see: https://docs.hcaptcha.com/configuration/#javascript-api
@@ -53,6 +53,8 @@ type CaptchaContainer = {
};
declare global {
+ // Window を拡張してるため、空ではない
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface Window extends CaptchaContainer { }
}
@@ -70,7 +72,7 @@ const emit = defineEmits<{
const available = ref(false);
-const captchaEl = shallowRef<HTMLDivElement | undefined>();
+const captchaEl = useTemplateRef('captchaEl');
const captchaWidgetId = ref<string | undefined>(undefined);
const testcaptchaInput = ref('');
const testcaptchaPassed = ref(false);
@@ -115,7 +117,7 @@ watch(() => [props.instanceUrl, props.sitekey, props.secretKey], async () => {
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'), {
+ (window.document.getElementById(scriptId.value) ?? window.document.head.appendChild(Object.assign(window.document.createElement('script'), {
async: true,
id: scriptId.value,
src: src.value,
@@ -152,12 +154,12 @@ async function requestRender() {
if (captcha.value.render && captchaEl.value instanceof Element && props.sitekey) {
// reCAPTCHAのレンダリング重複判定を回避するため、captchaEl配下に仮のdivを用意する.
// (同じdivに対して複数回renderを呼び出すとreCAPTCHAはエラーを返すので)
- const elem = document.createElement('div');
+ const elem = window.document.createElement('div');
captchaEl.value.appendChild(elem);
captchaWidgetId.value = captcha.value.render(elem, {
sitekey: props.sitekey,
- theme: defaultStore.state.darkMode ? 'dark' : 'light',
+ theme: store.s.darkMode ? 'dark' : 'light',
callback: callback,
'expired-callback': () => callback(undefined),
'error-callback': () => callback(undefined),
@@ -185,7 +187,7 @@ async function requestRender() {
function clearWidget() {
if (props.provider === 'mcaptcha') {
- const container = document.getElementById('mcaptcha__widget-container');
+ const container = window.document.getElementById('mcaptcha__widget-container');
if (container) {
container.innerHTML = '';
}
diff --git a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts
index b9770670dc..a42e80c27a 100644
--- a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue
index 7aa916134f..7ee1e98b48 100644
--- a/packages/frontend/src/components/MkChannelFollowButton.vue
+++ b/packages/frontend/src/components/MkChannelFollowButton.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/components/MkChannelList.stories.impl.ts b/packages/frontend/src/components/MkChannelList.stories.impl.ts
index f69b20c049..47ca864dc0 100644
--- a/packages/frontend/src/components/MkChannelList.stories.impl.ts
+++ b/packages/frontend/src/components/MkChannelList.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { channel } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue
index 2850ecca16..fdb7d2a1c4 100644
--- a/packages/frontend/src/components/MkChannelList.vue
+++ b/packages/frontend/src/components/MkChannelList.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination :pagination="pagination">
<template #empty>
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.notFound }}</div>
</div>
</template>
@@ -19,8 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
+import type { Paging } from '@/components/MkPagination.vue';
import MkChannelPreview from '@/components/MkChannelPreview.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
diff --git a/packages/frontend/src/components/MkChannelPreview.stories.impl.ts b/packages/frontend/src/components/MkChannelPreview.stories.impl.ts
index de0193c78f..dbee069771 100644
--- a/packages/frontend/src/components/MkChannelPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkChannelPreview.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { channel } from '../../.storybook/fakes.js';
import MkChannelPreview from './MkChannelPreview.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkChart.stories.impl.ts b/packages/frontend/src/components/MkChart.stories.impl.ts
index 1bcb9c30d8..3caf01d34e 100644
--- a/packages/frontend/src/components/MkChart.stories.impl.ts
+++ b/packages/frontend/src/components/MkChart.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import { getChartResolver } from '../../.storybook/charts.js';
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index d05f4921f6..7e164362c1 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -45,23 +45,19 @@ export type ChartSrc =
</script>
<script lang="ts" setup>
-/* eslint-disable id-denylist --
- Chart.js has a `data` attribute in most chart definitions, which triggers the
- id-denylist violation when setting it. This is causing about 60+ lint issues.
- As this is part of Chart.js's API it makes sense to disable the check here.
-*/
-import { onMounted, ref, shallowRef, watch } from 'vue';
+
+import { onMounted, ref, useTemplateRef, watch } from 'vue';
import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { alpha } from '@/scripts/color.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { alpha } from '@/utility/color.js';
import date from '@/filters/date.js';
import bytes from '@/filters/bytes.js';
-import { initChart } from '@/scripts/init-chart.js';
-import { chartLegend } from '@/scripts/chart-legend.js';
+import { initChart } from '@/utility/init-chart.js';
+import { chartLegend } from '@/utility/chart-legend.js';
import MkChartLegend from '@/components/MkChartLegend.vue';
initChart();
@@ -96,7 +92,7 @@ const props = withDefaults(defineProps<{
nowForChromatic: undefined,
});
-const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
+const legendEl = useTemplateRef('legendEl');
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
const negate = arr => arr.map(x => -x);
@@ -134,7 +130,7 @@ let chartData: {
bytes?: boolean;
} | null = null;
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const chartEl = useTemplateRef('chartEl');
const fetching = ref(true);
const getDate = (ago: number) => {
@@ -161,7 +157,7 @@ const render = () => {
chartInstance.destroy();
}
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const maxes = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y)));
@@ -849,7 +845,7 @@ watch(() => [props.src, props.span], fetchAndRender);
onMounted(() => {
fetchAndRender();
});
-/* eslint-enable id-denylist */
+
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue
index e28d6ad6ba..ed0c3412d2 100644
--- a/packages/frontend/src/components/MkChartLegend.vue
+++ b/packages/frontend/src/components/MkChartLegend.vue
@@ -14,7 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { shallowRef } from 'vue';
-import { Chart, LegendItem } from 'chart.js';
+import { Chart } from 'chart.js';
+import type { LegendItem } from 'chart.js';
const chart = shallowRef<Chart>();
const type = shallowRef<string>();
diff --git a/packages/frontend/src/components/MkClickerGame.stories.impl.ts b/packages/frontend/src/components/MkClickerGame.stories.impl.ts
index 36313f965d..eb7e61f294 100644
--- a/packages/frontend/src/components/MkClickerGame.stories.impl.ts
+++ b/packages/frontend/src/components/MkClickerGame.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue
index 9a0a9fba05..775964af50 100644
--- a/packages/frontend/src/components/MkClickerGame.vue
+++ b/packages/frontend/src/components/MkClickerGame.vue
@@ -23,9 +23,9 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
import * as os from '@/os.js';
import { useInterval } from '@@/js/use-interval.js';
-import * as game from '@/scripts/clicker-game.js';
+import * as game from '@/utility/clicker-game.js';
import number from '@/filters/number.js';
-import { claimAchievement } from '@/scripts/achievements.js';
+import { claimAchievement } from '@/utility/achievements.js';
const saveData = game.saveData;
const cookies = computed(() => saveData.value?.cookies);
diff --git a/packages/frontend/src/components/MkClipPreview.stories.impl.ts b/packages/frontend/src/components/MkClipPreview.stories.impl.ts
index 62503fb98a..496dc09eed 100644
--- a/packages/frontend/src/components/MkClipPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkClipPreview.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { clip } from '../../.storybook/fakes.js';
import MkClipPreview from './MkClipPreview.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkClipPreview.vue b/packages/frontend/src/components/MkClipPreview.vue
index 5b09ec90dd..2154c08ab3 100644
--- a/packages/frontend/src/components/MkClipPreview.vue
+++ b/packages/frontend/src/components/MkClipPreview.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'misskey-js';
import { computed } from 'vue';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import number from '@/filters/number.js';
const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue
index e253b1b55f..40f41f5d0f 100644
--- a/packages/frontend/src/components/MkCode.core.vue
+++ b/packages/frontend/src/components/MkCode.core.vue
@@ -12,8 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, ref, watch } from 'vue';
import { bundledLanguagesInfo } from 'shiki/langs';
import type { BundledLanguage } from 'shiki/langs';
-import { getHighlighter, getTheme } from '@/scripts/code-highlighter.js';
-import { defaultStore } from '@/store.js';
+import { getHighlighter, getTheme } from '@/utility/code-highlighter.js';
+import { store } from '@/store.js';
const props = defineProps<{
code: string;
@@ -22,7 +22,7 @@ const props = defineProps<{
}>();
const highlighter = await getHighlighter();
-const darkMode = defaultStore.reactiveState.darkMode;
+const darkMode = store.r.darkMode;
const codeLang = ref<BundledLanguage | 'aiscript'>('js');
const [lightThemeName, darkThemeName] = await Promise.all([
@@ -93,7 +93,7 @@ watch(() => props.lang, (to) => {
.codeBlockRoot :global(.shiki) {
padding: 1em;
- margin: .5em 0;
+ margin: 0;
overflow: auto;
border-radius: var(--MI-radius-sm);
border: 1px solid var(--MI_THEME-divider);
diff --git a/packages/frontend/src/components/MkCode.stories.impl.ts b/packages/frontend/src/components/MkCode.stories.impl.ts
index b7e53e8e35..fae9d459fb 100644
--- a/packages/frontend/src/components/MkCode.stories.impl.ts
+++ b/packages/frontend/src/components/MkCode.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkCode from './MkCode.vue';
const code = `for (let i, 100) {
<: if (i % 15 == 0) "FizzBuzz"
diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue
index a46b220101..a99ab3bd09 100644
--- a/packages/frontend/src/components/MkCode.vue
+++ b/packages/frontend/src/components/MkCode.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</button>
<Suspense>
<template #fallback>
- <MkLoading />
+ <MkLoading/>
</template>
<XCode v-if="show && lang" :code="code" :lang="lang"/>
<pre v-else-if="show" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
@@ -28,9 +28,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { defineAsyncComponent, ref } from 'vue';
import * as os from '@/os.js';
import MkLoading from '@/components/global/MkLoading.vue';
-import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
code: string;
@@ -42,13 +42,12 @@ const props = withDefaults(defineProps<{
forceShow: false,
});
-const show = ref(props.forceShow === true ? true : !defaultStore.state.dataSaver.code);
+const show = ref(props.forceShow === true ? true : !prefer.s.dataSaver.code);
const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'));
function copy() {
copyToClipboard(props.code);
- os.success();
}
</script>
diff --git a/packages/frontend/src/components/MkCodeEditor.stories.impl.ts b/packages/frontend/src/components/MkCodeEditor.stories.impl.ts
index 5c410c4886..c76b6fd08e 100644
--- a/packages/frontend/src/components/MkCodeEditor.stories.impl.ts
+++ b/packages/frontend/src/components/MkCodeEditor.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { action } from '@storybook/addon-actions';
import MkCodeEditor from './MkCodeEditor.vue';
const code = `for (let i, 100) {
diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue
index 49b083815a..c3242a5914 100644
--- a/packages/frontend/src/components/MkCodeEditor.vue
+++ b/packages/frontend/src/components/MkCodeEditor.vue
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, watch, toRefs, shallowRef, nextTick } from 'vue';
+import { ref, watch, toRefs, useTemplateRef, nextTick } from 'vue';
import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
@@ -61,7 +61,7 @@ const { modelValue } = toRefs(props);
const v = ref<string>(modelValue.value ?? '');
const focused = ref(false);
const changed = ref(false);
-const inputEl = shallowRef<HTMLTextAreaElement>();
+const inputEl = useTemplateRef('inputEl');
const focus = () => inputEl.value?.focus();
diff --git a/packages/frontend/src/components/MkCodeInline.stories.impl.ts b/packages/frontend/src/components/MkCodeInline.stories.impl.ts
index 51d4d106ff..c17be177cb 100644
--- a/packages/frontend/src/components/MkCodeInline.stories.impl.ts
+++ b/packages/frontend/src/components/MkCodeInline.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkCodeInline from './MkCodeInline.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/MkColorInput.stories.impl.ts b/packages/frontend/src/components/MkColorInput.stories.impl.ts
index 61383e2cae..3df92ca858 100644
--- a/packages/frontend/src/components/MkColorInput.stories.impl.ts
+++ b/packages/frontend/src/components/MkColorInput.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { action } from '@storybook/addon-actions';
import MkColorInput from './MkColorInput.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue
index babf356942..9cf75d6528 100644
--- a/packages/frontend/src/components/MkColorInput.vue
+++ b/packages/frontend/src/components/MkColorInput.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, toRefs } from 'vue';
+import { ref, useTemplateRef, toRefs } from 'vue';
const props = defineProps<{
modelValue: string | null;
@@ -39,7 +39,7 @@ const emit = defineEmits<{
const { modelValue } = toRefs(props);
const v = ref(modelValue.value);
-const inputEl = shallowRef<HTMLElement>();
+const inputEl = useTemplateRef('inputEl');
const onInput = () => {
emit('update:modelValue', v.value ?? '');
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index 30a9b26bef..5b79ac6699 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</header>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_toggle_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_toggle_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_toggle_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_toggle_leaveTo : ''"
@enter="enter"
@afterEnter="afterEnter"
@leave="leave"
@@ -39,8 +39,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
-import { defaultStore } from '@/store.js';
+import { onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
+import { prefer } from '@/preferences.js';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
@@ -58,9 +58,9 @@ const props = withDefaults(defineProps<{
maxHeight: null,
});
-const rootEl = shallowRef<HTMLElement>();
-const contentEl = shallowRef<HTMLElement>();
-const headerEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
+const contentEl = useTemplateRef('contentEl');
+const headerEl = useTemplateRef('headerEl');
const showBody = ref(props.expanded);
const ignoreOmit = ref(false);
const omitted = ref(false);
diff --git a/packages/frontend/src/components/MkContextMenu.stories.impl.ts b/packages/frontend/src/components/MkContextMenu.stories.impl.ts
index 1ff0f51bd4..7a5e36131b 100644
--- a/packages/frontend/src/components/MkContextMenu.stories.impl.ts
+++ b/packages/frontend/src/components/MkContextMenu.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userEvent, within } from '@storybook/test';
import MkContextMenu from './MkContextMenu.vue';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index f51fefa0c0..9c6397a72c 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<Transition
appear
- :enterActiveClass="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_fade_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_fade_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_fade_leaveTo : ''"
>
<div ref="rootEl" :class="$style.root" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}">
<MkMenu :items="items" :align="'left'" @close="emit('closed')"/>
@@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
+import { onMounted, onBeforeUnmount, useTemplateRef, ref } from 'vue';
import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js';
-import contains from '@/scripts/contains.js';
-import { defaultStore } from '@/store.js';
+import contains from '@/utility/contains.js';
+import { prefer } from '@/preferences.js';
import * as os from '@/os.js';
const props = defineProps<{
@@ -34,7 +34,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
const zIndex = ref<number>(os.claimZIndex('high'));
@@ -68,11 +68,11 @@ onMounted(() => {
rootEl.value.style.left = `${left}px`;
}
- document.body.addEventListener('mousedown', onMousedown);
+ window.document.body.addEventListener('mousedown', onMousedown);
});
onBeforeUnmount(() => {
- document.body.removeEventListener('mousedown', onMousedown);
+ window.document.body.removeEventListener('mousedown', onMousedown);
});
function onMousedown(evt: Event) {
diff --git a/packages/frontend/src/components/MkCropperDialog.stories.impl.ts b/packages/frontend/src/components/MkCropperDialog.stories.impl.ts
index ce13093975..78cb4120de 100644
--- a/packages/frontend/src/components/MkCropperDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkCropperDialog.stories.impl.ts
@@ -3,14 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-/* eslint-disable @typescript-eslint/explicit-function-return-type */
-/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { file } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkCropperDialog from './MkCropperDialog.vue';
+import type { StoryObj } from '@storybook/vue3';
export const Default = {
render(args) {
return {
@@ -55,7 +53,7 @@ export const Default = {
http.get('/proxy/image.webp', async ({ request }) => {
const url = new URL(request.url).searchParams.get('url');
if (url === 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true') {
- const image = await (await fetch('client-assets/fedi.jpg')).blob();
+ const image = await (await window.fetch('client-assets/fedi.jpg')).blob();
return new HttpResponse(image, {
headers: {
'Content-Type': 'image/jpeg',
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 0186cfc2c0..ba21394cbc 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -31,17 +31,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import Cropper from 'cropperjs';
import tinycolor from 'tinycolor2';
+import { apiUrl } from '@@/js/config.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import * as os from '@/os.js';
-import { $i } from '@/account.js';
-import { defaultStore } from '@/store.js';
-import { apiUrl } from '@@/js/config.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
-import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
+import { getProxiedImageUrl } from '@/utility/media-proxy.js';
+import { prefer } from '@/preferences.js';
const emit = defineEmits<{
(ev: 'ok', cropped: Misskey.entities.DriveFile): void;
@@ -56,8 +56,8 @@ const props = defineProps<{
}>();
const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
-const imgEl = shallowRef<HTMLImageElement>();
+const dialogEl = useTemplateRef('dialogEl');
+const imgEl = useTemplateRef('imgEl');
let cropper: Cropper | null = null;
const loading = ref(true);
@@ -81,8 +81,8 @@ const ok = async () => {
formData.append('i', $i!.token);
if (props.uploadFolder) {
formData.append('folderId', props.uploadFolder);
- } else if (props.uploadFolder !== null && defaultStore.state.uploadFolder) {
- formData.append('folderId', defaultStore.state.uploadFolder);
+ } else if (props.uploadFolder !== null && prefer.s.uploadFolder) {
+ formData.append('folderId', prefer.s.uploadFolder);
}
window.fetch(apiUrl + '/drive/files/create', {
@@ -122,7 +122,7 @@ onMounted(() => {
cropper = new Cropper(imgEl.value!, {
});
- const computedStyle = getComputedStyle(document.documentElement);
+ const computedStyle = getComputedStyle(window.document.documentElement);
const selection = cropper.getCropperSelection()!;
selection.themeColor = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts
index 8a05e06311..3da27dcedb 100644
--- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { emojiDetailed } from '../../.storybook/fakes.js';
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
index e6ab17417d..54fda6bf7c 100644
--- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
@@ -57,14 +57,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
import MkLink from '@/components/MkLink.vue';
import { i18n } from '@/i18n.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
const props = defineProps<{
- emoji: Misskey.entities.EmojiDetailed,
+ emoji: Misskey.entities.EmojiDetailed,
}>();
const emit = defineEmits<{
@@ -73,7 +73,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
function cancel() {
emit('cancel');
@@ -85,7 +85,7 @@ function cancel() {
.emojiImgWrapper {
max-width: 100%;
height: 40cqh;
- background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-X5) 8px, var(--MI_THEME-X5) 14px);
+ background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)) 8px, light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)) 14px);
border-radius: var(--MI-radius);
margin: auto;
overflow-y: hidden;
@@ -101,7 +101,7 @@ function cancel() {
display: inline-block;
word-break: break-all;
padding: 3px 10px;
- background-color: var(--MI_THEME-X5);
+ background-color: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
border: solid 1px var(--MI_THEME-divider);
border-radius: var(--MI-radius);
}
diff --git a/packages/frontend/src/components/MkCwButton.stories.impl.ts b/packages/frontend/src/components/MkCwButton.stories.impl.ts
index 5d6ea56da9..bbe5f4eddb 100644
--- a/packages/frontend/src/components/MkCwButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkCwButton.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
import { file } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue
index b5f6e78b6c..cc8bbf1104 100644
--- a/packages/frontend/src/components/MkCwButton.vue
+++ b/packages/frontend/src/components/MkCwButton.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed } from 'vue';
import * as Misskey from 'misskey-js';
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
-import { concat } from '@/scripts/array.js';
+import { concat } from '@/utility/array.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index eeeabb476e..f94f28de41 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -4,14 +4,15 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<script lang="ts">
-import { defineComponent, h, PropType, TransitionGroup, useCssModule } from 'vue';
+import { defineComponent, h, TransitionGroup, useCssModule } from 'vue';
+import type { PropType } from 'vue';
+import type { MisskeyEntity } from '@/types/date-separated-list.js';
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';
+import { prefer } from '@/preferences.js';
import { $i } from '@/account.js';
export default defineComponent({
@@ -152,7 +153,7 @@ export default defineComponent({
[$style['direction-up']]: props.direction === 'up',
};
- return () => defaultStore.state.animation ? h(TransitionGroup, {
+ return () => prefer.s.animation ? h(TransitionGroup, {
class: classes,
name: 'list',
tag: 'div',
@@ -170,21 +171,17 @@ export default defineComponent({
container-type: inline-size;
&:global {
- > .list-move {
- transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
- }
-
- &.deny-move-transition > .list-move {
- transition: none !important;
- }
+ > .list-move {
+ transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
+ }
- > .list-enter-active {
- transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
- }
+ > .list-enter-active {
+ transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
+ }
- > *:empty {
- display: none;
- }
+ > *:empty {
+ display: none;
+ }
}
&:not(.date-separated-list-nogap) > *:not(:last-child) {
diff --git a/packages/frontend/src/components/MkDialog.stories.impl.ts b/packages/frontend/src/components/MkDialog.stories.impl.ts
index 2d8d3661f2..57c7916049 100644
--- a/packages/frontend/src/components/MkDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkDialog.stories.impl.ts
@@ -5,7 +5,7 @@
import { action } from '@storybook/addon-actions';
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { i18n } from '@/i18n.js';
import MkDialog from './MkDialog.vue';
const Base = {
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 9a59a9aac7..34a54a57bc 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i>
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
</div>
- <header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
- <div v-if="text" :class="$style.text"><Mfm :text="text" :isBlock="true" :plain="plain" /></div>
+ <header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header>
+ <div v-if="text" :class="$style.text" class="_selectable"><Mfm :text="text" :isBlock="true" :plain="plain"/></div>
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
<template #caption>
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, computed } from 'vue';
+import { ref, useTemplateRef, computed } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@@ -118,7 +118,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null);
@@ -143,6 +143,7 @@ const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'character
// overload function を使いたいので lint エラーを無視する
function done(canceled: true): void;
function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
+
function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result });
modal.value?.close();
diff --git a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
index e3391bcf7e..af58f5c375 100644
--- a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import MkDigitalClock from './MkDigitalClock.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkDigitalClock.vue b/packages/frontend/src/components/MkDigitalClock.vue
index 2e2321e6ac..8198356a76 100644
--- a/packages/frontend/src/components/MkDigitalClock.vue
+++ b/packages/frontend/src/components/MkDigitalClock.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { defaultIdlingRenderScheduler } from '@/scripts/idle-render.js';
+import { defaultIdlingRenderScheduler } from '@/utility/idle-render.js';
const props = withDefaults(defineProps<{
showS?: boolean;
diff --git a/idea/MkDisableSection.vue b/packages/frontend/src/components/MkDisableSection.vue
index 360705071b..bd7ecf225d 100644
--- a/idea/MkDisableSection.vue
+++ b/packages/frontend/src/components/MkDisableSection.vue
@@ -24,7 +24,8 @@ defineProps<{
}
.disabled {
- opacity: 0.7;
+ opacity: 0.3;
+ filter: saturate(0.5);
}
.cover {
@@ -34,7 +35,7 @@ defineProps<{
width: 100%;
height: 100%;
cursor: not-allowed;
- --color: color(from var(--MI_THEME-error) srgb r g b / 0.25);
+ --color: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
background-size: auto auto;
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
}
diff --git a/packages/frontend/src/components/MkDonation.stories.impl.ts b/packages/frontend/src/components/MkDonation.stories.impl.ts
index 27d6b7df6c..71d0c20c63 100644
--- a/packages/frontend/src/components/MkDonation.stories.impl.ts
+++ b/packages/frontend/src/components/MkDonation.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { onBeforeUnmount } from 'vue';
import MkDonation from './MkDonation.vue';
import { instance } from '@/instance.js';
diff --git a/packages/frontend/src/components/MkDrive.file.stories.impl.ts b/packages/frontend/src/components/MkDrive.file.stories.impl.ts
index 5f6e6a0667..933383775c 100644
--- a/packages/frontend/src/components/MkDrive.file.stories.impl.ts
+++ b/packages/frontend/src/components/MkDrive.file.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkDrive_file from './MkDrive.file.vue';
import { file } from '../../.storybook/fakes.js';
export const Default = {
diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue
index 5dee448329..22a2673f33 100644
--- a/packages/frontend/src/components/MkDrive.file.vue
+++ b/packages/frontend/src/components/MkDrive.file.vue
@@ -48,10 +48,10 @@ import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import bytes from '@/filters/bytes.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
-import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
-import { deviceKind } from '@/scripts/device-kind.js';
-import { useRouter } from '@/router/supplier.js';
+import { $i } from '@/i.js';
+import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
+import { deviceKind } from '@/utility/device-kind.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/components/MkDrive.folder.stories.impl.ts b/packages/frontend/src/components/MkDrive.folder.stories.impl.ts
index 5f8ef48520..e6c7c2f645 100644
--- a/packages/frontend/src/components/MkDrive.folder.stories.impl.ts
+++ b/packages/frontend/src/components/MkDrive.folder.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http, HttpResponse } from 'msw';
import * as Misskey from 'misskey-js';
import MkDrive_folder from './MkDrive.folder.vue';
diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue
index 8496890f60..d8ae3e9562 100644
--- a/packages/frontend/src/components/MkDrive.folder.vue
+++ b/packages/frontend/src/components/MkDrive.folder.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-if="!hover"><i :class="$style.icon" class="ti ti-folder ti-fw"></i></template>
{{ folder.name }}
</p>
- <p v-if="defaultStore.state.uploadFolder == folder.id" :class="$style.upload">
+ <p v-if="prefer.s.uploadFolder == folder.id" :class="$style.upload">
{{ i18n.ts.uploadFolder }}
</p>
<button v-if="selectMode" class="_button" :class="$style.checkboxWrapper" @click.prevent.stop="checkboxClicked">
@@ -38,11 +38,11 @@ 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 { misskeyApi } from '@/utility/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 { claimAchievement } from '@/utility/achievements.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
folder: Misskey.entities.DriveFolder;
@@ -244,8 +244,8 @@ function deleteFolder() {
misskeyApi('drive/folders/delete', {
folderId: props.folder.id,
}).then(() => {
- if (defaultStore.state.uploadFolder === props.folder.id) {
- defaultStore.set('uploadFolder', null);
+ if (prefer.s.uploadFolder === props.folder.id) {
+ prefer.commit('uploadFolder', null);
}
}).catch(err => {
switch (err.id) {
@@ -266,7 +266,7 @@ function deleteFolder() {
}
function setAsUploadFolder() {
- defaultStore.set('uploadFolder', props.folder.id);
+ prefer.commit('uploadFolder', props.folder.id);
}
function onContextmenu(ev: MouseEvent) {
@@ -295,9 +295,9 @@ function onContextmenu(ev: MouseEvent) {
danger: true,
action: deleteFolder,
}];
- if (defaultStore.state.devMode) {
+ if (prefer.s.devMode) {
menu = menu.concat([{ type: 'divider' }, {
- icon: 'ti ti-id',
+ icon: 'ti ti-hash',
text: i18n.ts.copyFolderId,
action: () => {
copyToClipboard(props.folder.id);
diff --git a/packages/frontend/src/components/MkDrive.navFolder.vue b/packages/frontend/src/components/MkDrive.navFolder.vue
index 8df3c86ebf..7433aea061 100644
--- a/packages/frontend/src/components/MkDrive.navFolder.vue
+++ b/packages/frontend/src/components/MkDrive.navFolder.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
diff --git a/packages/frontend/src/components/MkDrive.stories.impl.ts b/packages/frontend/src/components/MkDrive.stories.impl.ts
index fe20e61415..4394eebfda 100644
--- a/packages/frontend/src/components/MkDrive.stories.impl.ts
+++ b/packages/frontend/src/components/MkDrive.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http, HttpResponse } from 'msw';
import * as Misskey from 'misskey-js';
import MkDrive from './MkDrive.vue';
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index 5a0803f1e3..730dd0d55e 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -103,7 +103,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
+import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from './MkButton.vue';
import type { MenuItem } from '@/types/menu.js';
@@ -112,12 +112,12 @@ import XFolder from '@/components/MkDrive.folder.vue';
import XFile from '@/components/MkDrive.file.vue';
import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
-import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
-import { uploadFile, uploads } from '@/scripts/upload.js';
-import { claimAchievement } from '@/scripts/achievements.js';
+import { uploadFile, uploads } from '@/utility/upload.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { prefer } from '@/preferences.js';
const searchQuery = ref('');
@@ -139,8 +139,8 @@ const emit = defineEmits<{
(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
}>();
-const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>();
-const fileInput = shallowRef<HTMLInputElement>();
+const loadMoreFiles = useTemplateRef('loadMoreFiles');
+const fileInput = useTemplateRef('fileInput');
const folder = ref<Misskey.entities.DriveFolder | null>(null);
const files = ref<Misskey.entities.DriveFile[]>([]);
@@ -152,7 +152,7 @@ const selectedFiles = ref<Misskey.entities.DriveFile[]>([]);
const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
const uploadings = uploads;
const connection = useStream().useChannel('drive');
-const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい
+const keepOriginal = ref<boolean>(prefer.s.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい
// ドロップされようとしているか
const draghover = ref(false);
@@ -730,7 +730,7 @@ function onContextmenu(ev: MouseEvent) {
}
onMounted(() => {
- if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) {
+ if (prefer.s.enableInfiniteScroll && loadMoreFiles.value) {
nextTick(() => {
ilFilesObserver.observe(loadMoreFiles.value?.$el);
});
@@ -751,7 +751,7 @@ onMounted(() => {
});
onActivated(() => {
- if (defaultStore.state.enableInfiniteScroll) {
+ if (prefer.s.enableInfiniteScroll) {
nextTick(() => {
ilFilesObserver.observe(loadMoreFiles.value?.$el);
});
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts b/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts
index 3fa24d7edb..d259444e94 100644
--- a/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts
+++ b/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkDriveFileThumbnail from './MkDriveFileThumbnail.vue';
import { file } from '../../.storybook/fakes.js';
export const Default = {
diff --git a/packages/frontend/src/components/MkDriveSelectDialog.vue b/packages/frontend/src/components/MkDriveSelectDialog.vue
index f1ecc27123..1b9455e3f3 100644
--- a/packages/frontend/src/components/MkDriveSelectDialog.vue
+++ b/packages/frontend/src/components/MkDriveSelectDialog.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef } from 'vue';
+import { ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import XDrive from '@/components/MkDrive.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -43,7 +43,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]);
diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
index 6e9eb75920..d18fe0ed0c 100644
--- a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
+++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
@@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { shallowRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
+import { useTemplateRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
import { url } from '@@/js/config.js';
import { embedRouteWithScrollbar } from '@@/js/embed-page.js';
import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js';
@@ -105,8 +105,8 @@ import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { normalizeEmbedParams, getEmbedCode } from '@/scripts/get-embed-code.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { normalizeEmbedParams, getEmbedCode } from '@/utility/get-embed-code.js';
const emit = defineEmits<{
(ev: 'ok'): void;
@@ -121,7 +121,7 @@ const props = defineProps<{
}>();
//#region Modalの制御
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
function cancel() {
emit('cancel');
@@ -180,7 +180,7 @@ function applyToPreview() {
nextTick(() => {
if (currentPreviewUrl === embedPreviewUrl.value) {
// URLが変わらなくてもリロード
- iframeEl.value?.contentWindow?.location.reload();
+ iframeEl.value?.contentWindow?.window.location.reload();
}
});
}
@@ -194,14 +194,13 @@ function generate() {
function doCopy() {
copyToClipboard(result.value);
- os.success();
}
//#endregion
//#region プレビューのリサイズ
-const resizerRootEl = shallowRef<HTMLDivElement>();
+const resizerRootEl = useTemplateRef('resizerRootEl');
const iframeLoading = ref(true);
-const iframeEl = shallowRef<HTMLIFrameElement>();
+const iframeEl = useTemplateRef('iframeEl');
const iframeHeight = ref(0);
const iframeScale = ref(1);
const iframeStyle = computed(() => {
diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue
index a4f763e895..8142fdeb36 100644
--- a/packages/frontend/src/components/MkEmojiPicker.section.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.section.vue
@@ -61,8 +61,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, computed, Ref } from 'vue';
-import { CustomEmojiFolderTree, getEmojiName } from '@@/js/emojilist.js';
+import { ref, computed } from 'vue';
+import type { Ref } from 'vue';
+import { getEmojiName } from '@@/js/emojilist.js';
+import type { CustomEmojiFolderTree } from '@@/js/emojilist.js';
import { i18n } from '@/i18n.js';
import { customEmojis } from '@/custom-emojis.js';
import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue';
diff --git a/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts b/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts
index d38d8de808..bf4158a2c8 100644
--- a/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts
+++ b/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts
@@ -5,7 +5,7 @@
import { action } from '@storybook/addon-actions';
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { i18n } from '@/i18n.js';
import MkEmojiPicker from './MkEmojiPicker.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index a782ae9d3b..880f12f3fb 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -115,31 +115,34 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, computed, watch, onMounted } from 'vue';
+import { ref, useTemplateRef, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import {
emojilist,
emojiCharByCategory,
- UnicodeEmojiDef,
unicodeEmojiCategories as categories,
getEmojiName,
- CustomEmojiFolderTree,
getUnicodeEmoji,
} from '@@/js/emojilist.js';
+import type {
+ UnicodeEmojiDef,
+ CustomEmojiFolderTree,
+} from '@@/js/emojilist.js';
import XSection from '@/components/MkEmojiPicker.section.vue';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import * as os from '@/os.js';
-import { isTouchUsing } from '@/scripts/touch.js';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { isTouchUsing } from '@/utility/touch.js';
+import { deviceKind } from '@/utility/device-kind.js';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
-import { $i } from '@/account.js';
-import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
+import { $i } from '@/i.js';
+import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
showPinned?: boolean;
- pinnedEmojis?: string[];
+ pinnedEmojis?: string[];
maxHeight?: number;
asDrawer?: boolean;
asWindow?: boolean;
@@ -154,15 +157,16 @@ const emit = defineEmits<{
(ev: 'esc'): void;
}>();
-const searchEl = shallowRef<HTMLInputElement>();
-const emojisEl = shallowRef<HTMLDivElement>();
+const searchEl = useTemplateRef('searchEl');
+const emojisEl = useTemplateRef('emojisEl');
const {
emojiPickerScale,
emojiPickerWidth,
emojiPickerHeight,
- recentlyUsedEmojis,
-} = defaultStore.reactiveState;
+} = prefer.r;
+
+const recentlyUsedEmojis = store.r.recentlyUsedEmojis;
const recentlyUsedEmojisDef = computed(() => {
return recentlyUsedEmojis.value.map(getDef).filter(x => x != null);
@@ -317,7 +321,7 @@ watch(q, () => {
}
if (matches.size >= max) return matches;
- for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
+ for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) {
if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) {
matches.add(emoji);
@@ -334,7 +338,7 @@ watch(q, () => {
}
if (matches.size >= max) return matches;
- for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
+ for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) {
if (index[emoji.char].some(k => k.startsWith(newQ))) {
matches.add(emoji);
@@ -351,7 +355,7 @@ watch(q, () => {
}
if (matches.size >= max) return matches;
- for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
+ for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
for (const emoji of emojis) {
if (index[emoji.char].some(k => k.includes(newQ))) {
matches.add(emoji);
@@ -413,7 +417,7 @@ function computeButtonTitle(ev: MouseEvent): void {
function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) {
const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
- if (el) {
+ if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -427,10 +431,10 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef,
// 最近使った絵文字更新
if (!pinned.value?.includes(key)) {
- let recents = defaultStore.state.recentlyUsedEmojis;
+ let recents = store.s.recentlyUsedEmojis;
recents = recents.filter((emoji) => emoji !== key);
recents.unshift(key);
- defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
+ store.set('recentlyUsedEmojis', recents.splice(0, 32));
}
}
@@ -582,7 +586,7 @@ defineExpose({
&:disabled {
cursor: not-allowed;
- background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
+ background: linear-gradient(-45deg, transparent 0% 48%, light-dark(rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.15)) 48% 52%, transparent 52% 100%);
opacity: 1;
> .emoji {
@@ -617,7 +621,7 @@ defineExpose({
&:disabled {
cursor: not-allowed;
- background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
+ background: linear-gradient(-45deg, transparent 0% 48%, light-dark(rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.15)) 48% 52%, transparent 52% 100%);
opacity: 1;
> .emoji {
@@ -738,7 +742,7 @@ defineExpose({
&:disabled {
cursor: not-allowed;
- background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
+ background: linear-gradient(-45deg, transparent 0% 48%, light-dark(rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.15)) 48% 52%, transparent 52% 100%);
opacity: 1;
> .emoji {
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 3178f72498..5c4400eaf8 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
ref="modal"
v-slot="{ type, maxHeight }"
:zPriority="'middle'"
- :preferType="defaultStore.state.emojiPickerStyle"
+ :preferType="prefer.s.emojiPickerStyle"
:hasInteractionWithOtherFocusTrappedEls="true"
:transparentBg="true"
:manualShowing="manualShowing"
@@ -37,19 +37,19 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
manualShowing?: boolean | null;
src?: HTMLElement;
showPinned?: boolean;
- pinnedEmojis?: string[],
+ pinnedEmojis?: string[],
asReactionPicker?: boolean;
targetNote?: Misskey.entities.Note;
- choseAndClose?: boolean;
+ choseAndClose?: boolean;
}>(), {
manualShowing: null,
showPinned: true,
@@ -64,8 +64,8 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const modal = shallowRef<InstanceType<typeof MkModal>>();
-const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
+const modal = useTemplateRef('modal');
+const picker = useTemplateRef('picker');
function chosen(emoji: string) {
emit('done', emoji);
diff --git a/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts b/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts
index 6763f7c546..f531762710 100644
--- a/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts
+++ b/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkExtensionInstaller from './MkExtensionInstaller.vue';
import lightTheme from '@@/themes/_light.json5';
diff --git a/packages/frontend/src/components/MkExtensionInstaller.vue b/packages/frontend/src/components/MkExtensionInstaller.vue
index d59b20435e..a2247d844b 100644
--- a/packages/frontend/src/components/MkExtensionInstaller.vue
+++ b/packages/frontend/src/components/MkExtensionInstaller.vue
@@ -11,54 +11,91 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- 拡張用? -->
<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>
+
+ <h2 v-if="isPlugin" :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller._plugin.title }}</h2>
+ <h2 v-else-if="isTheme" :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller._theme.title }}</h2>
+
+ <MkInfo :warn="true">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</MkInfo>
+
+ <div v-if="isPlugin" class="_gaps_s">
+ <MkFolder :defaultOpen="true">
+ <template #icon><i class="ti ti-info-circle"></i></template>
+ <template #label>{{ i18n.ts.metadata }}</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>
+ <template #key>{{ i18n.ts.description }}</template>
+ <template #value>{{ extension.meta.description ?? i18n.ts.none }}</template>
+ </MkKeyValue>
<MkKeyValue>
- <template #key>{{ i18n.ts.name }}</template>
- <template #value>{{ extension.meta.name }}</template>
+ <template #key>{{ i18n.ts.version }}</template>
+ <template #value>{{ extension.meta.version }}</template>
</MkKeyValue>
<MkKeyValue>
- <template #key>{{ i18n.ts.author }}</template>
- <template #value>{{ extension.meta.author }}</template>
+ <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>
- </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>
+ </div>
+ </MkFolder>
+
+ <MkFolder :withSpacer="false">
+ <template #icon><i class="ti ti-code"></i></template>
+ <template #label>{{ i18n.ts._plugin.viewSource }}</template>
+
+ <MkCode :code="extension.raw"/>
+ </MkFolder>
+ </div>
+ <div v-else-if="isTheme" class="_gaps_s">
+ <MkFolder :defaultOpen="true">
+ <template #icon><i class="ti ti-info-circle"></i></template>
+ <template #label>{{ i18n.ts.metadata }}</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>
+ <template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
+ <template #value>{{ i18n.ts[extension.meta.base ?? 'none'] }}</template>
+ </MkKeyValue>
+ </div>
+ </MkFolder>
+
+ <MkFolder :withSpacer="false">
+ <template #icon><i class="ti ti-code"></i></template>
+ <template #label>{{ i18n.ts._theme.code }}</template>
+
+ <MkCode :code="extension.raw"/>
+ </MkFolder>
+ </div>
- <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>
+ <MkButton danger rounded large @click="emits('cancel')"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
+ <MkButton gradate rounded large @click="emits('confirm')"><i class="ti ti-download"></i> {{ i18n.ts.install }}</MkButton>
</div>
</div>
</template>
@@ -105,6 +142,7 @@ const props = defineProps<{
const emits = defineEmits<{
(ev: 'confirm'): void;
+ (ev: 'cancel'): void;
}>();
</script>
@@ -112,13 +150,13 @@ const emits = defineEmits<{
.extInstallerRoot {
border-radius: var(--MI-radius);
background: var(--MI_THEME-panel);
- padding: 1.5rem;
+ padding: 20px;
}
.extInstallerIconWrapper {
width: 48px;
height: 48px;
- font-size: 24px;
+ font-size: 20px;
line-height: 48px;
text-align: center;
border-radius: 50%;
@@ -135,10 +173,6 @@ const emits = defineEmits<{
margin: 0;
}
-.extInstallerNormDesc {
- text-align: center;
-}
-
.extInstallerKVList {
margin-top: 0;
margin-bottom: 0;
diff --git a/packages/frontend/src/components/MkFeatureBanner.vue b/packages/frontend/src/components/MkFeatureBanner.vue
new file mode 100644
index 0000000000..e990ffc8f0
--- /dev/null
+++ b/packages/frontend/src/components/MkFeatureBanner.vue
@@ -0,0 +1,43 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div v-panel :class="$style.root">
+ <img :class="$style.img" :src="icon"/>
+ <div :class="$style.text">
+ <slot></slot>
+ </div>
+</div>
+</template>
+
+<script setup lang="ts">
+withDefaults(defineProps<{
+ icon: string;
+ color: string;
+}>(), {
+});
+</script>
+
+<style module lang="scss">
+.root {
+ padding: 20px 24px;
+ text-align: center;
+ border-radius: var(--MI-radius);
+ background: linear-gradient(180deg, color(from v-bind(color) srgb r g b / 0.1), color(from v-bind(color) srgb r g b / 0));
+}
+
+.img {
+ display: block;
+ margin: 0 auto;
+ width: 40px;
+ aspect-ratio: 1;
+}
+
+.text {
+ margin-top: 12px;
+ font-size: 85%;
+ mix-blend-mode: luminosity;
+}
+</style>
diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
index 8754c72b7b..7b5eefdc10 100644
--- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue
+++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef, ref } from 'vue';
+import { useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -42,7 +42,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
const caption = ref(props.default);
diff --git a/packages/frontend/src/components/MkFlashPreview.stories.impl.ts b/packages/frontend/src/components/MkFlashPreview.stories.impl.ts
index fa5288b73d..4a751062c9 100644
--- a/packages/frontend/src/components/MkFlashPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkFlashPreview.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkFlashPreview from './MkFlashPreview.vue';
import { flash } from './../../.storybook/fakes.js';
export const Public = {
diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue
index 5bf3fdfe76..aa5c82fc16 100644
--- a/packages/frontend/src/components/MkFoldableSection.vue
+++ b/packages/frontend/src/components/MkFoldableSection.vue
@@ -14,10 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</button>
</header>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.folderToggleEnterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.folderToggleLeaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.folderToggleEnterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.folderToggleLeaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.folderToggleEnterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.folderToggleLeaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.folderToggleEnterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.folderToggleLeaveTo : ''"
@enter="enter"
@afterEnter="afterEnter"
@leave="leave"
@@ -31,10 +31,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, shallowRef, watch } from 'vue';
+import { onMounted, ref, useTemplateRef, watch } from 'vue';
import { miLocalStorage } from '@/local-storage.js';
-import { defaultStore } from '@/store.js';
-import { getBgColor } from '@/scripts/get-bg-color.js';
+import { prefer } from '@/preferences.js';
+import { getBgColor } from '@/utility/get-bg-color.js';
const miLocalStoragePrefix = 'ui:folder:' as const;
@@ -46,7 +46,7 @@ const props = withDefaults(defineProps<{
persistKey: null,
});
-const rootEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
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);
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 084c81bb52..0d062d0bd8 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -27,10 +27,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : undefined, overflow: maxHeight ? `auto` : undefined }" :aria-hidden="!opened">
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_toggle_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_toggle_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_toggle_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_toggle_leaveTo : ''"
@enter="enter"
@afterEnter="afterEnter"
@leave="leave"
@@ -56,9 +56,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { nextTick, onMounted, ref, shallowRef } from 'vue';
-import { defaultStore } from '@/store.js';
-import { getBgColor } from '@/scripts/get-bg-color.js';
+import { nextTick, onMounted, ref, useTemplateRef } from 'vue';
+import { prefer } from '@/preferences.js';
+import { getBgColor } from '@/utility/get-bg-color.js';
const props = withDefaults(defineProps<{
defaultOpen?: boolean;
@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<{
spacerMax: 22,
});
-const rootEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
const bgSame = ref(false);
const opened = ref(props.defaultOpen);
const openedAtLeastOnce = ref(props.defaultOpen);
@@ -116,7 +116,7 @@ function toggle() {
}
onMounted(() => {
- const computedStyle = getComputedStyle(document.documentElement);
+ const computedStyle = getComputedStyle(window.document.documentElement);
const parentBg = getBgColor(rootEl.value?.parentElement) ?? 'transparent';
const myBg = computedStyle.getPropertyValue('--MI_THEME-panel');
bgSame.value = parentBg === myBg;
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index 93c3b481b2..3ae56dbe8d 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -39,13 +39,13 @@ import { onBeforeUnmount, onMounted, ref, watch } 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 { misskeyApi } from '@/utility/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 { $i } from '@/account.js';
-import { defaultStore } from '@/store.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { pleaseLogin } from '@/utility/please-login.js';
+import { $i } from '@/i.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed,
@@ -106,7 +106,7 @@ async function onClick() {
userId: props.user.id,
});
} else {
- if (defaultStore.state.alwaysConfirmFollow && !hasPendingFollowRequestFromYou.value) {
+ if (prefer.s.alwaysConfirmFollow && !hasPendingFollowRequestFromYou.value) {
const { canceled } = await os.confirm({
type: 'question',
text: i18n.tsx.followConfirm({ name: props.user.name || props.user.username }),
@@ -136,11 +136,11 @@ async function onClick() {
} else {
await misskeyApi('following/create', {
userId: props.user.id,
- withReplies: defaultStore.state.defaultWithReplies,
+ withReplies: prefer.s.defaultFollowWithReplies,
});
emit('update:user', {
...props.user,
- withReplies: defaultStore.state.defaultWithReplies,
+ withReplies: prefer.s.defaultFollowWithReplies,
});
hasPendingFollowRequestFromYou.value = true;
diff --git a/packages/frontend/src/components/MkFormDialog.file.vue b/packages/frontend/src/components/MkFormDialog.file.vue
index ecb6cf882b..0a902f3400 100644
--- a/packages/frontend/src/components/MkFormDialog.file.vue
+++ b/packages/frontend/src/components/MkFormDialog.file.vue
@@ -15,8 +15,8 @@ import * as Misskey from 'misskey-js';
import { computed, ref } from 'vue';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
-import { selectFile } from '@/scripts/select-file.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { selectFile } from '@/utility/select-file.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
const props = defineProps<{
fileId?: string | null;
diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue
index a639eae208..4756079e76 100644
--- a/packages/frontend/src/components/MkFormDialog.vue
+++ b/packages/frontend/src/components/MkFormDialog.vue
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</div>
<div v-else class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</MkSpacer>
@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { reactive, shallowRef } from 'vue';
+import { reactive, useTemplateRef } from 'vue';
import MkInput from './MkInput.vue';
import MkTextarea from './MkTextarea.vue';
import MkSwitch from './MkSwitch.vue';
@@ -80,7 +80,7 @@ import MkRange from './MkRange.vue';
import MkButton from './MkButton.vue';
import MkRadios from './MkRadios.vue';
import XFile from './MkFormDialog.file.vue';
-import type { Form } from '@/scripts/form.js';
+import type { Form } from '@/utility/form.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
@@ -99,7 +99,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
const values = reactive({});
for (const item in props.form) {
diff --git a/packages/frontend/src/components/MkFukidashi.vue b/packages/frontend/src/components/MkFukidashi.vue
index 8b1c56fca4..e9544afa35 100644
--- a/packages/frontend/src/components/MkFukidashi.vue
+++ b/packages/frontend/src/components/MkFukidashi.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
tail === 'left' ? $style.left : $style.right,
negativeMargin === true && $style.negativeMargin,
shadow === true && $style.shadow,
+ accented === true && $style.accented
]"
>
<div :class="$style.bg">
@@ -30,10 +31,12 @@ withDefaults(defineProps<{
tail?: 'left' | 'right' | 'none';
negativeMargin?: boolean;
shadow?: boolean;
+ accented?: boolean;
}>(), {
tail: 'right',
negativeMargin: false,
shadow: false,
+ accented: false,
});
</script>
@@ -47,6 +50,10 @@ withDefaults(defineProps<{
min-height: calc(var(--fukidashi-radius) * 2);
padding-top: calc(var(--fukidashi-radius) * .13);
+ &.accented {
+ --fukidashi-bg: var(--MI_THEME-accent);
+ }
+
&.shadow {
filter: drop-shadow(0 4px 32px var(--MI_THEME-shadow));
}
@@ -77,7 +84,7 @@ withDefaults(defineProps<{
.content {
position: relative;
- padding: 8px 12px;
+ padding: 10px 14px;
}
.tail {
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
index a433ad680b..616e04aabb 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { galleryPost } from '../../.storybook/fakes.js';
import MkGalleryPostPreview from './MkGalleryPostPreview.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue
index 22f8355acf..49a6c65170 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.vue
+++ b/packages/frontend/src/components/MkGalleryPostPreview.vue
@@ -35,14 +35,14 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'misskey-js';
import { computed, ref } from 'vue';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
post: Misskey.entities.GalleryPost;
}>();
const hover = ref(false);
-const safe = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive);
+const safe = computed(() => prefer.s.nsfw === 'ignore' || prefer.s.nsfw === 'respect' && !props.post.isSensitive);
const show = computed(() => safe.value || hover.value);
function enterHover(): void {
diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue
index 0cc0df9911..28bb936755 100644
--- a/packages/frontend/src/components/MkHeatmap.vue
+++ b/packages/frontend/src/components/MkHeatmap.vue
@@ -13,14 +13,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
+import { onMounted, nextTick, watch, useTemplateRef, ref } from 'vue';
import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { alpha } from '@/scripts/color.js';
-import { initChart } from '@/scripts/init-chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { alpha } from '@/utility/color.js';
+import { initChart } from '@/utility/init-chart.js';
initChart();
@@ -35,8 +35,8 @@ const props = withDefaults(defineProps<{
label: '',
});
-const rootEl = shallowRef<HTMLDivElement | null>(null);
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const rootEl = useTemplateRef('rootEl');
+const chartEl = useTemplateRef('chartEl');
const now = new Date();
let chartInstance: Chart | null = null;
const fetching = ref(true);
@@ -106,7 +106,7 @@ async function renderChart() {
await nextTick();
- const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300';
+ const color = store.s.darkMode ? '#b4e900' : '#86b300';
// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする
const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue
index 196c962a06..bc63bef0b6 100644
--- a/packages/frontend/src/components/MkHorizontalSwipe.vue
+++ b/packages/frontend/src/components/MkHorizontalSwipe.vue
@@ -19,21 +19,20 @@ SPDX-License-Identifier: AGPL-3.0-only
:leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo"
:style="`--swipe: ${pullDistance}px;`"
>
- <!-- 【注意】slot内の最上位要素に動的にkeyを設定すること -->
- <!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
- <slot></slot>
+ <div :key="tabModel">
+ <slot></slot>
+ </div>
</Transition>
</div>
</template>
<script lang="ts" setup>
-import { ref, shallowRef, computed, nextTick, watch } from 'vue';
+import { ref, useTemplateRef, computed, nextTick, watch } from 'vue';
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
-import { defaultStore } from '@/store.js';
-import { isHorizontalSwipeSwiping as isSwiping } from '@/scripts/touch.js';
+import { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js';
+import { prefer } from '@/preferences.js';
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
-// eslint-disable-next-line no-undef
const tabModel = defineModel<string>('tab');
const props = defineProps<{
@@ -44,7 +43,7 @@ const emit = defineEmits<{
(ev: 'swiped', newKey: string, direction: 'left' | 'right'): void;
}>();
-const shouldAnimate = computed(() => defaultStore.reactiveState.enableHorizontalSwipe.value || defaultStore.reactiveState.animation.value);
+const shouldAnimate = computed(() => prefer.r.enableHorizontalSwipe.value || prefer.r.animation.value);
// ▼ しきい値 ▼ //
@@ -72,7 +71,7 @@ const isSwipingForClass = ref(false);
let swipeAborted = false;
function touchStart(event: TouchEvent) {
- if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
+ if (!prefer.r.enableHorizontalSwipe.value) return;
if (event.touches.length !== 1) return;
@@ -83,7 +82,7 @@ function touchStart(event: TouchEvent) {
}
function touchMove(event: TouchEvent) {
- if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
+ if (!prefer.r.enableHorizontalSwipe.value) return;
if (event.touches.length !== 1) return;
@@ -134,7 +133,7 @@ function touchEnd(event: TouchEvent) {
return;
}
- if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
+ if (!prefer.r.enableHorizontalSwipe.value) return;
if (event.touches.length !== 0) return;
diff --git a/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts b/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts
new file mode 100644
index 0000000000..339e6d10f3
--- /dev/null
+++ b/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { StoryObj } from '@storybook/vue3';
+import { file } from '../../.storybook/fakes.js';
+import MkImgPreviewDialog from './MkImgPreviewDialog.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkImgPreviewDialog,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '<MkImgPreviewDialog v-bind="props" />',
+ };
+ },
+ args: {
+ file: file(),
+ },
+ parameters: {
+ chromatic: {
+ // NOTE: ロードが終わるまで待つ
+ delay: 3000,
+ },
+ layout: 'centered',
+ },
+} satisfies StoryObj<typeof MkImgPreviewDialog>;
diff --git a/packages/frontend/src/components/MkImgPreviewDialog.vue b/packages/frontend/src/components/MkImgPreviewDialog.vue
new file mode 100644
index 0000000000..3e6e4e0ec9
--- /dev/null
+++ b/packages/frontend/src/components/MkImgPreviewDialog.vue
@@ -0,0 +1,58 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkModalWindow
+ ref="modal"
+ :width="1800"
+ :height="900"
+ @close="close"
+ @esc="close"
+ @click="close"
+>
+ <template #header>{{ file.name }}</template>
+ <div :class="$style.container">
+ <img :src="file.url" :alt="file.comment ?? file.name" :class="$style.img"/>
+ </div>
+</MkModalWindow>
+</template>
+<script lang="ts" setup>
+import { defineProps, ref } from 'vue';
+import MkModalWindow from './MkModalWindow.vue';
+import type * as Misskey from 'misskey-js';
+
+defineProps<{
+ file: Misskey.entities.DriveFile;
+}>();
+
+const modal = ref<typeof MkModalWindow | null>(null);
+
+function close() {
+ modal.value?.close();
+}
+
+</script>
+<style lang="scss" module>
+ .container {
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ min-height: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+
+ background-color: var(--MI_THEME-bg);
+ background-size: auto auto;
+ background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--MI_THEME-panel) 6px, var(--MI_THEME-panel) 12px);
+ }
+
+ .img {
+ width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ }
+</style>
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index b0741aaf5e..b71cabd5ff 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -6,16 +6,42 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''">
<TransitionGroup
- :duration="defaultStore.state.animation && props.transition?.duration || undefined"
- :enterActiveClass="defaultStore.state.animation && props.transition?.enterActiveClass || undefined"
- :leaveActiveClass="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style.transition_leaveActive) || undefined"
- :enterFromClass="defaultStore.state.animation && props.transition?.enterFromClass || undefined"
- :leaveToClass="defaultStore.state.animation && props.transition?.leaveToClass || undefined"
- :enterToClass="defaultStore.state.animation && props.transition?.enterToClass || undefined"
- :leaveFromClass="defaultStore.state.animation && props.transition?.leaveFromClass || undefined"
+ :duration="prefer.s.animation && props.transition?.duration || undefined"
+ :enterActiveClass="prefer.s.animation && props.transition?.enterActiveClass || undefined"
+ :leaveActiveClass="prefer.s.animation && (props.transition?.leaveActiveClass ?? $style.transition_leaveActive) || undefined"
+ :enterFromClass="prefer.s.animation && props.transition?.enterFromClass || undefined"
+ :leaveToClass="prefer.s.animation && props.transition?.leaveToClass || undefined"
+ :enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined"
+ :leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined"
>
- <canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/>
- <img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/>
+ <canvas
+ v-show="hide"
+ key="canvas"
+ ref="canvas"
+ :class="$style.canvas"
+ :width="canvasWidth"
+ :height="canvasHeight"
+ :title="title ?? undefined"
+ draggable="false"
+ tabindex="-1"
+ style="-webkit-user-drag: none;"
+ />
+ <img
+ v-show="!hide"
+ key="img"
+ ref="img"
+ :height="imgHeight ?? undefined"
+ :width="imgWidth ?? undefined"
+ :class="$style.img"
+ :src="src ?? undefined"
+ :title="title ?? undefined"
+ :alt="alt ?? undefined"
+ loading="eager"
+ decoding="async"
+ draggable="false"
+ tabindex="-1"
+ style="-webkit-user-drag: none;"
+ />
</TransitionGroup>
</div>
</template>
@@ -29,7 +55,7 @@ import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurha
const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
// テスト環境で Web Worker インスタンスは作成できない
if (import.meta.env.MODE === 'test') {
- const canvas = document.createElement('canvas');
+ const canvas = window.document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
@@ -43,13 +69,11 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
Math.min(navigator.hardwareConcurrency - 1, 4),
);
resolve(workers);
- if (_DEV_) console.log('WebGL2 in worker is supported!');
} else {
- const canvas = document.createElement('canvas');
+ const canvas = window.document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
- if (_DEV_) console.log('WebGL2 in worker is not supported...');
}
testWorker.terminate();
});
@@ -57,10 +81,10 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
</script>
<script lang="ts" setup>
-import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue';
+import { computed, nextTick, onMounted, onUnmounted, useTemplateRef, watch, ref } from 'vue';
import { v4 as uuid } from 'uuid';
import { render } from 'buraha';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
transition?: {
@@ -94,9 +118,9 @@ const props = withDefaults(defineProps<{
});
const viewId = uuid();
-const canvas = shallowRef<HTMLCanvasElement>();
-const root = shallowRef<HTMLDivElement>();
-const img = shallowRef<HTMLImageElement>();
+const canvas = useTemplateRef('canvas');
+const root = useTemplateRef('root');
+const img = useTemplateRef('img');
const loaded = ref(false);
const canvasWidth = ref(64);
const canvasHeight = ref(64);
diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue
index bdc38f5142..edfddc9037 100644
--- a/packages/frontend/src/components/MkInfo.vue
+++ b/packages/frontend/src/components/MkInfo.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div :class="[$style.root, { [$style.warn]: warn }]">
+<div :class="[$style.root, { [$style.warn]: warn }]" class="_selectable">
<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i>
<i v-else class="ti ti-info-circle" :class="$style.i"></i>
<div><slot></slot></div>
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index ec299dce36..26c361c122 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div>
+<div class="_selectable">
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
<div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]">
<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
@@ -44,12 +44,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs, InputHTMLAttributes } from 'vue';
+import { onMounted, onUnmounted, nextTick, ref, useTemplateRef, watch, computed, toRefs } from 'vue';
import { debounce } from 'throttle-debounce';
-import MkButton from '@/components/MkButton.vue';
import { useInterval } from '@@/js/use-interval.js';
+import type { InputHTMLAttributes } from 'vue';
+import type { SuggestionType } from '@/utility/autocomplete.js';
+import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js';
+import { Autocomplete } from '@/utility/autocomplete.js';
const props = defineProps<{
modelValue: string | number | null;
@@ -90,9 +92,9 @@ const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
-const inputEl = shallowRef<HTMLInputElement>();
-const prefixEl = shallowRef<HTMLElement>();
-const suffixEl = shallowRef<HTMLElement>();
+const inputEl = useTemplateRef('inputEl');
+const prefixEl = useTemplateRef('prefixEl');
+const suffixEl = useTemplateRef('suffixEl');
const height =
props.small ? 33 :
props.large ? 39 :
diff --git a/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts b/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
index 9e8de9d878..bd69fb2f82 100644
--- a/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
+++ b/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
@@ -3,13 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { federationInstance } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import { getChartResolver } from '../../.storybook/charts.js';
import MkInstanceCardMini from './MkInstanceCardMini.vue';
+import type { StoryObj } from '@storybook/vue3';
export const Default = {
render(args) {
@@ -48,7 +47,7 @@ export const Default = {
const url = new URL(urlStr);
if (url.href.startsWith('https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/')) {
- const image = await (await fetch(`client-assets/${url.pathname.split('/').pop()}`)).blob();
+ const image = await (await window.fetch(`client-assets/${url.pathname.split('/').pop()}`)).blob();
return new HttpResponse(image, {
headers: {
'Content-Type': 'image/jpeg',
diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue
index b063b82b17..d20b24a439 100644
--- a/packages/frontend/src/components/MkInstanceCardMini.vue
+++ b/packages/frontend/src/components/MkInstanceCardMini.vue
@@ -18,8 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkMiniChart from '@/components/MkMiniChart.vue';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
-import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
+import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
const props = defineProps<{
instance: Misskey.entities.FederationInstance;
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index d8066857fe..90391005bc 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -84,21 +84,22 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, computed, shallowRef } from 'vue';
+import { onMounted, ref, computed, useTemplateRef } from 'vue';
import { Chart } from 'chart.js';
+import type { HeatmapSource } from '@/components/MkHeatmap.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkChart from '@/components/MkChart.vue';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { $i } from '@/account.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { $i } from '@/i.js';
import * as os from '@/os.js';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import MkHeatmap, { type HeatmapSource } from '@/components/MkHeatmap.vue';
+import MkHeatmap from '@/components/MkHeatmap.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
-import { initChart } from '@/scripts/init-chart.js';
+import { initChart } from '@/utility/init-chart.js';
initChart();
@@ -108,8 +109,8 @@ const chartLimit = 500;
const chartSpan = ref<'hour' | 'day'>('hour');
const chartSrc = ref('active-users');
const heatmapSrc = ref<HeatmapSource>('active-users');
-const subDoughnutEl = shallowRef<HTMLCanvasElement>();
-const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
+const subDoughnutEl = useTemplateRef('subDoughnutEl');
+const pubDoughnutEl = useTemplateRef('pubDoughnutEl');
const { handler: externalTooltipHandler1 } = useChartTooltip({
position: 'middle',
@@ -125,7 +126,7 @@ function createDoughnut(chartEl, tooltip, data) {
labels: data.map(x => x.name),
datasets: [{
backgroundColor: data.map(x => x.color),
- borderColor: getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'),
+ borderColor: getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel'),
borderWidth: 2,
hoverOffset: 0,
data: data.map(x => x.value),
diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue
index 9d9cc76822..e0c07f1f9f 100644
--- a/packages/frontend/src/components/MkInstanceTicker.vue
+++ b/packages/frontend/src/components/MkInstanceTicker.vue
@@ -11,10 +11,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, type CSSProperties } from 'vue';
+import { computed } from 'vue';
+import type { CSSProperties } from 'vue';
import { instanceName as localInstanceName } from '@@/js/config.js';
import { instance as localInstance } from '@/instance.js';
-import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
+import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
const props = defineProps<{
host: string | null;
diff --git a/packages/frontend/src/components/MkInviteCode.stories.impl.ts b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
index 456d215288..ccdebf0a4d 100644
--- a/packages/frontend/src/components/MkInviteCode.stories.impl.ts
+++ b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue
index 1a71f6574f..ab797459cc 100644
--- a/packages/frontend/src/components/MkInviteCode.vue
+++ b/packages/frontend/src/components/MkInviteCode.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.items">
<div>
<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
- <div>{{ invite.code }}</div>
+ <div class="_selectableAtomic">{{ invite.code }}</div>
</div>
<div v-if="moderator">
<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
@@ -64,7 +64,7 @@ import { computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
@@ -90,7 +90,6 @@ function deleteCode() {
function copyInviteCode() {
copyToClipboard(props.invite.code);
- os.success();
}
</script>
diff --git a/packages/frontend/src/components/MkKeyValue.vue b/packages/frontend/src/components/MkKeyValue.vue
index 50c9e16e5e..b4185d2d0a 100644
--- a/packages/frontend/src/components/MkKeyValue.vue
+++ b/packages/frontend/src/components/MkKeyValue.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.key">
<slot name="key"></slot>
</div>
- <div :class="$style.value">
+ <div :class="$style.value" class="_selectable">
<slot name="value"></slot>
<button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button>
</div>
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
@@ -31,7 +31,6 @@ const props = withDefaults(defineProps<{
const copy_ = () => {
copyToClipboard(props.copy);
- os.success();
};
</script>
diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index c7af75e2e7..f33896b7da 100644
--- a/packages/frontend/src/components/MkLaunchPad.vue
+++ b/packages/frontend/src/components/MkLaunchPad.vue
@@ -27,11 +27,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
import { navbarItemDef } from '@/navbar.js';
-import { defaultStore } from '@/store.js';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { deviceKind } from '@/utility/device-kind.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
src?: HTMLElement;
@@ -48,9 +48,9 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
deviceKind === 'smartphone' ? 'drawer' :
'dialog';
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
-const menu = defaultStore.state.menu;
+const menu = prefer.s.menu;
const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k => navbarItemDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({
type: def.to ? 'link' : 'button',
diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue
index 263cd95eb1..4a431a64df 100644
--- a/packages/frontend/src/components/MkLink.vue
+++ b/packages/frontend/src/components/MkLink.vue
@@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue';
import { url as local } from '@@/js/config.js';
-import { useTooltip } from '@/scripts/use-tooltip.js';
+import { useTooltip } from '@/use/use-tooltip.js';
import * as os from '@/os.js';
import { isEnabledUrlPreview } from '@/instance.js';
-import { MkABehavior } from '@/components/global/MkA.vue';
+import type { MkABehavior } from '@/components/global/MkA.vue';
import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue
index 10450fb621..8ede22db0d 100644
--- a/packages/frontend/src/components/MkMediaAudio.vue
+++ b/packages/frontend/src/components/MkMediaAudio.vue
@@ -10,20 +10,20 @@ SPDX-License-Identifier: AGPL-3.0-only
tabindex="0"
:class="[
$style.audioContainer,
- (audio.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive,
+ (audio.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive,
]"
@contextmenu.stop
@keydown.stop
>
<button v-if="hide" :class="$style.hidden" @click="show">
<div :class="$style.hiddenTextWrapper">
- <b v-if="audio.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
- <b v-else style="display: block;"><i class="ti ti-music"></i> {{ defaultStore.state.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
+ <b v-if="audio.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
+ <b v-else style="display: block;"><i class="ti ti-music"></i> {{ prefer.s.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
</div>
</button>
- <div v-else-if="defaultStore.reactiveState.useNativeUIForVideoAudioPlayer.value" :class="$style.nativeAudioContainer">
+ <div v-else-if="prefer.s.useNativeUiForVideoAudioPlayer" :class="$style.nativeAudioContainer">
<audio
ref="audioEl"
preload="metadata"
@@ -91,17 +91,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
+import { useTemplateRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
-import { defaultStore } from '@/store.js';
+import type { Keymap } from '@/utility/hotkey.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { type Keymap } from '@/scripts/hotkey.js';
import bytes from '@/filters/bytes.js';
import { hms } from '@/filters/hms.js';
import MkMediaRange from '@/components/MkMediaRange.vue';
-import { $i, iAmModerator } from '@/account.js';
+import { $i, iAmModerator } from '@/i.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
audio: Misskey.entities.DriveFile;
@@ -150,17 +151,17 @@ const keymap = {
// PlayerElもしくはその子要素にフォーカスがあるかどうか
function hasFocus() {
if (!playerEl.value) return false;
- return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement);
+ return playerEl.value === window.document.activeElement || playerEl.value.contains(window.document.activeElement);
}
-const playerEl = shallowRef<HTMLDivElement>();
-const audioEl = shallowRef<HTMLAudioElement>();
+const playerEl = useTemplateRef('playerEl');
+const audioEl = useTemplateRef('audioEl');
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
-const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore'));
+const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));
async function show() {
- if (props.audio.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
+ if (props.audio.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
const { canceled } = await os.confirm({
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
@@ -219,10 +220,9 @@ function showMenu(ev: MouseEvent) {
});
}
+ const details: MenuItem[] = [];
if ($i?.id === props.audio.userId) {
- menu.push({
- type: 'divider',
- }, {
+ details.push({
type: 'link',
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
@@ -230,6 +230,29 @@ function showMenu(ev: MouseEvent) {
});
}
+ if (iAmModerator) {
+ details.push({
+ type: 'link',
+ text: i18n.ts.moderation,
+ icon: 'ti ti-photo-exclamation',
+ to: `/admin/file/${props.audio.id}`,
+ });
+ }
+
+ if (details.length > 0) {
+ menu.push({ type: 'divider' }, ...details);
+ }
+
+ if (prefer.s.devMode) {
+ menu.push({ type: 'divider' }, {
+ icon: 'ti ti-hash',
+ text: i18n.ts.copyFileId,
+ action: () => {
+ copyToClipboard(props.audio.id);
+ },
+ });
+ }
+
menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right',
@@ -239,7 +262,14 @@ function showMenu(ev: MouseEvent) {
});
}
-function toggleSensitive(file: Misskey.entities.DriveFile) {
+async function toggleSensitive(file: Misskey.entities.DriveFile) {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ text: file.isSensitive ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
+ });
+
+ if (canceled) return;
+
os.apiWithDialog('drive/files/update', {
fileId: file.id,
isSensitive: !file.isSensitive,
@@ -380,7 +410,7 @@ onDeactivated(() => {
elapsedTimeMs.value = 0;
durationMs.value = 0;
bufferedEnd.value = 0;
- hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore');
+ hide.value = (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore');
stopAudioElWatch();
onceInit = false;
if (mediaTickFrameId) {
diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue
index 56048a33d8..2aa971d43c 100644
--- a/packages/frontend/src/components/MkMediaBanner.vue
+++ b/packages/frontend/src/components/MkMediaBanner.vue
@@ -27,9 +27,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
import * as os from '@/os.js';
import MkMediaAudio from '@/components/MkMediaAudio.vue';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
media: Misskey.entities.DriveFile;
@@ -38,7 +38,7 @@ const props = defineProps<{
const hide = ref(true);
async function show() {
- if (props.media.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
+ if (props.media.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
const { canceled } = await os.confirm({
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue
index 4aca7256a5..0f72da9ce9 100644
--- a/packages/frontend/src/components/MkMediaImage.vue
+++ b/packages/frontend/src/components/MkMediaImage.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive]" @click="onclick">
+<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive]" @click="onclick">
<component
:is="disableImageLink ? 'div' : 'a'"
v-bind="disableImageLink ? {
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<ImgWithBlurhash
:hash="image.blurhash"
- :src="(defaultStore.state.dataSaver.media && hide) ? null : url"
+ :src="(prefer.s.dataSaver.media && hide) ? null : url"
:forceBlurhash="hide"
:cover="hide || cover"
:alt="image.comment"
@@ -32,8 +32,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-if="hide">
<div :class="$style.hiddenText">
<div :class="$style.hiddenTextWrapper">
- <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
- <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.dataSaver.media && image.size ? bytes(image.size) : i18n.ts.image }}</b>
+ <b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
+ <b v-else style="display: block;"><i class="ti ti-photo"></i> {{ prefer.s.dataSaver.media && image.size ? bytes(image.size) : i18n.ts.image }}</b>
<span v-if="controls" style="display: block;">{{ i18n.ts.clickToShow }}</span>
</div>
</div>
@@ -55,13 +55,14 @@ SPDX-License-Identifier: AGPL-3.0-only
import { watch, ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard';
+import { getStaticImageUrl } from '@/utility/media-proxy.js';
import bytes from '@/filters/bytes.js';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
-import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { $i, iAmModerator } from '@/account.js';
+import { $i, iAmModerator } from '@/i.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
image: Misskey.entities.DriveFile;
@@ -77,9 +78,9 @@ const props = withDefaults(defineProps<{
const hide = ref(true);
-const url = computed(() => (props.raw || defaultStore.state.loadRawImages)
+const url = computed(() => (props.raw || prefer.s.loadRawImages)
? props.image.url
- : defaultStore.state.disableShowingAnimatedImages
+ : prefer.s.disableShowingAnimatedImages
? getStaticImageUrl(props.image.url)
: props.image.thumbnailUrl,
);
@@ -91,7 +92,7 @@ async function onclick(ev: MouseEvent) {
if (hide.value) {
ev.stopPropagation();
- if (props.image.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
+ if (props.image.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
const { canceled } = await os.confirm({
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
@@ -105,7 +106,7 @@ async function onclick(ev: MouseEvent) {
// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
watch(() => props.image, () => {
- hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
+ hide.value = (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.image.isSensitive && prefer.s.nsfw !== 'ignore');
}, {
deep: true,
immediate: true,
@@ -124,19 +125,28 @@ function showMenu(ev: MouseEvent) {
if (iAmModerator) {
menuItems.push({
- text: i18n.ts.markAsSensitive,
+ text: props.image.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
icon: 'ti ti-eye-exclamation',
danger: true,
- action: () => {
- os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
+ action: async () => {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ text: props.image.isSensitive ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
+ });
+
+ if (canceled) return;
+
+ os.apiWithDialog('drive/files/update', {
+ fileId: props.image.id,
+ isSensitive: !props.image.isSensitive,
+ });
},
});
}
+ const details: MenuItem[] = [];
if ($i?.id === props.image.userId) {
- menuItems.push({
- type: 'divider',
- }, {
+ details.push({
type: 'link',
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
@@ -144,6 +154,29 @@ function showMenu(ev: MouseEvent) {
});
}
+ if (iAmModerator) {
+ details.push({
+ type: 'link',
+ text: i18n.ts.moderation,
+ icon: 'ti ti-photo-exclamation',
+ to: `/admin/file/${props.image.id}`,
+ });
+ }
+
+ if (details.length > 0) {
+ menuItems.push({ type: 'divider' }, ...details);
+ }
+
+ if (prefer.s.devMode) {
+ menuItems.push({ type: 'divider' }, {
+ icon: 'ti ti-hash',
+ text: i18n.ts.copyFileId,
+ action: () => {
+ copyToClipboard(props.image.id);
+ },
+ });
+ }
+
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 487cf509d6..fcc01d9197 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -12,9 +12,9 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[
$style.medias,
count === 1 ? [$style.n1, {
- [$style.n116_9]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '16_9',
- [$style.n11_1]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '1_1',
- [$style.n12_3]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '2_3',
+ [$style.n116_9]: prefer.s.mediaListWithOneImageAppearance === '16_9',
+ [$style.n11_1]: prefer.s.mediaListWithOneImageAppearance === '1_1',
+ [$style.n12_3]: prefer.s.mediaListWithOneImageAppearance === '2_3',
}] : count === 2 ? $style.n2 : count === 3 ? $style.n3 : count === 4 ? $style.n4 : $style.nMany,
]"
>
@@ -30,29 +30,29 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, onMounted, onUnmounted, shallowRef } from 'vue';
+import { computed, onMounted, onUnmounted, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import PhotoSwipeLightbox from 'photoswipe/lightbox';
import PhotoSwipe from 'photoswipe';
import 'photoswipe/style.css';
+import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES, FILE_TYPE_FLASH_CONTENT, FILE_EXT_FLASH_CONTENT } from '@@/js/const.js';
import XBanner from '@/components/MkMediaBanner.vue';
import XImage from '@/components/MkMediaImage.vue';
import XVideo from '@/components/MkMediaVideo.vue';
import XModPlayer from '@/components/SkModPlayer.vue';
import XFlashPlayer from '@/components/SkFlashPlayer.vue';
import * as os from '@/os.js';
-import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES, FILE_TYPE_FLASH_CONTENT, FILE_EXT_FLASH_CONTENT } from '@@/js/const.js';
-import { defaultStore } from '@/store.js';
-import { focusParent } from '@/scripts/focus.js';
+import { focusParent } from '@/utility/focus.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
mediaList: Misskey.entities.DriveFile[];
raw?: boolean;
}>();
-const gallery = shallowRef<HTMLDivElement>();
+const gallery = useTemplateRef('gallery');
const pswpZIndex = os.claimZIndex('middle');
-document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
+window.document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
const count = computed(() => props.mediaList.filter(media => previewable(media)).length);
let lightbox: PhotoSwipeLightbox | null = null;
@@ -79,7 +79,7 @@ async function calcAspectRatio() {
return `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`;
};
- switch (defaultStore.state.mediaListWithOneImageAppearance) {
+ switch (prefer.s.mediaListWithOneImageAppearance) {
case '16_9':
gallery.value.style.aspectRatio = ratioMax(16 / 9);
break;
@@ -182,7 +182,7 @@ onMounted(() => {
className: 'pswp__alt-text-container',
appendTo: 'wrapper',
onInit: (el, pswp) => {
- const textBox = document.createElement('p');
+ const textBox = window.document.createElement('p');
textBox.className = 'pswp__alt-text _acrylic';
el.appendChild(textBox);
@@ -206,19 +206,19 @@ onMounted(() => {
});
lightbox.on('afterInit', () => {
- activeEl = document.activeElement instanceof HTMLElement ? document.activeElement : null;
+ activeEl = window.document.activeElement instanceof HTMLElement ? window.document.activeElement : null;
focusParent(activeEl, true, true);
lightbox?.pswp?.element?.focus({
preventScroll: true,
});
- history.pushState(null, '', '#pswp');
+ window.history.pushState(null, '', '#pswp');
});
lightbox.on('destroy', () => {
focusParent(activeEl, true, false);
activeEl = null;
if (window.location.hash === '#pswp') {
- history.back();
+ window.history.back();
}
});
@@ -261,7 +261,6 @@ defineExpose({
.container {
position: relative;
width: 100%;
- margin-top: 4px;
}
.medias {
diff --git a/packages/frontend/src/components/MkMediaRange.vue b/packages/frontend/src/components/MkMediaRange.vue
index df7505b0c3..9689dc5cfa 100644
--- a/packages/frontend/src/components/MkMediaRange.vue
+++ b/packages/frontend/src/components/MkMediaRange.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { computed, ModelRef } from 'vue';
+import { computed } from 'vue';
withDefaults(defineProps<{
buffer?: number;
@@ -28,8 +28,7 @@ const emit = defineEmits<{
(ev: 'dragEnded', value: number): void;
}>();
-// eslint-disable-next-line no-undef
-const model = defineModel({ required: true }) as ModelRef<string | number>;
+const model = defineModel<string | number>({ required: true });
const modelValue = computed({
get: () => typeof model.value === 'number' ? model.value : parseFloat(model.value),
set: v => { model.value = v; },
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index 04f8e9f8d8..a60e100969 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[
$style.videoContainer,
controlsShowing && $style.active,
- (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive,
+ (video.isSensitive && prefer.s.highlightSensitiveMedia) && $style.sensitive,
]"
@mouseover="onMouseOver"
@mouseleave="onMouseLeave"
@@ -20,13 +20,13 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<button v-if="hide" :class="$style.hidden" @click="show">
<div :class="$style.hiddenTextWrapper">
- <b v-if="video.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
- <b v-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
+ <b v-if="video.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
+ <b v-else style="display: block;"><i class="ti ti-movie"></i> {{ prefer.s.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
</div>
</button>
- <div v-else-if="defaultStore.reactiveState.useNativeUIForVideoAudioPlayer.value" :class="$style.videoRoot">
+ <div v-else-if="prefer.s.useNativeUiForVideoAudioPlayer" :class="$style.videoRoot">
<video
ref="videoEl"
:class="$style.video"
@@ -112,19 +112,20 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
+import { ref, useTemplateRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
-import { type Keymap } from '@/scripts/hotkey.js';
+import type { Keymap } from '@/utility/hotkey.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard';
import bytes from '@/filters/bytes.js';
import { hms } from '@/filters/hms.js';
-import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { exitFullscreen, requestFullscreen } from '@/scripts/fullscreen.js';
-import hasAudio from '@/scripts/media-has-audio.js';
+import { exitFullscreen, requestFullscreen } from '@/utility/fullscreen.js';
+import hasAudio from '@/utility/media-has-audio.js';
import MkMediaRange from '@/components/MkMediaRange.vue';
-import { $i, iAmModerator } from '@/account.js';
+import { $i, iAmModerator } from '@/i.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
video: Misskey.entities.DriveFile;
@@ -173,14 +174,14 @@ const keymap = {
// PlayerElもしくはその子要素にフォーカスがあるかどうか
function hasFocus() {
if (!playerEl.value) return false;
- return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement);
+ return playerEl.value === window.document.activeElement || playerEl.value.contains(window.document.activeElement);
}
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
-const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
+const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.video.isSensitive && prefer.s.nsfw !== 'ignore'));
async function show() {
- if (props.video.isSensitive && defaultStore.state.confirmWhenRevealingSensitiveMedia) {
+ if (props.video.isSensitive && prefer.s.confirmWhenRevealingSensitiveMedia) {
const { canceled } = await os.confirm({
type: 'question',
text: i18n.ts.sensitiveMediaRevealConfirm,
@@ -218,7 +219,7 @@ function showMenu(ev: MouseEvent) {
'2.0x': 2,
},
},
- ...(document.pictureInPictureEnabled ? [{
+ ...(window.document.pictureInPictureEnabled ? [{
text: i18n.ts._mediaControls.pip,
icon: 'ti ti-picture-in-picture',
action: togglePictureInPicture,
@@ -244,10 +245,9 @@ function showMenu(ev: MouseEvent) {
});
}
+ const details: MenuItem[] = [];
if ($i?.id === props.video.userId) {
- menu.push({
- type: 'divider',
- }, {
+ details.push({
type: 'link',
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
@@ -255,6 +255,29 @@ function showMenu(ev: MouseEvent) {
});
}
+ if (iAmModerator) {
+ details.push({
+ type: 'link',
+ text: i18n.ts.moderation,
+ icon: 'ti ti-photo-exclamation',
+ to: `/admin/file/${props.video.id}`,
+ });
+ }
+
+ if (details.length > 0) {
+ menu.push({ type: 'divider' }, ...details);
+ }
+
+ if (prefer.s.devMode) {
+ menu.push({ type: 'divider' }, {
+ icon: 'ti ti-hash',
+ text: i18n.ts.copyFileId,
+ action: () => {
+ copyToClipboard(props.video.id);
+ },
+ });
+ }
+
menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right',
@@ -264,7 +287,14 @@ function showMenu(ev: MouseEvent) {
});
}
-function toggleSensitive(file: Misskey.entities.DriveFile) {
+async function toggleSensitive(file: Misskey.entities.DriveFile) {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ text: file.isSensitive ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
+ });
+
+ if (canceled) return;
+
os.apiWithDialog('drive/files/update', {
fileId: file.id,
isSensitive: !file.isSensitive,
@@ -272,8 +302,8 @@ function toggleSensitive(file: Misskey.entities.DriveFile) {
}
// MediaControl: Video State
-const videoEl = shallowRef<HTMLVideoElement>();
-const playerEl = shallowRef<HTMLDivElement>();
+const videoEl = useTemplateRef('videoEl');
+const playerEl = useTemplateRef('playerEl');
const isHoverring = ref(false);
const controlsShowing = computed(() => {
if (!oncePlayed.value) return true;
@@ -357,8 +387,8 @@ function toggleFullscreen() {
function togglePictureInPicture() {
if (videoEl.value) {
- if (document.pictureInPictureElement) {
- document.exitPictureInPicture();
+ if (window.document.pictureInPictureElement) {
+ window.document.exitPictureInPicture();
} else {
videoEl.value.requestPictureInPicture();
}
@@ -475,7 +505,7 @@ onDeactivated(() => {
elapsedTimeMs.value = 0;
durationMs.value = 0;
bufferedEnd.value = 0;
- hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore');
+ hide.value = (prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.video.isSensitive && prefer.s.nsfw !== 'ignore');
stopVideoElWatch();
onceInit = false;
if (mediaTickFrameId) {
diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue
index ac50d82a63..de8f39e7a8 100644
--- a/packages/frontend/src/components/MkMention.vue
+++ b/packages/frontend/src/components/MkMention.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<img :class="$style.icon" :src="avatarUrl" alt="">
<span>
<span>@{{ username }}</span>
- <span v-if="(host != localHost) || defaultStore.state.showFullAcct" :class="$style.host">@{{ toUnicode(host) }}</span>
+ <span v-if="(host != localHost)" :class="$style.host">@{{ toUnicode(host) }}</span>
</span>
</MkA>
</template>
@@ -17,10 +17,10 @@ SPDX-License-Identifier: AGPL-3.0-only
import { toUnicode } from 'punycode.js';
import { computed } from 'vue';
import { host as localHost } from '@@/js/config.js';
-import { $i } from '@/account.js';
-import { defaultStore } from '@/store.js';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
-import { MkABehavior } from '@/components/global/MkA.vue';
+import type { MkABehavior } from '@/components/global/MkA.vue';
+import { $i } from '@/i.js';
+import { getStaticImageUrl } from '@/utility/media-proxy.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
username: string;
@@ -36,7 +36,7 @@ const isMe = $i && (
`@${props.username}@${toUnicode(props.host)}` === `@${$i.username}@${toUnicode(localHost)}`.toLowerCase()
);
-const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar
+const avatarUrl = computed(() => prefer.s.disableShowingAnimatedImages || prefer.s.dataSaver.avatar
? getStaticImageUrl(`/avatar/@${props.username}@${props.host}`)
: `/avatar/@${props.username}@${props.host}`,
);
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index 086573ba6d..f7cd72b6c6 100644
--- a/packages/frontend/src/components/MkMenu.child.vue
+++ b/packages/frontend/src/components/MkMenu.child.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, provide, shallowRef, watch } from 'vue';
+import { nextTick, onMounted, onUnmounted, provide, useTemplateRef, watch } from 'vue';
import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js';
@@ -28,7 +28,7 @@ const emit = defineEmits<{
provide('isNestingMenu', true);
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
const align = 'left';
const SCROLLBAR_THICKNESS = 16;
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index b0a1b80210..c688cb4b1f 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.center]: align === 'center',
[$style.big]: big,
[$style.asDrawer]: asDrawer,
+ [$style.widthSpecified]: width != null,
}"
@focusin.passive.stop="() => {}"
>
@@ -29,12 +30,19 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<template v-for="item in (items2 ?? [])">
<div v-if="item.type === 'divider'" role="separator" tabindex="-1" :class="$style.divider"></div>
+
<span v-else-if="item.type === 'label'" role="menuitem" tabindex="-1" :class="[$style.label, $style.item]">
<span style="opacity: 0.7;">{{ item.text }}</span>
</span>
+
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
<span><MkEllipsis/></span>
</span>
+
+ <div v-else-if="item.type === 'component'" role="menuitem" tabindex="-1" :class="[$style.componentItem]">
+ <component :is="item.component" v-bind="item.props"/>
+ </div>
+
<MkA
v-else-if="item.type === 'link'"
role="menuitem"
@@ -48,10 +56,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<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>
+ <div :class="$style.item_content_text">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</MkA>
+
<a
v-else-if="item.type === 'a'"
role="menuitem"
@@ -67,10 +79,14 @@ 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>
+ <div :class="$style.item_content_text">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</a>
+
<button
v-else-if="item.type === 'user'"
role="menuitem"
@@ -85,6 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</button>
+
<button
v-else-if="item.type === 'switch'"
role="menuitemcheckbox"
@@ -98,10 +115,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<MkSwitchButton v-else :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
<div :class="$style.item_content">
- <span :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">{{ item.text }}</span>
+ <div :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<MkSwitchButton v-if="item.icon" :class="[$style.switchButton, $style.caret]" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
</div>
</button>
+
<button
v-else-if="item.type === 'radio'"
role="menuitem"
@@ -114,10 +135,14 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
<div :class="$style.item_content">
- <span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
+ <div :class="$style.item_content_text" style="pointer-events: none;">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
</div>
</button>
+
<button
v-else-if="item.type === 'radioOption'"
role="menuitemradio"
@@ -131,9 +156,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="[$style.radioIcon, { [$style.radioChecked]: unref(item.active) }]"></span>
</div>
<div :class="$style.item_content">
- <span :class="$style.item_content_text">{{ item.text }}</span>
+ <div :class="$style.item_content_text">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
</div>
</button>
+
<button
v-else-if="item.type === 'parent'"
role="menuitem"
@@ -145,12 +174,17 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
<div :class="$style.item_content">
- <span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
+ <div :class="$style.item_content_text" style="pointer-events: none;">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
</div>
</button>
+
<button
- v-else role="menuitem"
+ v-else
+ role="menuitem"
tabindex="0"
:class="['_button', $style.item, { [$style.danger]: item.danger, [$style.active]: unref(item.active) }]"
@click.prevent="unref(item.active) ? close(false) : clicked(item.action, $event)"
@@ -160,11 +194,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
<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>
+ <div :class="$style.item_content_text">
+ <div :class="$style.item_content_text_title">{{ item.text }}</div>
+ <div v-if="item.caption" :class="$style.item_content_text_caption">{{ item.caption }}</div>
+ </div>
<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</button>
</template>
+
<span v-if="items2 == null || items2.length === 0" tabindex="-1" :class="[$style.none, $style.item]">
<span>{{ i18n.ts.none }}</span>
</span>
@@ -176,15 +214,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
-import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } from 'vue';
+import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch, shallowRef } from 'vue';
+import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
+import type { Keymap } from '@/utility/hotkey.js';
import MkSwitchButton from '@/components/MkSwitch.button.vue';
-import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { isTouchUsing } from '@/scripts/touch.js';
-import { type Keymap } from '@/scripts/hotkey.js';
-import { isFocusable } from '@/scripts/focus.js';
-import { getNodeOrNull } from '@/scripts/get-dom-node-or-null.js';
+import { isTouchUsing } from '@/utility/touch.js';
+import { isFocusable } from '@/utility/focus.js';
+import { getNodeOrNull } from '@/utility/get-dom-node-or-null.js';
const childrenCache = new WeakMap<MenuParent, MenuItem[]>();
</script>
@@ -209,11 +247,11 @@ const big = isTouchUsing;
const isNestingMenu = inject<boolean>('isNestingMenu', false);
-const itemsEl = shallowRef<HTMLElement>();
+const itemsEl = useTemplateRef('itemsEl');
const items2 = ref<InnerMenuItem[]>();
-const child = shallowRef<InstanceType<typeof XChild>>();
+const child = useTemplateRef('child');
const keymap = {
'up|k|shift+tab': {
@@ -254,7 +292,7 @@ watch(() => props.items, () => {
});
const childMenu = ref<MenuItem[] | null>();
-const childTarget = shallowRef<HTMLElement | null>();
+const childTarget = shallowRef<HTMLElement>();
function closeChild() {
childMenu.value = null;
@@ -355,10 +393,10 @@ function switchItem(item: MenuSwitch & { ref: any }) {
function focusUp() {
if (disposed) return;
- if (!itemsEl.value?.contains(document.activeElement)) return;
+ if (!itemsEl.value?.contains(window.document.activeElement)) return;
const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable);
- const activeIndex = focusableElements.findIndex(el => el === document.activeElement);
+ const activeIndex = focusableElements.findIndex(el => el === window.document.activeElement);
const targetIndex = (activeIndex !== -1 && activeIndex !== 0) ? (activeIndex - 1) : (focusableElements.length - 1);
const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value;
@@ -367,10 +405,10 @@ function focusUp() {
function focusDown() {
if (disposed) return;
- if (!itemsEl.value?.contains(document.activeElement)) return;
+ if (!itemsEl.value?.contains(window.document.activeElement)) return;
const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable);
- const activeIndex = focusableElements.findIndex(el => el === document.activeElement);
+ const activeIndex = focusableElements.findIndex(el => el === window.document.activeElement);
const targetIndex = (activeIndex !== -1 && activeIndex !== (focusableElements.length - 1)) ? (activeIndex + 1) : 0;
const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value;
@@ -397,9 +435,9 @@ const onGlobalMousedown = (ev: MouseEvent) => {
const setupHandlers = () => {
if (!isNestingMenu) {
- document.addEventListener('focusin', onGlobalFocusin, { passive: true });
+ window.document.addEventListener('focusin', onGlobalFocusin, { passive: true });
}
- document.addEventListener('mousedown', onGlobalMousedown, { passive: true });
+ window.document.addEventListener('mousedown', onGlobalMousedown, { passive: true });
};
let disposed = false;
@@ -407,9 +445,9 @@ let disposed = false;
const disposeHandlers = () => {
disposed = true;
if (!isNestingMenu) {
- document.removeEventListener('focusin', onGlobalFocusin);
+ window.document.removeEventListener('focusin', onGlobalFocusin);
}
- document.removeEventListener('mousedown', onGlobalMousedown);
+ window.document.removeEventListener('mousedown', onGlobalMousedown);
};
onMounted(() => {
@@ -435,6 +473,12 @@ onBeforeUnmount(() => {
}
}
+ &:not(.widthSpecified) {
+ > .menu {
+ max-width: 400px;
+ }
+ }
+
&.big:not(.asDrawer) {
> .menu {
min-width: 230px;
@@ -558,11 +602,11 @@ onBeforeUnmount(() => {
}
&.danger {
- --menuFg: #ff2a2a;
+ --menuFg: var(--MI_THEME-error);
--menuHoverFg: #fff;
- --menuHoverBg: #ff4242;
+ --menuHoverBg: var(--MI_THEME-error);
--menuActiveFg: #fff;
- --menuActiveBg: #d42e2e;
+ --menuActiveBg: hsl(from var(--MI_THEME-error) h s calc(l - 10));
}
&.radio {
@@ -604,10 +648,19 @@ onBeforeUnmount(() => {
.item_content_text {
max-width: calc(100vw - 4rem);
+}
+
+.item_content_text_title {
text-overflow: ellipsis;
overflow: hidden;
}
+.item_content_text_caption {
+ text-wrap: auto;
+ font-size: 85%;
+ opacity: 0.7;
+}
+
.switchButton {
margin-left: -2px;
--height: 1.35em;
diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue
index 7ea585ecc2..98bd471438 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('--MI_THEME-accent'));
+const accent = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-accent'));
const color = accent.toRgbString();
function draw(): void {
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index a446dad0ab..b5c93df4ed 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -42,14 +42,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, shallowRef, computed } from 'vue';
+import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, useTemplateRef, computed } from 'vue';
+import type { Keymap } from '@/utility/hotkey.js';
import * as os from '@/os.js';
-import { isTouchUsing } from '@/scripts/touch.js';
-import { defaultStore } from '@/store.js';
-import { deviceKind } from '@/scripts/device-kind.js';
-import { type Keymap } from '@/scripts/hotkey.js';
-import { focusTrap } from '@/scripts/focus-trap.js';
-import { focusParent } from '@/scripts/focus.js';
+import { isTouchUsing } from '@/utility/touch.js';
+import { deviceKind } from '@/utility/device-kind.js';
+import { focusTrap } from '@/utility/focus-trap.js';
+import { focusParent } from '@/utility/focus.js';
+import { prefer } from '@/preferences.js';
function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === 'BODY') return null;
@@ -100,13 +100,13 @@ const maxHeight = ref<number>();
const fixed = ref(false);
const transformOrigin = ref('center');
const showing = ref(true);
-const modalRootEl = shallowRef<HTMLElement>();
-const content = shallowRef<HTMLElement>();
+const modalRootEl = useTemplateRef('modalRootEl');
+const content = useTemplateRef('content');
const zIndex = os.claimZIndex(props.zPriority);
const useSendAnime = ref(false);
const type = computed<ModalTypes>(() => {
if (props.preferType === 'auto') {
- if ((defaultStore.state.menuStyle === 'drawer') || (defaultStore.state.menuStyle === 'auto' && isTouchUsing && deviceKind === 'smartphone')) {
+ if ((prefer.s.menuStyle === 'drawer') || (prefer.s.menuStyle === 'auto' && isTouchUsing && deviceKind === 'smartphone')) {
return 'drawer';
} else {
return props.src != null ? 'popup' : 'dialog';
@@ -117,7 +117,7 @@ const type = computed<ModalTypes>(() => {
});
const isEnableBgTransparent = computed(() => props.transparentBg && (type.value === 'popup'));
const transitionName = computed((() =>
- defaultStore.state.animation
+ prefer.s.animation
? useSendAnime.value
? 'send'
: type.value === 'drawer'
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index f06cfffee4..19989e375b 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
+import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue';
import MkModal from './MkModal.vue';
const props = withDefaults(defineProps<{
@@ -47,9 +47,9 @@ const emit = defineEmits<{
(event: 'esc'): void;
}>();
-const modal = shallowRef<InstanceType<typeof MkModal>>();
-const rootEl = shallowRef<HTMLElement>();
-const headerEl = shallowRef<HTMLElement>();
+const modal = useTemplateRef('modal');
+const rootEl = useTemplateRef('rootEl');
+const headerEl = useTemplateRef('headerEl');
const bodyWidth = ref(0);
const bodyHeight = ref(0);
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 2f0e39835c..fed3dafeea 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, [$style.skipRender]: defaultStore.state.skipNoteRender }]"
+ :class="[$style.root, { [$style.showActionsOnlyHover]: prefer.s.showNoteActionsOnlyHover, [$style.skipRender]: prefer.s.skipNoteRender }]"
:tabindex="isDeleted ? '-1' : '0'"
>
<div v-if="appearNote.reply && inReplyToCollapsed" :class="$style.collapsedInReplyTo">
@@ -86,12 +86,13 @@ SPDX-License-Identifier: AGPL-3.0-only
:enableEmojiMenuReaction="true"
:isAnim="allowAnim"
:isBlock="true"
+ class="_selectable"
/>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else-if="translation">
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
- <Mfm :text="translation.text" :isBlock="true" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
+ <Mfm :text="translation.text" :isBlock="true" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
</div>
</div>
<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
@@ -159,9 +160,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<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>
+ <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
</button>
- <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
+ <button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
<i class="ti ti-paperclip"></i>
</button>
<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown.prevent="showMenu()">
@@ -206,14 +207,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
+import { computed, inject, onMounted, ref, useTemplateRef, watch, provide } from 'vue';
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 { Ref } from 'vue';
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
import type { MenuItem } from '@/types/menu.js';
+import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
+import type { Keymap } from '@/utility/hotkey.js';
import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
@@ -226,35 +230,36 @@ import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
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 { pleaseLogin } from '@/utility/please-login.js';
+import { checkWordMute } from '@/utility/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';
-import * as sound from '@/scripts/sound.js';
-import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
-import { defaultStore, noteViewInterruptors } from '@/store.js';
-import { reactionPicker } from '@/scripts/reaction-picker.js';
-import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
-import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
-import { $i } from '@/account.js';
+import * as sound from '@/utility/sound.js';
+import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
+import { reactionPicker } from '@/utility/reaction-picker.js';
+import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
+import { checkAnimationFromMfm } from '@/utility/check-animated-mfm.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
-import { getAbuseNoteMenu, getCopyNoteLinkMenu, 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';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { getNoteSummary } from '@/scripts/get-note-summary.js';
+import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
+import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js';
+import { useNoteCapture } from '@/use/use-note-capture.js';
+import { deepClone } from '@/utility/clone.js';
+import { useTooltip } from '@/use/use-tooltip.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { getNoteSummary } from '@/utility/get-note-summary.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
-import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
+import { showMovedDialog } from '@/utility/show-moved-dialog.js';
import { useRouter } from '@/router/supplier.js';
-import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/scripts/boost-quote.js';
+import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/utility/boost-quote.js';
import { isEnabledUrlPreview } from '@/instance.js';
-import { type Keymap } from '@/scripts/hotkey.js';
-import { focusPrev, focusNext } from '@/scripts/focus.js';
-import { getAppearNote } from '@/scripts/get-appear-note.js';
+import { focusPrev, focusNext } from '@/utility/focus.js';
+import { getAppearNote } from '@/utility/get-appear-note.js';
+import { prefer } from '@/preferences.js';
+import { getPluginHandlers } from '@/plugin.js';
+import { DI } from '@/di.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@@ -265,7 +270,7 @@ const props = withDefaults(defineProps<{
mock: false,
});
-provide('mock', props.mock);
+provide(DI.mock, props.mock);
const emit = defineEmits<{
(ev: 'reaction', emoji: string): void;
@@ -289,6 +294,7 @@ function noteclick(id: string) {
}
// plugin
+const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result: Misskey.entities.Note | null = deepClone(note.value);
@@ -309,17 +315,17 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note.value);
-const rootEl = shallowRef<HTMLElement>();
-const menuButton = shallowRef<HTMLElement>();
-const menuVersionsButton = shallowRef<HTMLElement>();
-const renoteButton = shallowRef<HTMLElement>();
-const renoteTime = shallowRef<HTMLElement>();
-const reactButton = shallowRef<HTMLElement>();
-const quoteButton = shallowRef<HTMLElement>();
-const clipButton = shallowRef<HTMLElement>();
-const likeButton = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
+const menuButton = useTemplateRef('menuButton');
+const renoteButton = useTemplateRef('renoteButton');
+const renoteTime = useTemplateRef('renoteTime');
+const reactButton = useTemplateRef('reactButton');
+const clipButton = useTemplateRef('clipButton');
+const menuVersionsButton = useTemplateRef('menuVersionsButton');
+const quoteButton = useTemplateRef('quoteButton');
+const likeButton = useTemplateRef('likeButton');
const appearNote = computed(() => getAppearNote(note.value));
-const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
+const galleryEl = useTemplateRef('galleryEl');
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(defaultStore.state.uncollapseCW);
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
@@ -330,13 +336,13 @@ const isDeleted = ref(false);
const renoted = ref(false);
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true));
-const showSoftWordMutedWord = computed(() => defaultStore.state.showSoftWordMutedWord);
+const showSoftWordMutedWord = computed(() => prefer.s.showSoftWordMutedWord);
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false);
-const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
+const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
const renoteCollapsed = ref(
- defaultStore.state.collapseRenotes && isRenote && (
+ prefer.s.collapseRenotes && isRenote && (
($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
(appearNote.value.myReaction != null)
),
@@ -401,7 +407,7 @@ const keymap = {
},
'c': () => {
if (renoteCollapsed.value) return;
- if (!defaultStore.state.showClipButtonInNoteFooter) return;
+ if (!prefer.s.showClipButtonInNoteFooter) return;
clip();
},
'o': () => {
@@ -699,7 +705,7 @@ function react(viaKeyboard = false): void {
override: defaultLike.value,
});
const el = reactButton.value;
- if (el) {
+ if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -709,7 +715,16 @@ function react(viaKeyboard = false): void {
}
} else {
blur();
- reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+ reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
+ if (prefer.s.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
if (props.mock) {
@@ -781,7 +796,7 @@ function onContextmenu(ev: MouseEvent): void {
if (ev.target && isLink(ev.target as HTMLElement)) return;
if (window.getSelection()?.toString() !== '') return;
- if (defaultStore.state.useReactionPickerForContextMenu) {
+ if (prefer.s.useReactionPickerForContextMenu) {
ev.preventDefault();
react();
} else {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 1bbd3ba5d0..531232f86a 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -101,13 +101,14 @@ SPDX-License-Identifier: AGPL-3.0-only
:enableEmojiMenuReaction="true"
:isAnim="allowAnim"
:isBlock="true"
+ class="_selectable"
/>
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<div v-else-if="translation">
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
- <Mfm :text="translation.text" :isBlock="true" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
+ <Mfm :text="translation.text" :isBlock="true" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
</div>
</div>
<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
@@ -169,9 +170,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<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>
+ <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
</button>
- <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()">
+ <button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()">
<i class="ti ti-paperclip"></i>
</button>
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="showMenu()">
@@ -240,12 +241,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue';
+import { computed, inject, onMounted, provide, ref, useTemplateRef } from 'vue';
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 { computeMergedCw } from '@@/js/compute-merged-cw.js';
+import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
+import type { Paging } from '@/components/MkPagination.vue';
+import type { Keymap } from '@/utility/hotkey.js';
import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
@@ -256,41 +260,41 @@ import MkPoll from '@/components/MkPoll.vue';
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
-import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js';
-import { checkWordMute } from '@/scripts/check-word-mute.js';
+import { pleaseLogin } from '@/utility/please-login.js';
+import { checkWordMute } from '@/utility/check-word-mute.js';
import { userPage } from '@/filters/user.js';
import { notePage } from '@/filters/note.js';
import number from '@/filters/number.js';
import * as os from '@/os.js';
-import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
-import * as sound from '@/scripts/sound.js';
-import { defaultStore, noteViewInterruptors } from '@/store.js';
-import { reactionPicker } from '@/scripts/reaction-picker.js';
-import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
-import { $i } from '@/account.js';
+import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
+import * as sound from '@/utility/sound.js';
+import { reactionPicker } from '@/utility/reaction-picker.js';
+import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.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';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
+import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/utility/get-note-menu.js';
+import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js';
+import { checkAnimationFromMfm } from '@/utility/check-animated-mfm.js';
+import { useNoteCapture } from '@/use/use-note-capture.js';
+import { deepClone } from '@/utility/clone.js';
+import { useTooltip } from '@/use/use-tooltip.js';
+import { claimAchievement } from '@/utility/achievements.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
-import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
+import { showMovedDialog } from '@/utility/show-moved-dialog.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkPagination, { type Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue';
import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/scripts/boost-quote.js';
import { isEnabledUrlPreview } from '@/instance.js';
-import { getAppearNote } from '@/scripts/get-appear-note.js';
-import { type Keymap } from '@/scripts/hotkey.js';
+import { getAppearNote } from '@/utility/get-appear-note.js';
+import { prefer } from '@/preferences.js';
+import { getPluginHandlers } from '@/plugin.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
+ initialTab?: string;
expandAllCws?: boolean;
- initialTab: string;
}>(), {
initialTab: 'replies',
});
@@ -300,6 +304,7 @@ const inChannel = inject('inChannel', null);
const note = ref(deepClone(props.note));
// plugin
+const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result: Misskey.entities.Note | null = deepClone(note.value);
@@ -320,17 +325,17 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note.value);
-const rootEl = shallowRef<HTMLElement>();
-const menuButton = shallowRef<HTMLElement>();
-const menuVersionsButton = shallowRef<HTMLElement>();
-const renoteButton = shallowRef<HTMLElement>();
-const renoteTime = shallowRef<HTMLElement>();
-const reactButton = shallowRef<HTMLElement>();
-const quoteButton = shallowRef<HTMLElement>();
-const clipButton = shallowRef<HTMLElement>();
-const likeButton = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
+const menuButton = useTemplateRef('menuButton');
+const renoteButton = useTemplateRef('renoteButton');
+const renoteTime = useTemplateRef('renoteTime');
+const reactButton = useTemplateRef('reactButton');
+const clipButton = useTemplateRef('clipButton');
+const menuVersionsButton = useTemplateRef('menuVersionsButton');
+const quoteButton = useTemplateRef('quoteButton');
+const likeButton = useTemplateRef('likeButton');
const appearNote = computed(() => getAppearNote(note.value));
-const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
+const galleryEl = useTemplateRef('galleryEl');
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(defaultStore.state.uncollapseCW);
const isDeleted = ref(false);
@@ -341,8 +346,8 @@ const translating = ref(false);
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
-const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
-const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
+const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm);
+const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance);
const conversation = ref<Misskey.entities.Note[]>([]);
const replies = ref<Misskey.entities.Note[]>([]);
const quotes = ref<Misskey.entities.Note[]>([]);
@@ -380,7 +385,7 @@ const keymap = {
'q': () => { if (canRenote.value && !renoted.value && !renoting) renote(defaultStore.state.visibilityOnBoost); },
'm': () => showMenu(),
'c': () => {
- if (!defaultStore.state.showClipButtonInNoteFooter) return;
+ if (!prefer.s.showClipButtonInNoteFooter) return;
clip();
},
'o': () => galleryEl.value?.openGallery(),
@@ -644,7 +649,7 @@ function react(): void {
override: defaultLike.value,
});
const el = reactButton.value;
- if (el) {
+ if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -654,7 +659,16 @@ function react(): void {
}
} else {
blur();
- reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+ reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
+ if (prefer.s.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
misskeyApi('notes/reactions/create', {
@@ -728,7 +742,7 @@ function onContextmenu(ev: MouseEvent): void {
if (ev.target && isLink(ev.target as HTMLElement)) return;
if (window.getSelection()?.toString() !== '') return;
- if (defaultStore.state.useReactionPickerForContextMenu) {
+ if (prefer.s.useReactionPickerForContextMenu) {
ev.preventDefault();
react();
} else {
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index 3b15242685..42b61b841a 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -41,9 +41,9 @@ import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js';
-import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
+import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js';
import { popupMenu } from '@/os.js';
-import { defaultStore } from '@/store.js';
+import { DI } from '@/di.js';
const props = defineProps<{
note: Misskey.entities.Note & {
@@ -61,7 +61,7 @@ async function menuVersions(viaKeyboard = false): Promise<void> {
}).then(focus).finally(cleanup);
}
-const mock = inject<boolean>('mock', false);
+const mock = inject(DI.mock, false);
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/components/MkNoteMediaGrid.vue b/packages/frontend/src/components/MkNoteMediaGrid.vue
index bf105c3c27..764d9f6a32 100644
--- a/packages/frontend/src/components/MkNoteMediaGrid.vue
+++ b/packages/frontend/src/components/MkNoteMediaGrid.vue
@@ -4,51 +4,51 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
- <template v-for="file in note.files">
- <div
- v-if="(((
- (defaultStore.state.nsfw === 'force' || file.isSensitive) &&
- defaultStore.state.nsfw !== 'ignore'
- ) || (defaultStore.state.dataSaver.media && file.type.startsWith('image/'))) &&
- !showingFiles.has(file.id)
- )"
- :class="[$style.filePreview, { [$style.square]: square }]"
- @click="showingFiles.add(file.id)"
- >
- <MkDriveFileThumbnail
- :file="file"
- fit="cover"
- :highlightWhenSensitive="defaultStore.state.highlightSensitiveMedia"
- :forceBlurhash="true"
- :large="true"
- :class="$style.file"
- />
- <div :class="$style.sensitive">
- <div>
- <div v-if="file.isSensitive"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media && file.size ? ` (${bytes(file.size)})` : '' }}</div>
- <div v-else><i class="ti ti-photo"></i> {{ defaultStore.state.dataSaver.media && file.size ? bytes(file.size) : i18n.ts.image }}</div>
- <div>{{ i18n.ts.clickToShow }}</div>
- </div>
+<template v-for="file in note.files">
+ <div
+ v-if="(((
+ (prefer.s.nsfw === 'force' || file.isSensitive) &&
+ prefer.s.nsfw !== 'ignore'
+ ) || (prefer.s.dataSaver.media && file.type.startsWith('image/'))) &&
+ !showingFiles.has(file.id)
+ )"
+ :class="[$style.filePreview, { [$style.square]: square }]"
+ @click="showingFiles.add(file.id)"
+ >
+ <MkDriveFileThumbnail
+ :file="file"
+ fit="cover"
+ :highlightWhenSensitive="prefer.s.highlightSensitiveMedia"
+ :forceBlurhash="true"
+ :large="true"
+ :class="$style.file"
+ />
+ <div :class="$style.sensitive">
+ <div>
+ <div v-if="file.isSensitive"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ prefer.s.dataSaver.media && file.size ? ` (${bytes(file.size)})` : '' }}</div>
+ <div v-else><i class="ti ti-photo"></i> {{ prefer.s.dataSaver.media && file.size ? bytes(file.size) : i18n.ts.image }}</div>
+ <div>{{ i18n.ts.clickToShow }}</div>
</div>
</div>
- <MkA v-else :class="[$style.filePreview, { [$style.square]: square }]" :to="notePage(note)">
- <MkDriveFileThumbnail
- :file="file"
- fit="cover"
- :highlightWhenSensitive="defaultStore.state.highlightSensitiveMedia"
- :large="true"
- :class="$style.file"
- />
- </MkA>
- </template>
+ </div>
+ <MkA v-else :class="[$style.filePreview, { [$style.square]: square }]" :to="notePage(note)">
+ <MkDriveFileThumbnail
+ :file="file"
+ fit="cover"
+ :highlightWhenSensitive="prefer.s.highlightSensitiveMedia"
+ :large="true"
+ :class="$style.file"
+ />
+ </MkA>
+</template>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
+import * as Misskey from 'misskey-js';
import { notePage } from '@/filters/note.js';
import { i18n } from '@/i18n.js';
-import * as Misskey from 'misskey-js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
import bytes from '@/filters/bytes.js';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index c121758387..3d1884b6e4 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -93,24 +93,22 @@ import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
import MkCwButton from '@/components/MkCwButton.vue';
import { notePage } from '@/filters/note.js';
import * as os from '@/os.js';
-import * as sound from '@/scripts/sound.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import * as sound from '@/utility/sound.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { userPage } from '@/filters/user.js';
-import { checkWordMute } from '@/scripts/check-word-mute.js';
+import { checkWordMute } from '@/utility/check-word-mute.js';
import { defaultStore } from '@/store.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 { pleaseLogin, type OpenOnRemoteOptions } from '@/utility/please-login.js';
+import { showMovedDialog } from '@/utility/show-moved-dialog.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
-import { reactionPicker } from '@/scripts/reaction-picker.js';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { getNoteMenu } from '@/scripts/get-note-menu.js';
-import { useNoteCapture } from '@/scripts/use-note-capture.js';
-import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/scripts/boost-quote.js';
-
-const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
+import { reactionPicker } from '@/utility/reaction-picker.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { getNoteMenu } from '@/utility/get-note-menu.js';
+import { useNoteCapture } from '@/utility/use-note-capture.js';
+import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/utility/boost-quote.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@@ -124,6 +122,8 @@ const props = withDefaults(defineProps<{
depth: 1,
});
+const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
+
const el = shallowRef<HTMLElement>();
const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
const translation = ref<any>(null);
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index bd157d0b14..928a143a72 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad">
<template #empty>
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.noNotes }}</div>
</div>
</template>
@@ -32,9 +32,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, shallowRef } from 'vue';
+import { defineAsyncComponent, useTemplateRef } from 'vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
import { defaultStore } from '@/store.js';
@@ -51,7 +52,7 @@ const props = defineProps<{
disableAutoLoad?: boolean;
}>();
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
defineExpose({
pagingComponent,
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index a910151e42..8e86d67ab9 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -7,7 +7,7 @@ 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', 'exportCompleted', 'login', 'scheduledNoteFailed'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
+ <MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'createToken', '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>
@@ -27,6 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
[$style.t_login]: notification.type === 'login',
+ [$style.t_createToken]: notification.type === 'createToken',
[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
[$style.t_pollEnded]: notification.type === 'edited',
[$style.t_roleAssigned]: notification.type === 'scheduledNoteFailed',
@@ -44,6 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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>
+ <i v-else-if="notification.type === 'createToken'" class="ti ti-key"></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>
@@ -68,6 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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 === 'createToken'">{{ i18n.ts._notification.createToken }}</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>
@@ -117,6 +120,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
{{ i18n.ts.showFile }}
</MkA>
+ <MkA v-else-if="notification.type === 'createToken'" :class="$style.text" to="/settings/apps">
+ <Mfm :text="i18n.tsx._notification.createTokenDescription({ text: i18n.ts.manageAccessTokens })"/>
+ </MkA>
<div v-else-if="notification.type === 'scheduledNoteFailed'" :class="$style.text">
{{ notification.reason }}
</div>
@@ -188,16 +194,16 @@ import * as Misskey from 'misskey-js';
import { UserDetailed } from 'misskey-js/autogen/models.js';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue';
-import { getNoteSummary } from '@/scripts/get-note-summary.js';
+import { getNoteSummary } from '@/utility/get-note-summary.js';
import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { signinRequired } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { ensureSignin } from '@/i.js';
import { infoImageUrl } from '@/instance.js';
import MkFollowButton from '@/components/MkFollowButton.vue';
-const $i = signinRequired();
+const $i = ensureSignin();
const props = withDefaults(defineProps<{
notification: Misskey.entities.Notification;
@@ -408,6 +414,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none;
}
+.t_createToken {
+ padding: 3px;
+ background: var(--eventOther);
+ pointer-events: none;
+}
+
.tail {
flex: 1;
min-width: 0;
diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue
index d07827d11a..d074dceb2f 100644
--- a/packages/frontend/src/components/MkNotificationSelectWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue
@@ -30,15 +30,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, Ref, shallowRef } from 'vue';
+import { ref, useTemplateRef } from 'vue';
+import { notificationTypes } from '@@/js/const.js';
import MkSwitch from './MkSwitch.vue';
import MkInfo from './MkInfo.vue';
import MkButton from './MkButton.vue';
+import type { Ref } from 'vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
-import { notificationTypes } from '@@/js/const.js';
import { i18n } from '@/i18n.js';
-type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>
+type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>;
const emit = defineEmits<{
(ev: 'done', v: { excludeTypes: string[] }): void,
@@ -51,7 +52,7 @@ const props = withDefaults(defineProps<{
excludeTypes: () => [],
});
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
const typesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as TypesMap);
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 51c4ea7ce4..4a1377655f 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination ref="pagingComponent" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.noNotifications }}</div>
</div>
</template>
@@ -24,17 +24,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated } from 'vue';
+import { defineAsyncComponent, onUnmounted, onDeactivated, onMounted, computed, useTemplateRef, onActivated } from 'vue';
+import * as Misskey from 'misskey-js';
+import type { notificationTypes } from '@@/js/const.js';
import MkPagination from '@/components/MkPagination.vue';
import XNotification from '@/components/MkNotification.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
-import { notificationTypes } from '@@/js/const.js';
import { infoImageUrl } from '@/instance.js';
-import { defaultStore } from '@/store.js';
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
-import * as Misskey from 'misskey-js';
+import { prefer } from '@/preferences.js';
const MkNote = defineAsyncComponent(() =>
(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
@@ -46,9 +46,9 @@ const props = defineProps<{
excludeTypes?: typeof notificationTypes[number][];
}>();
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
-const pagination = computed(() => defaultStore.reactiveState.useGroupedNotifications.value ? {
+const pagination = computed(() => prefer.r.useGroupedNotifications.value ? {
endpoint: 'i/notifications-grouped' as const,
limit: 20,
params: computed(() => ({
@@ -64,7 +64,7 @@ const pagination = computed(() => defaultStore.reactiveState.useGroupedNotificat
function onNotification(notification) {
const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false;
- if (isMuted || document.visibilityState === 'visible') {
+ if (isMuted || window.document.visibilityState === 'visible') {
useStream().send('readNotification');
}
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index b978a71c15..7292b28f25 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
+import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
@@ -22,7 +22,7 @@ const props = withDefaults(defineProps<{
maxHeight: 200,
});
-const content = shallowRef<HTMLElement>();
+const content = useTemplateRef('content');
const omitted = ref(false);
const ignoreOmit = ref(false);
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 3ff4cc215c..56592dcf5c 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -22,28 +22,30 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</template>
- <div ref="contents" :class="$style.root" style="container-type: inline-size;">
- <RouterView :key="reloadCount" :router="windowRouter"/>
+ <div :class="$style.root">
+ <StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :key="reloadCount" :router="windowRouter"/>
+ <RouterView v-else :key="reloadCount" :router="windowRouter"/>
</div>
</MkWindow>
</template>
<script lang="ts" setup>
-import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
+import { computed, onMounted, onUnmounted, provide, ref, useTemplateRef } from 'vue';
import { url } from '@@/js/config.js';
-import { getScrollContainer } from '@@/js/scroll.js';
+import type { PageMetadata } from '@/page.js';
import MkUserName from './global/MkUserName.vue';
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 { useScrollPositionManager } from '@/nirax.js';
+import { popout as _popout } from '@/utility/popout.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { openingWindowsCount } from '@/os.js';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { useRouterFactory } from '@/router/supplier.js';
-import { mainRouter } from '@/router/main.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { createRouter, mainRouter } from '@/router.js';
+import { analytics } from '@/analytics.js';
+import { DI } from '@/di.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
initialPath: string;
@@ -53,15 +55,12 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const routerFactory = useRouterFactory();
-const windowRouter = routerFactory(props.initialPath);
+const windowRouter = createRouter(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: string; }[]>([{
- path: windowRouter.getCurrentPath(),
- key: windowRouter.getCurrentKey(),
+const windowEl = useTemplateRef('windowEl');
+const history = ref<{ path: string; }[]>([{
+ path: windowRouter.getCurrentFullPath(),
}]);
const buttonsLeft = computed(() => {
const buttons: Record<string, unknown>[] = [];
@@ -90,18 +89,36 @@ const buttonsRight = computed(() => {
});
const reloadCount = ref(0);
+function getSearchMarker(path: string) {
+ const hash = path.split('#')[1];
+ if (hash == null) return null;
+ return hash;
+}
+
+const searchMarkerId = ref<string | null>(getSearchMarker(props.initialPath));
+
windowRouter.addListener('push', ctx => {
- history.value.push({ path: ctx.path, key: ctx.key });
+ history.value.push({ path: ctx.fullPath });
});
windowRouter.addListener('replace', ctx => {
history.value.pop();
- history.value.push({ path: ctx.path, key: ctx.key });
+ history.value.push({ path: ctx.fullPath });
+});
+
+windowRouter.addListener('change', ctx => {
+ if (_DEV_) console.log('windowRouter: change', ctx.fullPath);
+ searchMarkerId.value = getSearchMarker(ctx.fullPath);
+ analytics.page({
+ path: ctx.fullPath,
+ title: ctx.fullPath,
+ });
});
windowRouter.init();
-provide('router', windowRouter);
+provide(DI.router, windowRouter);
+provide('inAppSearchMarkerId', searchMarkerId);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
@@ -124,20 +141,20 @@ const contextmenu = computed(() => ([{
icon: 'ti ti-external-link',
text: i18n.ts.openInNewTab,
action: () => {
- window.open(url + windowRouter.getCurrentPath(), '_blank', 'noopener');
+ window.open(url + windowRouter.getCurrentFullPath(), '_blank', 'noopener');
windowEl.value?.close();
},
}, {
icon: 'ti ti-link',
text: i18n.ts.copyLink,
action: () => {
- copyToClipboard(url + windowRouter.getCurrentPath());
+ copyToClipboard(url + windowRouter.getCurrentFullPath());
},
}]));
function back() {
history.value.pop();
- windowRouter.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
+ windowRouter.replace(history.value.at(-1)!.path);
}
function reload() {
@@ -149,18 +166,21 @@ function close() {
}
function expand() {
- mainRouter.push(windowRouter.getCurrentPath(), 'forcePage');
+ mainRouter.push(windowRouter.getCurrentFullPath(), 'forcePage');
windowEl.value?.close();
}
function popout() {
- _popout(windowRouter.getCurrentPath(), windowEl.value?.$el);
+ _popout(windowRouter.getCurrentFullPath(), windowEl.value?.$el);
windowEl.value?.close();
}
-useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter);
-
onMounted(() => {
+ analytics.page({
+ path: props.initialPath,
+ title: props.initialPath,
+ });
+
openingWindowsCount.value++;
if (openingWindowsCount.value >= 3) {
claimAchievement('open3windows');
@@ -178,9 +198,7 @@ defineExpose({
<style lang="scss" module>
.root {
- overscroll-behavior: contain;
-
- min-height: 100%;
+ height: 100%;
background: var(--MI_THEME-bg);
--MI-margin: var(--MI-marginHalf);
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index f37cb10f6d..71f7fc513b 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_fade_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_fade_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_fade_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_fade_leaveTo : ''"
mode="out-in"
>
<MkLoading v-if="fetching"/>
@@ -18,22 +18,22 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else-if="empty" key="_empty_" class="empty">
<slot name="empty">
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</slot>
</div>
- <div v-else ref="rootEl">
- <div v-show="pagination.reversed && more" key="_more_" class="_margin">
- <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMoreAhead : null" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary rounded @click="fetchMoreAhead">
+ <div v-else ref="rootEl" class="_gaps">
+ <div v-show="pagination.reversed && more" key="_more_">
+ <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMoreAhead : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMoreAhead">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
</div>
<slot :items="Array.from(items.values())" :fetching="fetching || moreFetching"></slot>
- <div v-show="!pagination.reversed && more" key="_more_" class="_margin">
- <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMore : null" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary rounded @click="fetchMore">
+ <div v-show="!pagination.reversed && more" key="_more_">
+ <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMore : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMore">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
@@ -43,15 +43,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
-import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch, type Ref } from 'vue';
+import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch, type Ref } from 'vue';
import * as Misskey from 'misskey-js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
-import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { MisskeyEntity } from '@/types/date-separated-list.js';
+import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible } from '@@/js/scroll.js';
+import type { ComputedRef } from 'vue';
+import type { MisskeyEntity } from '@/types/date-separated-list.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
+import { prefer } from '@/preferences.js';
const SECOND_FETCH_LIMIT = 30;
const TOLERANCE = 16;
@@ -74,8 +74,6 @@ export type Paging<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints>
reversed?: boolean;
offsetMode?: boolean | ComputedRef<boolean>;
-
- pageEl?: HTMLElement;
};
type MisskeyEntityMap = Map<string, MisskeyEntity>;
@@ -107,7 +105,7 @@ const emit = defineEmits<{
(ev: 'init'): void;
}>();
-const rootEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
// 遡り中かどうか
const backed = ref(false);
@@ -140,10 +138,9 @@ const empty = computed(() => items.value.size === 0);
const error = ref(false);
const {
enableInfiniteScroll,
-} = defaultStore.reactiveState;
+} = prefer.r;
-const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value);
-const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body);
+const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : window.document.body);
const visibility = useDocumentVisibility();
@@ -174,13 +171,13 @@ watch(rootEl, () => {
});
});
-watch([backed, contentEl], () => {
+watch([backed, rootEl], () => {
if (!backed.value) {
- if (!contentEl.value) return;
+ if (!rootEl.value) return;
scrollRemove.value = props.pagination.reversed
- ? onScrollBottom(contentEl.value, executeQueue, TOLERANCE)
- : onScrollTop(contentEl.value, (topVisible) => { if (topVisible) executeQueue(); }, TOLERANCE);
+ ? onScrollBottom(rootEl.value, executeQueue, TOLERANCE)
+ : onScrollTop(rootEl.value, (topVisible) => { if (topVisible) executeQueue(); }, TOLERANCE);
} else {
if (scrollRemove.value) scrollRemove.value();
scrollRemove.value = null;
@@ -360,7 +357,7 @@ const appearFetchMoreAhead = async (): Promise<void> => {
fetchMoreAppearTimeout();
};
-const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE);
+const isHead = (): boolean => isBackTop.value || (props.pagination.reversed ? isTailVisible : isHeadVisible)(rootEl.value!, TOLERANCE);
watch(visibility, () => {
if (visibility.value === 'hidden') {
@@ -375,7 +372,7 @@ watch(visibility, () => {
timerForSetPause = null;
} else {
isPausingUpdate = false;
- if (isTop()) {
+ if (isHead()) {
executeQueue();
}
}
@@ -387,16 +384,18 @@ watch(visibility, () => {
* ストリーミングから降ってきたアイテムはこれで追加する
* @param item アイテム
*/
-const prepend = (item: MisskeyEntity): void => {
+function prepend(item: MisskeyEntity): void {
if (items.value.size === 0) {
items.value.set(item.id, item);
fetching.value = false;
return;
}
- if (isTop() && !isPausingUpdate) unshiftItems([item]);
+ if (_DEV_) console.log(isHead(), isPausingUpdate);
+
+ if (isHead() && !isPausingUpdate) unshiftItems([item]);
else prependQueue(item);
-};
+}
/**
* 新着アイテムをitemsの先頭に追加し、displayLimitを適用する
@@ -458,7 +457,7 @@ onDeactivated(() => {
});
function toBottom() {
- scrollToBottom(contentEl.value!);
+ scrollToBottom(rootEl.value!);
}
onBeforeMount(() => {
diff --git a/packages/frontend/src/components/MkPasswordDialog.vue b/packages/frontend/src/components/MkPasswordDialog.vue
index e749725fea..2abf8669ed 100644
--- a/packages/frontend/src/components/MkPasswordDialog.vue
+++ b/packages/frontend/src/components/MkPasswordDialog.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :marginMin="20" :marginMax="28">
<div style="padding: 0 0 16px 0; text-align: center;">
- <img src="/fluent-emoji/1f510.png" alt="🔐" style="display: block; margin: 0 auto; width: 48px;">
+ <img src="/client-assets/locked_with_key_3d.png" alt="🔐" style="display: block; margin: 0 auto; width: 48px;">
<div style="margin-top: 16px;">{{ i18n.ts.authenticationRequiredToContinue }}</div>
</div>
@@ -39,14 +39,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const emit = defineEmits<{
(ev: 'done', v: { password: string; token: string | null; }): void;
@@ -54,8 +54,8 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
-const passwordInput = shallowRef<InstanceType<typeof MkInput>>();
+const dialog = useTemplateRef('dialog');
+const passwordInput = useTemplateRef('passwordInput');
const password = ref('');
const isBackupCode = ref(false);
const token = ref<string | null>(null);
diff --git a/packages/frontend/src/components/MkPolkadots.vue b/packages/frontend/src/components/MkPolkadots.vue
new file mode 100644
index 0000000000..285c4d0b79
--- /dev/null
+++ b/packages/frontend/src/components/MkPolkadots.vue
@@ -0,0 +1,40 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.root, accented ? $style.accented : null]"></div>
+</template>
+
+<script lang="ts" setup>
+const props = withDefaults(defineProps<{
+ accented?: boolean;
+}>(), {
+ accented: false,
+});
+</script>
+
+<style lang="scss" module>
+.root {
+ --c: var(--MI_THEME-divider);
+
+ &.accented {
+ --c: var(--MI_THEME-accent);
+ opacity: 0.5;
+ }
+
+ --dot-size: 2px;
+ --gap-size: 40px;
+ --offset: calc(var(--gap-size) / 2);
+
+ height: 200px;
+ margin-bottom: -200px;
+
+ background-image: linear-gradient(transparent 60%, transparent 100%), radial-gradient(var(--c) var(--dot-size), transparent var(--dot-size)), radial-gradient(var(--c) var(--dot-size), transparent var(--dot-size));
+ background-position: 0 0, 0 0, var(--offset) var(--offset);
+ background-size: 100% 100%, var(--gap-size) var(--gap-size), var(--gap-size) var(--gap-size);
+ mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
+ pointer-events: none;
+}
+</style>
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index f6218de4c8..ff83520acd 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -35,11 +35,11 @@ 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 type { OpenOnRemoteOptions } from '@/utility/please-login.js';
+import { sum } from '@/utility/array.js';
+import { pleaseLogin } from '@/utility/please-login.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue
index 3726ddf822..22fe189a63 100644
--- a/packages/frontend/src/components/MkPollEditor.vue
+++ b/packages/frontend/src/components/MkPollEditor.vue
@@ -58,8 +58,8 @@ import MkInput from './MkInput.vue';
import MkSelect from './MkSelect.vue';
import MkSwitch from './MkSwitch.vue';
import MkButton from './MkButton.vue';
-import { formatDateTimeString } from '@/scripts/format-time-string.js';
-import { addTime } from '@/scripts/time.js';
+import { formatDateTimeString } from '@/utility/format-time-string.js';
+import { addTime } from '@/utility/time.js';
import { i18n } from '@/i18n.js';
export type PollEditorModelValue = {
diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue
index f1b5ff4de0..be5927c536 100644
--- a/packages/frontend/src/components/MkPopupMenu.vue
+++ b/packages/frontend/src/components/MkPopupMenu.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef } from 'vue';
+import { ref, useTemplateRef } from 'vue';
import MkModal from './MkModal.vue';
import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js';
@@ -28,7 +28,7 @@ const emit = defineEmits<{
(ev: 'closing'): void;
}>();
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
const manualShowing = ref(true);
const hiding = ref(false);
diff --git a/packages/frontend/src/components/MkPostForm.TextCounter.vue b/packages/frontend/src/components/MkPostForm.TextCounter.vue
new file mode 100644
index 0000000000..b1d39df5d3
--- /dev/null
+++ b/packages/frontend/src/components/MkPostForm.TextCounter.vue
@@ -0,0 +1,95 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.textCountRoot]">
+ <div :class="$style.textCountLabel">{{ i18n.ts.textCount }}</div>
+ <div
+ :class="[$style.textCount,
+ { [$style.danger]: textCountPercentage > 100 },
+ { [$style.warning]: textCountPercentage > 90 && textCountPercentage <= 100 },
+ ]"
+ >
+ <div :class="$style.textCountGraph"></div>
+ <div><span :class="$style.textCountCurrent">{{ number(textLength) }}</span> / {{ number(maxTextLength) }}</div>
+ </div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, useTemplateRef } from 'vue';
+import { instance } from '@/instance.js';
+import { i18n } from '@/i18n.js';
+import number from '@/filters/number.js';
+
+const props = defineProps<{
+ textLength: number;
+}>();
+
+const maxTextLength = computed(() => {
+ return instance ? instance.maxNoteTextLength : 1000;
+});
+
+const textCountPercentage = computed(() => {
+ return props.textLength / maxTextLength.value * 100;
+});
+</script>
+
+<style lang="scss" module>
+.textCountRoot {
+ padding: 4px 14px;
+}
+
+.textCountLabel {
+ font-size: 11px;
+ opacity: 0.8;
+ margin-bottom: 4px;
+}
+
+.textCount {
+ display: flex;
+ gap: var(--MI-marginHalf);
+ align-items: center;
+ font-size: 12px;
+ --countColor: var(--MI_THEME-accent);
+
+ &.danger {
+ --countColor: var(--MI_THEME-error);
+ }
+
+ &.warning {
+ --countColor: var(--MI_THEME-warn);
+ }
+
+ .textCountGraph {
+ position: relative;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ background-image: conic-gradient(
+ var(--countColor) 0% v-bind("Math.min(100, textCountPercentage) + '%'"),
+ rgba(0, 0, 0, .2) v-bind("Math.min(100, textCountPercentage) + '%'") 100%
+ );
+
+ &::after {
+ content: '';
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background-color: var(--MI_THEME-popup);
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+ }
+
+ .textCountCurrent {
+ color: var(--countColor);
+ font-weight: 700;
+ font-size: 18px;
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index ca227d649a..921829c41e 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="$style.headerRight">
<template v-if="!(channel != null && fixed)">
- <button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" :disabled="editId != null" @click="setVisibility">
+ <button v-if="channel == null" ref="visibilityButton" v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" :disabled="editId != null" @click="setVisibility">
<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span>
<span v-if="visibility === 'home'"><i class="ti ti-home"></i></span>
<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span>
@@ -32,11 +32,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
</button>
</template>
- <button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified' || editId != null" @click="toggleLocalOnly">
+ <button v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified' || editId != null" @click="toggleLocalOnly">
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
<span v-else><i class="ti ti-rocket-off"></i></span>
</button>
- <button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance">
+ <button ref="otherSettingsButton" v-tooltip="i18n.ts.other" class="_button" :class="$style.headerRightItem" @click="showOtherSettings"><i class="ti ti-dots"></i></button>
+ <button v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance">
<span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span>
<span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span>
<span v-else><i class="ph-smiley ph-bold ph-lg"></i></span>
@@ -65,9 +66,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</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">
+ <div v-show="useCw" :class="$style.cwOuter">
<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 v-if="maxCwTextLength - cwTextLength < 20" :class="['_acrylic', $style.cwTextCount, { [$style.cwTextOver]: cwTextLength > maxCwTextLength }]">{{ maxCwTextLength - cwTextLength }}</div>
</div>
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
@@ -106,41 +107,48 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, toRaw, type ShallowRef } from 'vue';
+import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef, toRaw } from 'vue';
import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
import { toASCII } from 'punycode.js';
import { host, url } from '@@/js/config.js';
+import type { ShallowRef } from 'vue';
import { appendContentWarning } from '@@/js/append-content-warning.js';
import type { MenuItem } from '@/types/menu.js';
import type { PostFormProps } from '@/types/post-form.js';
-import MkNoteSimple from '@/components/MkNoteSimple.vue';
+import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
import MkNotePreview from '@/components/MkNotePreview.vue';
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
-import MkPollEditor, { type PollEditorModelValue } from '@/components/MkPollEditor.vue';
-import { erase, unique } from '@/scripts/array.js';
-import { extractMentions } from '@/scripts/extract-mentions.js';
-import { formatTimeString } from '@/scripts/format-time-string.js';
-import { Autocomplete } from '@/scripts/autocomplete.js';
+import XTextCounter from '@/components/MkPostForm.TextCounter.vue';
+import MkPollEditor from '@/components/MkPollEditor.vue';
+import MkNoteSimple from '@/components/MkNoteSimple.vue';
+import { erase, unique } from '@/utility/array.js';
+import { extractMentions } from '@/utility/extract-mentions.js';
+import { formatTimeString } from '@/utility/format-time-string.js';
+import { Autocomplete } from '@/utility/autocomplete.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { selectFiles } from '@/scripts/select-file.js';
-import { defaultStore, notePostInterruptors, postFormActions } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { selectFiles } from '@/utility/select-file.js';
+import { store } from '@/store.js';
import MkInfo from '@/components/MkInfo.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { signinRequired, notesCount, incNotesCount, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account.js';
-import { uploadFile } from '@/scripts/upload.js';
-import { deepClone } from '@/scripts/clone.js';
+import { ensureSignin, notesCount, incNotesCount } from '@/i.js';
+import { getAccounts, openAccountMenu as openAccountMenu_ } from '@/accounts.js';
+import { uploadFile } from '@/utility/upload.js';
+import { deepClone } from '@/utility/clone.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
-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 MkScheduleEditor from '@/components/MkScheduleEditor.vue';
+import { miLocalStorage } from '@/local-storage.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { emojiPicker } from '@/utility/emoji-picker.js';
+import { mfmFunctionPicker } from '@/utility/mfm-function-picker.js';
+import { prefer } from '@/preferences.js';
+import { getPluginHandlers } from '@/plugin.js';
+import { DI } from '@/di.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const modal = inject('modal');
@@ -157,7 +165,7 @@ const props = withDefaults(defineProps<PostFormProps & {
initialLocalOnly: undefined,
});
-provide('mock', props.mock);
+provide(DI.mock, props.mock);
const emit = defineEmits<{
(ev: 'posted'): void;
@@ -168,10 +176,11 @@ const emit = defineEmits<{
(ev: 'fileChangeSensitive', fileId: string, to: boolean): void;
}>();
-const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
-const cwInputEl = shallowRef<HTMLInputElement | null>(null);
-const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
-const visibilityButton = shallowRef<HTMLElement>();
+const textareaEl = useTemplateRef('textareaEl');
+const cwInputEl = useTemplateRef('cwInputEl');
+const hashtagsInputEl = useTemplateRef('hashtagsInputEl');
+const visibilityButton = useTemplateRef('visibilityButton');
+const otherSettingsButton = useTemplateRef('otherSettingsButton');
const posting = ref(false);
const posted = ref(false);
@@ -179,19 +188,18 @@ const text = ref(props.initialText ?? '');
const files = ref(props.initialFiles ?? []);
const poll = ref<PollEditorModelValue | null>(null);
const useCw = ref<boolean>(!!props.initialCw);
-const showPreview = ref(defaultStore.state.showPreview);
-watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
-const showAddMfmFunction = ref(defaultStore.state.enableQuickAddMfmFunction);
-watch(showAddMfmFunction, () => defaultStore.set('enableQuickAddMfmFunction', showAddMfmFunction.value));
+const showPreview = ref(store.s.showPreview);
+watch(showPreview, () => store.set('showPreview', showPreview.value));
+const showAddMfmFunction = ref(prefer.s.enableQuickAddMfmFunction);
+watch(showAddMfmFunction, () => prefer.commit('enableQuickAddMfmFunction', showAddMfmFunction.value));
const cw = ref<string | null>(props.initialCw ?? null);
-const localOnly = ref(props.initialLocalOnly ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly));
-const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility));
+const localOnly = ref(props.initialLocalOnly ?? (prefer.s.rememberNoteVisibility ? store.s.localOnly : prefer.s.defaultNoteLocalOnly));
+const visibility = ref(props.initialVisibility ?? (prefer.s.rememberNoteVisibility ? store.s.visibility : prefer.s.defaultNoteVisibility));
const visibleUsers = ref<Misskey.entities.UserDetailed[]>([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(u => pushVisibleUser(u));
}
-const reactionAcceptance = ref(defaultStore.state.reactionAcceptance);
-const autocomplete = ref(null);
+const reactionAcceptance = ref(store.s.reactionAcceptance);
const draghover = ref(false);
const quoteId = ref<string | null>(null);
const hasNotSpecifiedMentions = ref(false);
@@ -204,6 +212,7 @@ const scheduleNote = ref<{
scheduledAt: number | null;
} | null>(null);
const renoteTargetNote: ShallowRef<PostFormProps['renote'] | null> = shallowRef(props.renote);
+const postFormActions = getPluginHandlers('post_form_action');
const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
@@ -255,8 +264,11 @@ const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 1000;
});
-const cwLength = computed(() => cw.value?.length ?? 0);
-const maxCwLength = computed(() => instance.maxCwLength);
+const cwTextLength = computed((): number => {
+ return cw.value?.length ?? 0;
+});
+
+const maxCwTextLength = computed(() => instance.maxCwLength);
const canPost = computed((): boolean => {
return !props.mock && !posting.value && !posted.value &&
@@ -268,13 +280,19 @@ const canPost = computed((): boolean => {
quoteId.value != null
) &&
(textLength.value <= maxTextLength.value) &&
- (cwLength.value <= maxCwLength.value) &&
+ (
+ useCw.value ?
+ (
+ cw.value != null && cw.value.trim() !== '' &&
+ cwTextLength.value <= maxCwTextLength.value
+ ) : true
+ ) &&
(files.value.length <= 16) &&
(!poll.value || poll.value.choices.length >= 2);
});
-const withHashtags = computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
-const hashtags = computed(defaultStore.makeGetterSetter('postFormHashtags'));
+const withHashtags = computed(store.makeGetterSetter('postFormWithHashtags'));
+const hashtags = computed(store.makeGetterSetter('postFormHashtags'));
watch(text, () => {
checkMissingMention();
@@ -362,7 +380,7 @@ if (props.specified) {
}
// keep cw when reply
-if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
+if (prefer.s.keepCw && props.reply && props.reply.cw) {
useCw.value = true;
cw.value = props.reply.cw;
}
@@ -484,7 +502,7 @@ function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities
function upload(file: File, name?: string): void {
if (props.mock) return;
- uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
+ uploadFile(file, prefer.s.uploadFolder, name).then(res => {
files.value.push(res);
});
}
@@ -505,8 +523,8 @@ function setVisibility() {
}, {
changeVisibility: v => {
visibility.value = v;
- if (defaultStore.state.rememberNoteVisibility) {
- defaultStore.set('visibility', visibility.value);
+ if (prefer.s.rememberNoteVisibility) {
+ store.set('visibility', visibility.value);
}
},
closed: () => dispose(),
@@ -553,8 +571,8 @@ async function toggleLocalOnly() {
}
localOnly.value = !localOnly.value;
- if (defaultStore.state.rememberNoteVisibility) {
- defaultStore.set('localOnly', localOnly.value);
+ if (prefer.s.rememberNoteVisibility) {
+ store.set('localOnly', localOnly.value);
}
}
@@ -574,6 +592,47 @@ async function toggleReactionAcceptance() {
reactionAcceptance.value = select.result;
}
+//#region その他の設定メニューpopup
+function showOtherSettings() {
+ let reactionAcceptanceIcon = 'ti ti-icons';
+
+ if (reactionAcceptance.value === 'likeOnly') {
+ reactionAcceptanceIcon = 'ti ti-heart _love';
+ } else if (reactionAcceptance.value === 'likeOnlyForRemote') {
+ reactionAcceptanceIcon = 'ti ti-heart-plus';
+ }
+
+ const menuItems = [{
+ type: 'component',
+ component: XTextCounter,
+ props: {
+ textLength: textLength,
+ },
+ }, { type: 'divider' }, {
+ icon: reactionAcceptanceIcon,
+ text: i18n.ts.reactionAcceptance,
+ action: () => {
+ toggleReactionAcceptance();
+ },
+ }, { type: 'divider' }, {
+ icon: 'ti ti-trash',
+ text: i18n.ts.reset,
+ danger: true,
+ action: async () => {
+ if (props.mock) return;
+ const { canceled } = await os.confirm({
+ type: 'question',
+ text: i18n.ts.resetAreYouSure,
+ });
+ if (canceled) return;
+ clear();
+ },
+ }] satisfies MenuItem[];
+
+ os.popupMenu(menuItems, otherSettingsButton.value);
+}
+//#endregion
+
function pushVisibleUser(user: Misskey.entities.UserDetailed) {
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.value.push(user);
@@ -623,6 +682,8 @@ function onCompositionEnd(ev: CompositionEvent) {
justEndedComposition.value = true;
}
+const pastedFileName = 'yyyy-MM-dd HH-mm-ss [{{number}}]';
+
async function onPaste(ev: ClipboardEvent) {
if (props.mock) return;
if (!ev.clipboardData) return;
@@ -633,7 +694,7 @@ async function onPaste(ev: ClipboardEvent) {
if (!file) continue;
const lio = file.name.lastIndexOf('.');
const ext = lio >= 0 ? file.name.slice(lio) : '';
- const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`;
+ const formatted = `${formatTimeString(new Date(file.lastModified), pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`;
upload(file, formatted);
}
}
@@ -678,7 +739,7 @@ async function onPaste(ev: ClipboardEvent) {
return;
}
- const fileName = formatTimeString(new Date(), defaultStore.state.pastedFileName).replace(/{{number}}/g, '0');
+ const fileName = formatTimeString(new Date(), pastedFileName).replace(/{{number}}/g, '0');
const file = new File([paste], `${fileName}.txt`, { type: 'text/plain' });
upload(file, `${fileName}.txt`);
});
@@ -772,19 +833,19 @@ function deleteDraft() {
miLocalStorage.setItem('drafts', JSON.stringify(draftData));
}
-async function post(ev?: MouseEvent) {
- if (useCw.value && (cw.value == null || cw.value.trim() === '')) {
- os.alert({
- type: 'error',
- text: i18n.ts.cwNotationRequired,
- });
- return;
- }
+function isAnnoying(text: string): boolean {
+ return text.includes('$[x2') ||
+ text.includes('$[x3') ||
+ text.includes('$[x4') ||
+ text.includes('$[scale') ||
+ text.includes('$[position');
+}
+async function post(ev?: MouseEvent) {
if (ev) {
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
- if (el) {
+ if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -796,14 +857,10 @@ async function post(ev?: MouseEvent) {
if (props.mock) return;
- const annoying =
- text.value.includes('$[x2') ||
- text.value.includes('$[x3') ||
- text.value.includes('$[x4') ||
- text.value.includes('$[scale') ||
- text.value.includes('$[position');
-
- if (annoying && visibility.value === 'public') {
+ if (visibility.value === 'public' && (
+ (useCw.value && cw.value != null && cw.value.trim() !== '' && isAnnoying(cw.value)) || // CWが迷惑になる場合
+ ((!useCw.value || cw.value == null || cw.value.trim() === '') && text.value != null && text.value.trim() !== '' && isAnnoying(text.value)) // CWが無い かつ 本文が迷惑になる場合
+ )) {
const { canceled, result } = await os.actions({
type: 'warning',
text: i18n.ts.thisPostMayBeAnnoying,
@@ -884,6 +941,7 @@ async function post(ev?: MouseEvent) {
}
// plugin
+ const notePostInterruptors = getPluginHandlers('note_post_interruptor');
if (notePostInterruptors.length > 0) {
for (const interruptor of notePostInterruptors) {
try {
@@ -898,7 +956,7 @@ async function post(ev?: MouseEvent) {
if (postAccount.value) {
const storedAccounts = await getAccounts();
- token = storedAccounts.find(x => x.id === postAccount.value?.id)?.token;
+ token = storedAccounts.find(x => x.user.id === postAccount.value?.id)?.token;
}
posting.value = true;
@@ -1182,6 +1240,8 @@ defineExpose({
&.modal {
width: 100%;
max-width: 520px;
+ overflow-x: clip;
+ overflow-y: auto;
}
}
@@ -1292,7 +1352,7 @@ defineExpose({
border-radius: var(--MI-radius-sm);
&:hover {
- background: var(--MI_THEME-X5);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
&:disabled {
@@ -1356,7 +1416,7 @@ defineExpose({
margin-right: 14px;
padding: 8px 0 8px 8px;
border-radius: var(--MI-radius-sm);
- background: var(--MI_THEME-X4);
+ background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
}
.hasNotSpecifiedMentions {
@@ -1387,7 +1447,12 @@ defineExpose({
}
}
-.cwFrame {
+.cwOuter {
+ width: 100%;
+ position: relative;
+}
+
+.cw {
z-index: 1;
padding-bottom: 8px;
border-bottom: solid 0.5px var(--MI_THEME-divider);
@@ -1396,6 +1461,23 @@ defineExpose({
position: relative;
}
+.cwTextCount {
+ position: absolute;
+ top: 0;
+ right: 2px;
+ padding: 2px 6px;
+ font-size: .9em;
+ color: var(--MI_THEME-warn);
+ border-radius: 6px;
+ max-width: 100%;
+ min-width: 1.6em;
+ text-align: center;
+
+ &.cwTextOver {
+ color: #ff2a2a;
+ }
+}
+
.hashtags {
z-index: 1;
padding-top: 8px;
@@ -1470,7 +1552,7 @@ defineExpose({
border-radius: var(--MI-radius-sm);
&:hover {
- background: var(--MI_THEME-X5);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
&.footerButtonActive {
diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue
index bab7d22112..43348475e9 100644
--- a/packages/frontend/src/components/MkPostFormAttaches.vue
+++ b/packages/frontend/src/components/MkPostFormAttaches.vue
@@ -22,20 +22,27 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
</Sortable>
- <p :class="[$style.remain, {
- [$style.exceeded]: props.modelValue.length > 16,
- }]">{{ 16 - props.modelValue.length }}/16</p>
+ <p
+ :class="[$style.remain, {
+ [$style.exceeded]: props.modelValue.length > 16,
+ }]"
+ >
+ {{ props.modelValue.length }}/16
+ </p>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, inject } from 'vue';
import * as Misskey from 'misskey-js';
+import type { MenuItem } from '@/types/menu';
+import { copyToClipboard } from '@/utility/copy-to-clipboard';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import type { MenuItem } from '@/types/menu.js';
+import { prefer } from '@/preferences.js';
+import { DI } from '@/di.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@@ -44,7 +51,7 @@ const props = defineProps<{
detachMediaFn?: (id: string) => void;
}>();
-const mock = inject<boolean>('mock', false);
+const mock = inject(DI.mock, false);
const emit = defineEmits<{
(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void;
@@ -168,6 +175,14 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
text: i18n.ts.cropImage,
icon: 'ti ti-crop',
action: () : void => { crop(file); },
+ }, {
+ text: i18n.ts.preview,
+ icon: 'ti ti-photo-search',
+ action: () => {
+ os.popup(defineAsyncComponent(() => import('@/components/MkImgPreviewDialog.vue')), {
+ file: file,
+ });
+ },
});
}
@@ -184,6 +199,16 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
action: () => { detachAndDeleteMedia(file); },
});
+ if (prefer.s.devMode) {
+ menuItems.push({ type: 'divider' }, {
+ icon: 'ti ti-hash',
+ text: i18n.ts.copyFileId,
+ action: () => {
+ copyToClipboard(file.id);
+ },
+ });
+ }
+
os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => menuShowing = false);
menuShowing = true;
}
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index 0fd17e12c7..aa3eebb257 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -4,17 +4,32 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkModal ref="modal" :preferType="'dialog'" @click="modal?.close()" @closed="onModalClosed()" @esc="modal?.close()">
- <MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="onCancel" @esc="onCancel"/>
+<MkModal
+ ref="modal"
+ :preferType="'dialog'"
+ @click="modal?.close()"
+ @closed="onModalClosed()"
+ @esc="modal?.close()"
+>
+ <MkPostForm
+ ref="form"
+ :class="$style.form"
+ v-bind="props"
+ autofocus
+ freezeAfterPosted
+ @posted="onPosted"
+ @cancel="onCancel"
+ @esc="onCancel"
+ />
</MkModal>
</template>
<script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
+import type { PostFormProps } from '@/types/post-form.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<PostFormProps & {
instant?: boolean;
@@ -29,8 +44,7 @@ const emit = defineEmits<{
(ev: 'closed', cancelled: boolean): void;
}>();
-const modal = shallowRef<InstanceType<typeof MkModal>>();
-const form = shallowRef<InstanceType<typeof MkPostForm>>();
+const modal = useTemplateRef('modal');
function onPosted() {
modal.value?.close({
diff --git a/packages/frontend/src/components/MkPreferenceContainer.vue b/packages/frontend/src/components/MkPreferenceContainer.vue
new file mode 100644
index 0000000000..70b111513c
--- /dev/null
+++ b/packages/frontend/src/components/MkPreferenceContainer.vue
@@ -0,0 +1,103 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root" @contextmenu.prevent.stop="showMenu($event, true)">
+ <div :class="$style.body">
+ <slot></slot>
+ </div>
+ <div :class="$style.menu">
+ <i v-if="isSyncEnabled" class="ti ti-cloud-cog" style="color: var(--MI_THEME-accent); opacity: 0.7;"></i>
+ <i v-if="isAccountOverrided" class="ti ti-user-cog" style="color: var(--MI_THEME-accent); opacity: 0.7;"></i>
+ <div :class="$style.buttons">
+ <button class="_button" style="color: var(--MI_THEME-fg)" @click="showMenu($event)"><i class="ti ti-dots"></i></button>
+ </div>
+ </div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import type { PREF_DEF } from '@/preferences/def.js';
+import * as os from '@/os.js';
+import { prefer } from '@/preferences.js';
+
+const props = withDefaults(defineProps<{
+ k: keyof typeof PREF_DEF;
+}>(), {
+});
+
+const isAccountOverrided = ref(prefer.isAccountOverrided(props.k));
+const isSyncEnabled = ref(prefer.isSyncEnabled(props.k));
+
+function showMenu(ev: MouseEvent, contextmenu?: boolean) {
+ const i = window.setInterval(() => {
+ isAccountOverrided.value = prefer.isAccountOverrided(props.k);
+ isSyncEnabled.value = prefer.isSyncEnabled(props.k);
+ }, 100);
+ if (contextmenu) {
+ os.contextMenu(prefer.getPerPrefMenu(props.k), ev).then(() => {
+ window.clearInterval(i);
+ });
+ } else {
+ os.popupMenu(prefer.getPerPrefMenu(props.k), ev.currentTarget ?? ev.target, {
+ onClosing: () => {
+ window.clearInterval(i);
+ },
+ });
+ }
+}
+</script>
+
+<style lang="scss" module>
+.root {
+ position: relative;
+ display: flex;
+
+ &:hover {
+ &::before {
+ content: '';
+ position: absolute;
+ top: -8px;
+ left: -8px;
+ width: calc(100% + 16px);
+ height: calc(100% + 16px);
+ border-radius: 8px;
+ background: light-dark(rgba(0, 0, 0, 0.02), rgba(255, 255, 255, 0.02));
+ pointer-events: none;
+ }
+
+ .menu {
+ .buttons {
+ opacity: 0.7;
+ }
+ }
+ }
+
+ .body {
+ flex: 1;
+ }
+
+ .menu {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ margin-left: 12px;
+ font-size: 12px;
+ padding-left: 8px;
+ border-left: solid 1px var(--MI_THEME-divider);
+
+ &:hover {
+ .buttons {
+ opacity: 1;
+ }
+ }
+
+ .buttons {
+ opacity: 0.3;
+ }
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/MkPreview.vue b/packages/frontend/src/components/MkPreview.vue
index 6efd99d14b..d8dfbd1655 100644
--- a/packages/frontend/src/components/MkPreview.vue
+++ b/packages/frontend/src/components/MkPreview.vue
@@ -43,7 +43,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkRadio from '@/components/MkRadio.vue';
import * as os from '@/os.js';
import * as config from '@@/js/config.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
const text = ref('');
const flag = ref(true);
diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index 4fb4c6fe56..1fbf00d212 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -23,10 +23,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
-import { i18n } from '@/i18n.js';
+import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
import { getScrollContainer } from '@@/js/scroll.js';
-import { isHorizontalSwipeSwiping } from '@/scripts/touch.js';
+import { i18n } from '@/i18n.js';
+import { isHorizontalSwipeSwiping } from '@/utility/touch.js';
const SCROLL_STOP = 10;
const MAX_PULL_DISTANCE = Infinity;
@@ -43,7 +43,7 @@ const pullDistance = ref(0);
let supportPointerDesktop = false;
let startScreenY: number | null = null;
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
let scrollEl: HTMLElement | null = null;
let disabled = false;
diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
index 5e42df4795..9c37eb5e72 100644
--- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue
+++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
@@ -42,12 +42,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { ref } from 'vue';
-import { $i, getAccounts } from '@/account.js';
+import { $i } from '@/i.js';
import MkButton from '@/components/MkButton.vue';
import { instance } from '@/instance.js';
import { apiWithDialog, promiseDialog } from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
+import { getAccounts } from '@/accounts.js';
defineProps<{
primary?: boolean;
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index af81eb814d..559399d1d4 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -4,7 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<script lang="ts">
-import { VNode, defineComponent, h, ref, watch } from 'vue';
+import { defineComponent, h, ref, watch } from 'vue';
+import type { VNode } from 'vue';
import MkRadio from './MkRadio.vue';
export default defineComponent({
diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue
index d009f3858c..81cbd6b842 100644
--- a/packages/frontend/src/components/MkRange.vue
+++ b/packages/frontend/src/components/MkRange.vue
@@ -33,8 +33,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
-import { isTouchUsing } from '@/scripts/touch.js';
+import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
+import { isTouchUsing } from '@/utility/touch.js';
import * as os from '@/os.js';
const props = withDefaults(defineProps<{
@@ -58,8 +58,8 @@ const emit = defineEmits<{
(ev: 'dragEnded', value: number): void;
}>();
-const containerEl = shallowRef<HTMLElement>();
-const thumbEl = shallowRef<HTMLElement>();
+const containerEl = useTemplateRef('containerEl');
+const thumbEl = useTemplateRef('thumbEl');
const rawValue = ref((props.modelValue - props.min) / (props.max - props.min));
const steppedRawValue = computed(() => {
@@ -151,9 +151,9 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
closed: () => dispose(),
});
- const style = document.createElement('style');
- style.appendChild(document.createTextNode('* { cursor: grabbing !important; } body * { pointer-events: none !important; }'));
- document.head.appendChild(style);
+ const style = window.document.createElement('style');
+ style.appendChild(window.document.createTextNode('* { cursor: grabbing !important; } body * { pointer-events: none !important; }'));
+ window.document.head.appendChild(style);
const thumbWidth = getThumbWidth();
@@ -172,7 +172,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
let beforeValue = finalValue.value;
const onMouseup = () => {
- document.head.removeChild(style);
+ window.document.head.removeChild(style);
tooltipForDragShowing.value = false;
window.removeEventListener('mousemove', onDrag);
window.removeEventListener('touchmove', onDrag);
diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue
index c0cbd8a65d..453253f0fc 100644
--- a/packages/frontend/src/components/MkReactionIcon.vue
+++ b/packages/frontend/src/components/MkReactionIcon.vue
@@ -9,8 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, shallowRef } from 'vue';
-import { useTooltip } from '@/scripts/use-tooltip.js';
+import { defineAsyncComponent, useTemplateRef } from 'vue';
+import { useTooltip } from '@/use/use-tooltip.js';
import * as os from '@/os.js';
const props = defineProps<{
@@ -20,7 +20,7 @@ const props = defineProps<{
withTooltip?: boolean;
}>();
-const elRef = shallowRef();
+const elRef = useTemplateRef('elRef');
if (props.withTooltip) {
useTooltip(elRef, (showing) => {
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index 9cd972639f..e66a056a3f 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -8,33 +8,34 @@ SPDX-License-Identifier: AGPL-3.0-only
ref="buttonEl"
v-ripple="canToggle"
class="_button"
- :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
+ :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: prefer.s.reactionsDisplaySize === 'small', [$style.large]: prefer.s.reactionsDisplaySize === 'large' }]"
@click="toggleReaction()"
@contextmenu.prevent.stop="menu"
>
- <MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]" @click="toggleReaction()" @click.stop/>
+ <MkReactionIcon :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]" @click="toggleReaction()" @click.stop/>
<span :class="$style.count">{{ count }}</span>
</button>
</template>
<script lang="ts" setup>
-import { computed, inject, onMounted, shallowRef, watch } from 'vue';
+import { computed, inject, onMounted, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { getUnicodeEmoji } from '@@/js/emojilist.js';
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
import XDetails from '@/components/MkReactionsViewer.details.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import * as os from '@/os.js';
-import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
-import { useTooltip } from '@/scripts/use-tooltip.js';
-import { $i } from '@/account.js';
+import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
+import { useTooltip } from '@/use/use-tooltip.js';
+import { $i } from '@/i.js';
import MkReactionEffect from '@/components/MkReactionEffect.vue';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { defaultStore } from '@/store.js';
+import { claimAchievement } from '@/utility/achievements.js';
import { i18n } from '@/i18n.js';
-import * as sound from '@/scripts/sound.js';
-import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
+import * as sound from '@/utility/sound.js';
+import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js';
import { customEmojisMap } from '@/custom-emojis.js';
+import { prefer } from '@/preferences.js';
+import { DI } from '@/di.js';
const props = defineProps<{
reaction: string;
@@ -43,13 +44,13 @@ const props = defineProps<{
note: Misskey.entities.Note;
}>();
-const mock = inject<boolean>('mock', false);
+const mock = inject(DI.mock, false);
const emit = defineEmits<{
(ev: 'reactionToggled', emoji: string, newCount: number): void;
}>();
-const buttonEl = shallowRef<HTMLElement>();
+const buttonEl = useTemplateRef('buttonEl');
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));
@@ -90,6 +91,15 @@ async function toggleReaction() {
}
});
} else {
+ if (prefer.s.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: props.reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
if (mock) {
@@ -126,7 +136,7 @@ async function menu(ev) {
}
function anime() {
- if (document.hidden || !defaultStore.state.animation || buttonEl.value == null) return;
+ if (window.document.hidden || !prefer.s.animation || buttonEl.value == null) return;
const rect = buttonEl.value.getBoundingClientRect();
const x = rect.left + 16;
diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue
index a70ed18d18..cf0bb7b1f2 100644
--- a/packages/frontend/src/components/MkReactionsViewer.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.vue
@@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<TransitionGroup
- :enterActiveClass="defaultStore.state.animation ? $style.transition_x_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_x_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_x_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_x_leaveTo : ''"
- :moveClass="defaultStore.state.animation ? $style.transition_x_move : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_x_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_x_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_x_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_x_leaveTo : ''"
+ :moveClass="prefer.s.animation ? $style.transition_x_move : ''"
tag="div" :class="$style.root"
>
<XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :isInitial="initialReactions.has(reaction)" :note="note" @reactionToggled="onMockToggleReaction"/>
@@ -21,7 +21,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'misskey-js';
import { inject, watch, ref } from 'vue';
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
+import { DI } from '@/di.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@@ -30,7 +31,7 @@ const props = withDefaults(defineProps<{
maxNumber: Infinity,
});
-const mock = inject<boolean>('mock', false);
+const mock = inject(DI.mock, false);
const emit = defineEmits<{
(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
diff --git a/packages/frontend/src/components/MkRemoteCaution.vue b/packages/frontend/src/components/MkRemoteCaution.vue
index 6391468204..0d1a2f0b76 100644
--- a/packages/frontend/src/components/MkRemoteCaution.vue
+++ b/packages/frontend/src/components/MkRemoteCaution.vue
@@ -4,14 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div :class="$style.root"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i>{{ i18n.ts.remoteUserCaution }}<a :class="$style.link" :href="href" rel="nofollow noopener" target="_blank">{{ i18n.ts.showOnRemote }}</a></div>
+<div :class="$style.root"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i>{{ i18n.ts.remoteUserCaution }}<a v-if="href" :class="$style.link" :href="href" rel="nofollow noopener" target="_blank">{{ i18n.ts.showOnRemote }}</a></div>
</template>
<script lang="ts" setup>
import { i18n } from '@/i18n.js';
defineProps<{
- href: string;
+ href?: string;
}>();
</script>
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index 64b573c4d3..1ab2397337 100644
--- a/packages/frontend/src/components/MkRetentionHeatmap.vue
+++ b/packages/frontend/src/components/MkRetentionHeatmap.vue
@@ -13,18 +13,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, nextTick, shallowRef, ref } from 'vue';
+import { onMounted, nextTick, useTemplateRef, ref } from 'vue';
import { Chart } from 'chart.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { alpha } from '@/scripts/color.js';
-import { initChart } from '@/scripts/init-chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { alpha } from '@/utility/color.js';
+import { initChart } from '@/utility/init-chart.js';
initChart();
-const rootEl = shallowRef<HTMLDivElement | null>(null);
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const rootEl = useTemplateRef('rootEl');
+const chartEl = useTemplateRef('chartEl');
let chartInstance: Chart | null = null;
const fetching = ref(true);
@@ -75,7 +75,7 @@ async function renderChart() {
await nextTick();
- const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300';
+ const color = store.s.darkMode ? '#b4e900' : '#86b300';
const getYYYYMMDD = (date: Date) => {
const y = date.getFullYear().toString().padStart(2, '0');
diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue
index d41793b0fa..ba66ffecc0 100644
--- a/packages/frontend/src/components/MkRetentionLineChart.vue
+++ b/packages/frontend/src/components/MkRetentionLineChart.vue
@@ -8,19 +8,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
import { Chart } from 'chart.js';
import tinycolor from 'tinycolor2';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { alpha } from '@/scripts/color.js';
-import { initChart } from '@/scripts/init-chart.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { alpha } from '@/utility/color.js';
+import { initChart } from '@/utility/init-chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
initChart();
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const chartEl = useTemplateRef('chartEl');
const { handler: externalTooltipHandler } = useChartTooltip();
@@ -42,9 +42,9 @@ const getDate = (ymd: string) => {
onMounted(async () => {
let raw = await misskeyApi('retention', { });
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
- const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent'));
+ const accent = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-accent'));
const color = accent.toHex();
if (chartEl.value == null) return;
diff --git a/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts
index 411d62edf9..b090f0a0fa 100644
--- a/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http, HttpResponse } from 'msw';
import { role } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue
index 8d11bd855f..5f77dc6734 100644
--- a/packages/frontend/src/components/MkRoleSelectDialog.vue
+++ b/packages/frontend/src/components/MkRoleSelectDialog.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:width="400"
:height="500"
@close="onCloseModalWindow"
- @closed="$emit('dispose')"
+ @closed="emit('closed')"
>
<template #header>{{ title }}</template>
<MkSpacer :marginMin="20" :marginMax="28">
@@ -49,7 +49,7 @@ import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkRolePreview from '@/components/MkRolePreview.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import * as os from '@/os.js';
import MkSpacer from '@/components/global/MkSpacer.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -58,7 +58,7 @@ import MkLoading from '@/components/global/MkLoading.vue';
const emit = defineEmits<{
(ev: 'done', value: Misskey.entities.Role[]),
(ev: 'close'),
- (ev: 'dispose'),
+ (ev: 'closed'),
}>();
const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 79a56b68a8..361d80c68b 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -40,11 +40,29 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue';
+import { onMounted, nextTick, ref, watch, computed, toRefs, useSlots } from 'vue';
import { useInterval } from '@@/js/use-interval.js';
+import type { VNode, VNodeChild } from 'vue';
import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
+type ItemOption = {
+ type?: 'option';
+ value: string | number | null;
+ label: string;
+};
+
+type ItemGroup = {
+ type: 'group';
+ label: string;
+ items: ItemOption[];
+};
+
+export type MkSelectItem = ItemOption | ItemGroup;
+
+// TODO: itemsをslot内のoptionで指定する用法は廃止する(props.itemsを必須化する)
+// see: https://github.com/misskey-dev/misskey/issues/15558
+
const props = defineProps<{
modelValue: string | number | null;
required?: boolean;
@@ -55,6 +73,7 @@ const props = defineProps<{
inline?: boolean;
small?: boolean;
large?: boolean;
+ items?: MkSelectItem[];
}>();
const emit = defineEmits<{
@@ -106,7 +125,30 @@ onMounted(() => {
});
});
-watch(modelValue, () => {
+watch([modelValue, () => props.items], () => {
+ if (props.items) {
+ let found: ItemOption | null = null;
+ for (const item of props.items) {
+ if (item.type === 'group') {
+ for (const option of item.items) {
+ if (option.value === modelValue.value) {
+ found = option;
+ break;
+ }
+ }
+ } else {
+ if (item.value === modelValue.value) {
+ found = item;
+ break;
+ }
+ }
+ }
+ if (found) {
+ currentValueText.value = found.label;
+ }
+ return;
+ }
+
const scanOptions = (options: VNodeChild[]) => {
for (const vnode of options) {
if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
@@ -129,7 +171,7 @@ watch(modelValue, () => {
};
scanOptions(slots.default!());
-}, { immediate: true });
+}, { immediate: true, deep: true });
function show() {
if (opening.value) return;
@@ -138,41 +180,70 @@ function show() {
opening.value = true;
const menu: MenuItem[] = [];
- let options = slots.default!();
- const pushOption = (option: VNode) => {
- menu.push({
- text: option.children as string,
- active: computed(() => modelValue.value === option.props?.value),
- action: () => {
- emit('update:modelValue', option.props?.value);
- },
- });
- };
-
- 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 (props.items) {
+ for (const item of props.items) {
+ if (item.type === 'group') {
menu.push({
type: 'label',
- text: optgroup.props?.label,
+ text: item.label,
});
- 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?
+ for (const option of item.items) {
+ menu.push({
+ text: option.label,
+ active: computed(() => modelValue.value === option.value),
+ action: () => {
+ emit('update:modelValue', option.value);
+ },
+ });
+ }
} else {
- const option = vnode;
- pushOption(option);
+ menu.push({
+ text: item.label,
+ active: computed(() => modelValue.value === item.value),
+ action: () => {
+ emit('update:modelValue', item.value);
+ },
+ });
}
}
- };
+ } else {
+ let options = slots.default!();
+
+ const pushOption = (option: VNode) => {
+ menu.push({
+ text: option.children as string,
+ active: computed(() => modelValue.value === option.props?.value),
+ action: () => {
+ emit('update:modelValue', option.props?.value);
+ },
+ });
+ };
- scanOptions(options);
+ 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;
+ menu.push({
+ type: 'label',
+ text: optgroup.props?.label,
+ });
+ 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;
+ pushOption(option);
+ }
+ }
+ };
+
+ scanOptions(options);
+ }
os.popupMenu(menu, container.value, {
width: container.value?.offsetWidth,
diff --git a/packages/frontend/src/components/MkSignin.input.vue b/packages/frontend/src/components/MkSignin.input.vue
index e98ac9cfd2..aacd1eae2a 100644
--- a/packages/frontend/src/components/MkSignin.input.vue
+++ b/packages/frontend/src/components/MkSignin.input.vue
@@ -58,7 +58,7 @@ import { toUnicode } from 'punycode.js';
import { query, extractDomain } from '@@/js/url.js';
import { host as configHost } from '@@/js/config.js';
-import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
+import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index d6177762d2..3037a41657 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -67,20 +67,20 @@ SPDX-License-Identifier: AGPL-3.0-only
import { nextTick, onBeforeUnmount, ref, shallowRef, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
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 { misskeyApi } from '@/scripts/misskey-api.js';
-import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
-import { login } from '@/account.js';
+import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
+import type { PwResponse } from '@/components/MkSignin.password.vue';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { showSuspendedDialog } from '@/utility/show-suspended-dialog.js';
import { i18n } from '@/i18n.js';
import { showSystemAccountDialog } from '@/scripts/show-system-account-dialog.js';
import * as os from '@/os.js';
import XInput from '@/components/MkSignin.input.vue';
-import XPassword, { type PwResponse } from '@/components/MkSignin.password.vue';
+import XPassword from '@/components/MkSignin.password.vue';
import XTotp from '@/components/MkSignin.totp.vue';
import XPasskey from '@/components/MkSignin.passkey.vue';
+import { login } from '@/accounts.js';
const emit = defineEmits<{
(ev: 'login', v: Misskey.entities.SigninFlowResponse & { finished: true }): void;
diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue
index 676a336ec7..f122da7468 100644
--- a/packages/frontend/src/components/MkSigninDialog.vue
+++ b/packages/frontend/src/components/MkSigninDialog.vue
@@ -24,8 +24,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { shallowRef } from 'vue';
-import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
+import { useTemplateRef } from 'vue';
+import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
import MkSignin from '@/components/MkSignin.vue';
import MkModal from '@/components/MkModal.vue';
import { i18n } from '@/i18n.js';
@@ -46,7 +46,7 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
function onClose() {
emit('cancelled');
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index dd263ce642..b152ba81a6 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -90,12 +90,13 @@ 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 type { Captcha } from '@/components/MkCaptcha.vue';
+import MkCaptcha from '@/components/MkCaptcha.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { login } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
+import { login } from '@/accounts.js';
const props = withDefaults(defineProps<{
autoSet?: boolean;
@@ -278,7 +279,7 @@ async function onSubmit(): Promise<void> {
'testcaptcha-response': testcaptchaResponse.value,
};
- const res = await fetch(`${config.apiUrl}/signup`, {
+ const res = await window.fetch(`${config.apiUrl}/signup`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
index 9df3ec0c30..8d99bc44b7 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
+++ b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { onBeforeUnmount } from 'vue';
import MkSignupServerRules from './MkSignupDialog.rules.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 291c3ecc2f..bf1b5fcf3e 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef, ref } from 'vue';
+import { useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import XSignup from '@/components/MkSignupDialog.form.vue';
import XServerRules from '@/components/MkSignupDialog.rules.vue';
@@ -52,7 +52,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
const isAcceptedServerRule = ref(false);
diff --git a/packages/frontend/src/components/MkSortOrderEditor.define.ts b/packages/frontend/src/components/MkSortOrderEditor.define.ts
index f023b5d72b..e56b93f98a 100644
--- a/packages/frontend/src/components/MkSortOrderEditor.define.ts
+++ b/packages/frontend/src/components/MkSortOrderEditor.define.ts
@@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-export type SortOrderDirection = '+' | '-'
+export type SortOrderDirection = '+' | '-';
export type SortOrder<T extends string> = {
key: T;
direction: SortOrderDirection;
-}
+};
diff --git a/packages/frontend/src/components/MkSortOrderEditor.vue b/packages/frontend/src/components/MkSortOrderEditor.vue
index 9decacc5f5..27ffc724ae 100644
--- a/packages/frontend/src/components/MkSortOrderEditor.vue
+++ b/packages/frontend/src/components/MkSortOrderEditor.vue
@@ -27,9 +27,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { toRefs } from 'vue';
import MkTagItem from '@/components/MkTagItem.vue';
import MkButton from '@/components/MkButton.vue';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
-import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
+import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
const emit = defineEmits<{
(ev: 'update', sortOrders: SortOrder<T>[]): void;
diff --git a/packages/frontend/src/components/MkSparkle.vue b/packages/frontend/src/components/MkSparkle.vue
index b3fc67c0df..2400c5ec7f 100644
--- a/packages/frontend/src/components/MkSparkle.vue
+++ b/packages/frontend/src/components/MkSparkle.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
+import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
const particles = ref<{
id: string,
@@ -66,7 +66,7 @@ const particles = ref<{
dur: number,
color: string
}[]>([]);
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
const width = ref(0);
const height = ref(0);
const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202'];
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index 56e8fcfa37..272646f338 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -4,27 +4,62 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="rrevdjwu" :class="{ grid }">
- <div v-for="group in def" class="group">
- <div v-if="group.title" class="title">{{ group.title }}</div>
+<div ref="rootEl" class="rrevdjwu" :class="{ grid }">
+ <MkInput
+ v-if="searchIndex && searchIndex.length > 0"
+ v-model="searchQuery"
+ :placeholder="i18n.ts.search"
+ type="search"
+ style="margin-bottom: 16px;"
+ @input.passive="searchOnInput"
+ @keydown="searchOnKeyDown"
+ >
+ <template #prefix><i class="ti ti-search"></i></template>
+ </MkInput>
- <div class="items">
- <template v-for="(item, i) in group.items">
- <a v-if="item.type === 'a'" :href="item.href" :target="item.target" class="_button item" :class="{ danger: item.danger, active: item.active }">
- <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
- <span class="text">{{ item.text }}</span>
- </a>
- <button v-else-if="item.type === 'button'" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="ev => item.action(ev)">
- <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
- <span class="text">{{ item.text }}</span>
- </button>
- <MkA v-else :to="item.to" class="_button item" :class="{ danger: item.danger, active: item.active }">
- <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
- <span class="text">{{ item.text }}</span>
- </MkA>
- </template>
+ <template v-if="rawSearchQuery == ''">
+ <div v-for="group in def" class="group">
+ <div v-if="group.title" class="title">{{ group.title }}</div>
+
+ <div class="items">
+ <template v-for="(item, i) in group.items">
+ <a v-if="item.type === 'a'" :href="item.href" :target="item.target" class="_button item" :class="{ danger: item.danger, active: item.active }">
+ <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
+ <span class="text">{{ item.text }}</span>
+ </a>
+ <button v-else-if="item.type === 'button'" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="ev => item.action(ev)">
+ <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
+ <span class="text">{{ item.text }}</span>
+ </button>
+ <MkA v-else :to="item.to" class="_button item" :class="{ danger: item.danger, active: item.active }">
+ <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
+ <span class="text">{{ item.text }}</span>
+ </MkA>
+ </template>
+ </div>
+ </div>
+ </template>
+ <template v-else>
+ <div v-for="item, index in searchResult">
+ <MkA
+ :to="item.path + '#' + item.id"
+ class="_button searchResultItem"
+ :class="{ selected: searchSelectedIndex !== null && searchSelectedIndex === index }"
+ >
+ <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span>
+ <span class="text">
+ <template v-if="item.isRoot">
+ {{ item.label }}
+ </template>
+ <template v-else>
+ <span style="opacity: 0.7; font-size: 90%;">{{ item.parentLabels.join(' > ') }}</span>
+ <br>
+ <span>{{ item.label }}</span>
+ </template>
+ </span>
+ </MkA>
</div>
- </div>
+ </template>
</div>
</template>
@@ -45,7 +80,7 @@ export type SuperMenuDef = {
text: string;
danger?: boolean;
active?: boolean;
- action: (ev: MouseEvent) => void;
+ action: (ev: MouseEvent) => void | Promise<void>;
} | {
type?: 'link';
to: string;
@@ -58,10 +93,115 @@ export type SuperMenuDef = {
</script>
<script lang="ts" setup>
-defineProps<{
+import { useTemplateRef, ref, watch, nextTick } from 'vue';
+import type { SearchIndexItem } from '@/utility/autogen/settings-search-index.js';
+import MkInput from '@/components/MkInput.vue';
+import { i18n } from '@/i18n.js';
+import { getScrollContainer } from '@@/js/scroll.js';
+import { useRouter } from '@/router.js';
+import { initIntlString, compareStringIncludes } from '@/utility/intl-string.js';
+
+const props = defineProps<{
def: SuperMenuDef[];
grid?: boolean;
+ searchIndex?: SearchIndexItem[];
}>();
+
+initIntlString();
+
+const router = useRouter();
+const rootEl = useTemplateRef('rootEl');
+
+const searchQuery = ref('');
+const rawSearchQuery = ref('');
+
+const searchSelectedIndex = ref<null | number>(null);
+const searchResult = ref<{
+ id: string;
+ path: string;
+ label: string;
+ icon?: string;
+ isRoot: boolean;
+ parentLabels: string[];
+}[]>([]);
+
+watch(searchQuery, (value) => {
+ rawSearchQuery.value = value;
+});
+
+watch(rawSearchQuery, (value) => {
+ searchResult.value = [];
+ searchSelectedIndex.value = null;
+
+ if (value === '') {
+ return;
+ }
+
+ const dive = (items: SearchIndexItem[], parents: SearchIndexItem[] = []) => {
+ for (const item of items) {
+ const matched = (
+ compareStringIncludes(item.label, value) ||
+ item.keywords.some((x) => compareStringIncludes(x, value))
+ );
+
+ if (matched) {
+ searchResult.value.push({
+ id: item.id,
+ path: item.path ?? parents.find((x) => x.path != null)?.path ?? '/', // never gets `/`
+ label: item.label,
+ parentLabels: parents.map((x) => x.label).toReversed(),
+ icon: item.icon ?? parents.find((x) => x.icon != null)?.icon,
+ isRoot: parents.length === 0,
+ });
+ }
+
+ if (item.children) {
+ dive(item.children, [item, ...parents]);
+ }
+ }
+ };
+
+ if (props.searchIndex) {
+ dive(props.searchIndex);
+ }
+});
+
+function searchOnInput(ev: InputEvent) {
+ searchSelectedIndex.value = null;
+ rawSearchQuery.value = (ev.target as HTMLInputElement).value;
+}
+
+function searchOnKeyDown(ev: KeyboardEvent) {
+ if (ev.isComposing) return;
+
+ if (ev.key === 'Enter' && searchSelectedIndex.value != null) {
+ ev.preventDefault();
+ router.push(searchResult.value[searchSelectedIndex.value].path + '#' + searchResult.value[searchSelectedIndex.value].id);
+ } else if (ev.key === 'ArrowDown') {
+ ev.preventDefault();
+ const current = searchSelectedIndex.value ?? -1;
+ searchSelectedIndex.value = current + 1 >= searchResult.value.length ? 0 : current + 1;
+ } else if (ev.key === 'ArrowUp') {
+ ev.preventDefault();
+ const current = searchSelectedIndex.value ?? 0;
+ searchSelectedIndex.value = current - 1 < 0 ? searchResult.value.length - 1 : current - 1;
+ }
+
+ if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') {
+ nextTick(() => {
+ if (!rootEl.value) return;
+ const selectedEl = rootEl.value.querySelector<HTMLElement>('.searchResultItem.selected');
+ if (selectedEl != null) {
+ const scrollContainer = getScrollContainer(selectedEl);
+ if (!scrollContainer) return;
+ scrollContainer.scrollTo({
+ top: selectedEl.offsetTop - scrollContainer.clientHeight / 2 + selectedEl.clientHeight / 2,
+ behavior: 'instant',
+ });
+ }
+ });
+ }
+}
</script>
<style lang="scss" scoped>
@@ -184,5 +324,52 @@ defineProps<{
}
}
}
+
+ .searchResultItem {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 9px 16px 9px 8px;
+ border-radius: 9px;
+ font-size: 0.9em;
+
+ &:hover {
+ text-decoration: none;
+ background: var(--MI_THEME-panelHighlight);
+ }
+
+ &.selected {
+ outline: 2px solid var(--MI_THEME-focus);
+ }
+
+ &:focus-visible,
+ &.selected {
+ outline-offset: -2px;
+ }
+
+ &.active {
+ color: var(--MI_THEME-accent);
+ background: var(--MI_THEME-accentedBg);
+ }
+
+ &.danger {
+ color: var(--MI_THEME-error);
+ }
+
+ > .icon {
+ width: 32px;
+ margin-right: 2px;
+ flex-shrink: 0;
+ text-align: center;
+ opacity: 0.8;
+ }
+
+ > .text {
+ white-space: normal;
+ padding-right: 12px;
+ flex-shrink: 1;
+ }
+ }
}
</style>
diff --git a/packages/frontend/src/components/MkSwitch.button.vue b/packages/frontend/src/components/MkSwitch.button.vue
index 581aa4e644..e9a029c993 100644
--- a/packages/frontend/src/components/MkSwitch.button.vue
+++ b/packages/frontend/src/components/MkSwitch.button.vue
@@ -19,7 +19,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { toRefs, Ref } from 'vue';
+import { toRefs } from 'vue';
+import type { Ref } from 'vue';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue
index 5e6029ee40..797e577fa4 100644
--- a/packages/frontend/src/components/MkSwitch.vue
+++ b/packages/frontend/src/components/MkSwitch.vue
@@ -27,7 +27,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { toRefs, Ref } from 'vue';
+import { toRefs } from 'vue';
+import type { Ref } from 'vue';
import XButton from '@/components/MkSwitch.button.vue';
const props = defineProps<{
diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue
index 485d003f93..f819f82923 100644
--- a/packages/frontend/src/components/MkSystemWebhookEditor.vue
+++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue
@@ -92,18 +92,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
+import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
import * as Misskey from 'misskey-js';
-import MkInput from '@/components/MkInput.vue';
-import MkSwitch from '@/components/MkSwitch.vue';
-import {
+import type {
MkSystemWebhookEditorProps,
MkSystemWebhookResult,
SystemWebhookEventType,
} from '@/components/MkSystemWebhookEditor.impl.js';
+import MkInput from '@/components/MkInput.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js';
@@ -114,7 +114,7 @@ type EventType = {
userCreated: boolean;
inactiveModeratorsWarning: boolean;
inactiveModeratorsInvitationOnlyChanged: boolean;
-}
+};
const emit = defineEmits<{
(ev: 'submitted', result: MkSystemWebhookResult): void;
@@ -122,7 +122,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
const props = defineProps<MkSystemWebhookEditorProps>();
diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue
index 87aa046963..9d541c8acb 100644
--- a/packages/frontend/src/components/MkTagCloud.vue
+++ b/packages/frontend/src/components/MkTagCloud.vue
@@ -15,18 +15,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, watch, onBeforeUnmount, ref, shallowRef } from 'vue';
+import { onMounted, watch, onBeforeUnmount, ref, useTemplateRef } from 'vue';
import tinycolor from 'tinycolor2';
const loaded = !!window.TagCanvas;
const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz';
-const computedStyle = getComputedStyle(document.documentElement);
+const computedStyle = getComputedStyle(window.document.documentElement);
const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const available = ref(false);
-const rootEl = shallowRef<HTMLElement | null>(null);
-const canvasEl = shallowRef<HTMLCanvasElement | null>(null);
-const tagsEl = shallowRef<HTMLElement | null>(null);
+const rootEl = useTemplateRef('rootEl');
+const canvasEl = useTemplateRef('canvasEl');
+const tagsEl = useTemplateRef('tagsEl');
const width = ref(300);
watch(available, () => {
@@ -57,7 +57,7 @@ onMounted(() => {
if (loaded) {
available.value = true;
} else {
- document.head.appendChild(Object.assign(document.createElement('script'), {
+ window.document.head.appendChild(Object.assign(window.document.createElement('script'), {
async: true,
src: '/client-assets/tagcanvas.min.js',
})).addEventListener('load', () => available.value = true);
diff --git a/packages/frontend/src/components/MkTagItem.stories.impl.ts b/packages/frontend/src/components/MkTagItem.stories.impl.ts
index 3f243ff651..ac932c8342 100644
--- a/packages/frontend/src/components/MkTagItem.stories.impl.ts
+++ b/packages/frontend/src/components/MkTagItem.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkTagItem from './MkTagItem.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index 9deb6528d1..ca42865f96 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div>
+<div class="_selectable">
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
<div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;">
<textarea
@@ -36,11 +36,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
+import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, useTemplateRef } from 'vue';
import { debounce } from 'throttle-debounce';
+import type { SuggestionType } from '@/utility/autocomplete.js';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js';
+import { Autocomplete } from '@/utility/autocomplete.js';
const props = defineProps<{
modelValue: string | null;
@@ -74,7 +75,7 @@ const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
-const inputEl = shallowRef<HTMLTextAreaElement>();
+const inputEl = useTemplateRef('inputEl');
const preview = ref(false);
let autocompleteWorker: Autocomplete | null = null;
diff --git a/packages/frontend/src/components/MkThemePreview.vue b/packages/frontend/src/components/MkThemePreview.vue
new file mode 100644
index 0000000000..5b180b3680
--- /dev/null
+++ b/packages/frontend/src/components/MkThemePreview.vue
@@ -0,0 +1,96 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<svg
+ version="1.1"
+ viewBox="0 0 203.2 152.4"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+>
+ <g fill-rule="evenodd">
+ <rect width="203.2" height="152.4" :fill="themeVariables.bg" stroke-width=".26458" />
+ <rect width="65.498" height="152.4" :fill="themeVariables.panel" stroke-width=".26458" />
+ <rect x="65.498" width="137.7" height="40.892" :fill="themeVariables.acrylicBg" stroke-width=".265" />
+ <path transform="scale(.26458)" d="m439.77 247.19c-43.673 0-78.832 35.157-78.832 78.83v249.98h407.06v-328.81z" :fill="themeVariables.panel" />
+ </g>
+ <circle cx="32.749" cy="83.054" r="21.132" :fill="themeVariables.accentedBg" stroke-dasharray="0.319256, 0.319256" stroke-width=".15963" style="paint-order:stroke fill markers" />
+ <circle cx="136.67" cy="106.76" r="23.876" :fill="themeVariables.fg" fill-opacity="0.5" stroke-dasharray="0.352425, 0.352425" stroke-width=".17621" style="paint-order:stroke fill markers" />
+ <g :fill="themeVariables.fg" fill-rule="evenodd" stroke-width=".26458">
+ <rect x="171.27" y="87.815" width="48.576" height="6.8747" ry="3.4373"/>
+ <rect x="171.27" y="105.09" width="48.576" height="6.875" ry="3.4375"/>
+ <rect x="171.27" y="121.28" width="48.576" height="6.875" ry="3.4375"/>
+ <rect x="171.27" y="137.47" width="48.576" height="6.875" ry="3.4375"/>
+ </g>
+ <path d="m65.498 40.892h137.7" :stroke="themeVariables.divider" stroke-width="0.75" />
+ <g transform="matrix(.60823 0 0 .60823 25.45 75.755)" fill="none" :stroke="themeVariables.accent" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+ <path d="m0 0h24v24h-24z" fill="none" stroke="none" />
+ <path d="m5 12h-2l9-9 9 9h-2" />
+ <path d="m5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7" />
+ <path d="m9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6" />
+ </g>
+ <g transform="matrix(.61621 0 0 .61621 25.354 117.92)" fill="none" :stroke="themeVariables.fg" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+ <path d="m0 0h24v24h-24z" fill="none" stroke="none" />
+ <path d="m10 5a2 2 0 1 1 4 0 7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2-3v-3a7 7 0 0 1 4-6" />
+ <path d="m9 17v1a3 3 0 0 0 6 0v-1" />
+ </g>
+ <image x="20.948" y="18.388" width="23.602" height="23.602" image-rendering="optimizeSpeed" preserveAspectRatio="xMidYMid meet" v-bind="{ 'xlink:href': instance.iconUrl || '/favicon.ico' }" />
+</svg>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import { instance } from '@/instance.js';
+import { compile } from '@/theme.js';
+import type { Theme } from '@/theme.js';
+import { deepClone } from '@/utility/clone.js';
+import lightTheme from '@@/themes/_light.json5';
+import darkTheme from '@@/themes/_dark.json5';
+
+const props = defineProps<{
+ theme: Theme;
+}>();
+
+const themeVariables = ref<{
+ bg: string;
+ acrylicBg: string;
+ panel: string;
+ fg: string;
+ divider: string;
+ accent: string;
+ accentedBg: string;
+}>({
+ bg: 'var(--MI_THEME-bg)',
+ acrylicBg: 'var(--MI_THEME-acrylicBg)',
+ panel: 'var(--MI_THEME-panel)',
+ fg: 'var(--MI_THEME-fg)',
+ divider: 'var(--MI_THEME-divider)',
+ accent: 'var(--MI_THEME-accent)',
+ accentedBg: 'var(--MI_THEME-accentedBg)',
+});
+
+watch(() => props.theme, (theme) => {
+ if (theme == null) return;
+
+ const _theme = deepClone(theme);
+
+ if (_theme?.base != null) {
+ const base = [lightTheme, darkTheme].find(x => x.id === _theme.base);
+ if (base) _theme.props = Object.assign({}, base.props, _theme.props);
+ }
+
+ const compiled = compile(_theme);
+
+ themeVariables.value = {
+ bg: compiled.bg ?? 'var(--MI_THEME-bg)',
+ acrylicBg: compiled.acrylicBg ?? 'var(--MI_THEME-acrylicBg)',
+ panel: compiled.panel ?? 'var(--MI_THEME-panel)',
+ fg: compiled.fg ?? 'var(--MI_THEME-fg)',
+ divider: compiled.divider ?? 'var(--MI_THEME-divider)',
+ accent: compiled.accent ?? 'var(--MI_THEME-accent)',
+ accentedBg: compiled.accentedBg ?? 'var(--MI_THEME-accentedBg)',
+ };
+}, { immediate: true });
+</script>
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 7a9abab62e..15b9da7655 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
v-if="paginationQuery"
ref="tlComponent"
:pagination="paginationQuery"
- :noGap="!defaultStore.state.showGapBetweenNotesInTimeline"
+ :noGap="!prefer.s.showGapBetweenNotesInTimeline"
@queue="emit('queue', $event)"
@status="prComponent?.setDisabled($event)"
/>
@@ -17,17 +17,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
+import { computed, watch, onUnmounted, provide, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import type { BasicTimelineType } from '@/timelines.js';
+import type { Paging } from '@/components/MkPagination.vue';
import MkNotes from '@/components/MkNotes.vue';
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
import { useStream } from '@/stream.js';
-import * as sound from '@/scripts/sound.js';
-import { $i } from '@/account.js';
+import * as sound from '@/utility/sound.js';
+import { $i } from '@/i.js';
import { instance } from '@/instance.js';
-import { defaultStore } from '@/store.js';
-import { Paging } from '@/components/MkPagination.vue';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
@@ -59,19 +59,19 @@ provide('tl_withSensitive', computed(() => props.withSensitive));
provide('inChannel', computed(() => props.src === 'channel'));
type TimelineQueryType = {
- antennaId?: string,
- withRenotes?: boolean,
- withReplies?: boolean,
- withFiles?: boolean,
- withBots?: boolean,
- visibility?: string,
- listId?: string,
- channelId?: string,
- roleId?: string
-}
+ antennaId?: string,
+ withRenotes?: boolean,
+ withReplies?: boolean,
+ withFiles?: boolean,
+ withBots?: boolean,
+ visibility?: string,
+ listId?: string,
+ channelId?: string,
+ roleId?: string
+};
-const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
-const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();
+const prComponent = useTemplateRef('prComponent');
+const tlComponent = useTemplateRef('tlComponent');
let tlNotesCount = 0;
@@ -266,7 +266,7 @@ function updatePaginationQuery() {
}
function refreshEndpointAndChannel() {
- if (!defaultStore.state.disableStreamingTimeline) {
+ if (!prefer.s.disableStreamingTimeline) {
disconnectChannel();
connectChannel();
}
diff --git a/packages/frontend/src/components/MkToast.vue b/packages/frontend/src/components/MkToast.vue
index 38b537cbc9..571835432e 100644
--- a/packages/frontend/src/components/MkToast.vue
+++ b/packages/frontend/src/components/MkToast.vue
@@ -6,10 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_toast_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_toast_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_toast_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_toast_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_toast_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_toast_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_toast_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_toast_leaveTo : ''"
appear @afterLeave="emit('closed')"
>
<div v-if="showing" class="_acrylic" :class="$style.root" :style="{ zIndex }">
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as os from '@/os.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
withDefaults(defineProps<{
message: string;
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue
index 73aef68964..b449155edb 100644
--- a/packages/frontend/src/components/MkTokenGenerateWindow.vue
+++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue
@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef, ref } from 'vue';
+import { useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from './MkInput.vue';
import MkSwitch from './MkSwitch.vue';
@@ -55,7 +55,7 @@ import MkButton from './MkButton.vue';
import MkInfo from './MkInfo.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
-import { iAmAdmin } from '@/account.js';
+import { iAmAdmin } from '@/i.js';
const props = withDefaults(defineProps<{
title?: string | null;
@@ -77,10 +77,10 @@ const emit = defineEmits<{
const defaultPermissions = Misskey.permissions.filter(p => !p.startsWith('read:admin') && !p.startsWith('write:admin'));
const adminPermissions = Misskey.permissions.filter(p => p.startsWith('read:admin') || p.startsWith('write:admin'));
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
const name = ref(props.initialName);
-const permissionSwitches = ref(<Record<(typeof Misskey.permissions)[number], boolean>>{});
-const permissionSwitchesForAdmin = ref(<Record<(typeof Misskey.permissions)[number], boolean>>{});
+const permissionSwitches = ref({} as Record<(typeof Misskey.permissions)[number], boolean>);
+const permissionSwitchesForAdmin = ref({} as Record<(typeof Misskey.permissions)[number], boolean>);
if (props.initialPermissions) {
for (const kind of props.initialPermissions) {
diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue
index 22e74aa6d1..955c24b6ef 100644
--- a/packages/frontend/src/components/MkTooltip.vue
+++ b/packages/frontend/src/components/MkTooltip.vue
@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_tooltip_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_tooltip_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_tooltip_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_tooltip_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_tooltip_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_tooltip_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_tooltip_leaveTo : ''"
appear @afterLeave="emit('closed')"
>
<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
@@ -23,10 +23,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, shallowRef } from 'vue';
+import { nextTick, onMounted, onUnmounted, useTemplateRef } from 'vue';
import * as os from '@/os.js';
-import { calcPopupPosition } from '@/scripts/popup-position.js';
-import { defaultStore } from '@/store.js';
+import { calcPopupPosition } from '@/utility/popup-position.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
showing: boolean;
@@ -51,7 +51,7 @@ const emit = defineEmits<{
// タイミングによっては最初から showing = false な場合があり、その場合に closed 扱いにしないと永久にDOMに残ることになる
if (!props.showing) emit('closed');
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
const zIndex = os.claimZIndex('high');
function setPosition() {
diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue
index 53b8db38b2..ddb22f7d8c 100644
--- a/packages/frontend/src/components/MkTutorialDialog.Note.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue
@@ -38,7 +38,7 @@ import * as Misskey from 'misskey-js';
import { ref, reactive } from 'vue';
import { i18n } from '@/i18n.js';
import { globalEvents } from '@/events.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import MkNote from '@/components/MkNote.vue';
const props = defineProps<{
diff --git a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
index e1fc3e4f26..df184ec315 100644
--- a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
@@ -31,7 +31,7 @@ import MkPostForm from '@/components/MkPostForm.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkNote from '@/components/MkNote.vue';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
const emit = defineEmits<{
(ev: 'succeeded'): void;
diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue
index 11d7c8dc4d..3e91baada4 100644
--- a/packages/frontend/src/components/MkTutorialDialog.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.vue
@@ -148,7 +148,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, watch } from 'vue';
+import { ref, useTemplateRef, watch } from 'vue';
+import { host } from '@@/js/config.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import XNote from '@/components/MkTutorialDialog.Note.vue';
@@ -158,8 +159,7 @@ import XSensitive from '@/components/MkTutorialDialog.Sensitive.vue';
import MkAnimBg from '@/components/MkAnimBg.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { host } from '@@/js/config.js';
-import { claimAchievement } from '@/scripts/achievements.js';
+import { claimAchievement } from '@/utility/achievements.js';
import * as os from '@/os.js';
const props = defineProps<{
@@ -170,7 +170,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
const page = ref(props.initialPage ?? 0);
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index 7cafb1b0af..3685be6359 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -15,15 +15,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
+import { version } from '@@/js/config.js';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import MkSparkle from '@/components/MkSparkle.vue';
-import { version } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
-import { confetti } from '@/scripts/confetti.js';
+import { confetti } from '@/utility/confetti.js';
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
const whatIsNew = () => {
modal.value?.close();
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index 922fa86072..11071f2f60 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<iframe
v-if="player.url.startsWith('http://') || player.url.startsWith('https://')"
- sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin"
+ sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-storage-access-by-user-activation allow-same-origin"
scrolling="no"
:allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')"
:class="$style.playerIframe"
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin"
scrolling="no"
:style="{ position: 'relative', width: '100%', height: `${tweetHeight}px`, border: 0 }"
- :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"
+ :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${store.s.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"
></iframe>
</div>
<div :class="$style.action">
@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-else-if="theNote" :class="[$style.link, { [$style.compact]: compact }]"><XNoteSimple :note="theNote" :class="$style.body"/></div>
<div v-else-if="!hidePreview">
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url" @click.prevent="self ? true : warningExternalWebsite(url)" @click.stop>
- <div v-if="thumbnail && !sensitive" :class="$style.thumbnail" :style="defaultStore.state.dataSaver.urlPreview ? '' : `background-image: url('${thumbnail}')`">
+ <div v-if="thumbnail && !sensitive" :class="$style.thumbnail" :style="prefer.s.dataSaver.urlPreview ? '' : `background-image: url('${thumbnail}')`">
</div>
<article :class="$style.body">
<header :class="$style.header">
@@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, onDeactivated, onUnmounted, ref, watch } from 'vue';
+import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue';
import { url as local } from '@@/js/config.js';
import { versatileLang } from '@@/js/intl-const.js';
import * as Misskey from 'misskey-js';
@@ -98,12 +98,13 @@ import type MkNoteSimple from '@/components/MkNoteSimple.vue';
import type SkNoteSimple from '@/components/SkNoteSimple.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { deviceKind } from '@/utility/device-kind.js';
import MkButton from '@/components/MkButton.vue';
-import { transformPlayerUrl } from '@/scripts/player-url-transform.js';
-import { defaultStore } from '@/store.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
+import { transformPlayerUrl } from '@/utility/player-url-transform.js';
+import { store } from '@/store.js';
+import { prefer } from '@/preferences.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { warningExternalWebsite } from '@/utility/warning-external-website.js';
const XNoteSimple = defineAsyncComponent<typeof MkNoteSimple | typeof SkNoteSimple>(() =>
defaultStore.state.noteDesign === 'misskey'
@@ -301,6 +302,7 @@ onUnmounted(() => {
box-shadow: 0 0 0 1px var(--MI_THEME-divider);
border-radius: var(--MI-radius-sm);
overflow: clip;
+ text-align: left;
&:hover {
text-decoration: none;
diff --git a/packages/frontend/src/components/MkUrlPreviewPopup.vue b/packages/frontend/src/components/MkUrlPreviewPopup.vue
index e972973dba..fd36d6a82b 100644
--- a/packages/frontend/src/components/MkUrlPreviewPopup.vue
+++ b/packages/frontend/src/components/MkUrlPreviewPopup.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root" :style="{ zIndex, top: top + 'px', left: left + 'px' }">
- <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" @afterLeave="emit('closed')">
+ <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" @afterLeave="emit('closed')">
<MkUrlPreview v-if="showing" class="_popup _shadow" :url="url" :showActions="false"/>
</Transition>
</div>
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onMounted, ref } from 'vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
import * as os from '@/os.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
showing: boolean;
diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
index fe499fabbf..34e86444ad 100644
--- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
+++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
@@ -56,13 +56,13 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
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; }
+type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; };
const props = defineProps<{
user: Misskey.entities.User,
diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue
index ce28f6ec5e..e2e42e78f3 100644
--- a/packages/frontend/src/components/MkUserCardMini.vue
+++ b/packages/frontend/src/components/MkUserCardMini.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'misskey-js';
import { onMounted, ref } from 'vue';
import MkMiniChart from '@/components/MkMiniChart.vue';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
import { acct } from '@/filters/user.js';
const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index a6bbacacee..0c59c7748a 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_panel" :class="$style.root">
- <div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(user.bannerUrl) : user.bannerUrl})` : ''"></div>
+ <div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${prefer.s.disableShowingAnimatedImages ? getStaticImageUrl(user.bannerUrl) : user.bannerUrl})` : ''"></div>
<MkAvatar :class="$style.avatar" :user="user" indicator/>
<div :class="$style.title">
<MkA :class="$style.name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
@@ -47,10 +47,10 @@ import MkFollowButton from '@/components/MkFollowButton.vue';
import number from '@/filters/number.js';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
-import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
-import { defaultStore } from '@/store.js';
+import { $i } from '@/i.js';
+import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/utility/isFfVisibleForMe.js';
+import { getStaticImageUrl } from '@/utility/media-proxy.js';
+import { prefer } from '@/preferences.js';
defineProps<{
user: Misskey.entities.UserDetailed;
diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue
index 8dc01a08ab..785efe6e33 100644
--- a/packages/frontend/src/components/MkUserList.vue
+++ b/packages/frontend/src/components/MkUserList.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination :pagination="pagination" :displayLimit="50">
<template #empty>
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.noUsers }}</div>
</div>
</template>
@@ -21,8 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
+import type { Paging } from '@/components/MkPagination.vue';
import MkUserInfo from '@/components/MkUserInfo.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue
index 73e38bef09..29d6736ffc 100644
--- a/packages/frontend/src/components/MkUserPopup.vue
+++ b/packages/frontend/src/components/MkUserPopup.vue
@@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_popup_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_popup_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_popup_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_popup_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_popup_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_popup_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_popup_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_popup_leaveTo : ''"
appear @afterLeave="emit('closed')"
>
<div v-if="showing" :class="$style.root" class="_popup _shadow" :style="{ zIndex, top: top + 'px', left: left + 'px' }" @mouseover="() => { emit('mouseover'); }" @mouseleave="() => { emit('mouseleave'); }">
<div v-if="user != null">
- <div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(user.bannerUrl) : user.bannerUrl})` : ''">
+ <div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${prefer.s.disableShowingAnimatedImages ? getStaticImageUrl(user.bannerUrl) : user.bannerUrl})` : ''">
<span v-if="$i && $i.id != user.id && user.isFollowed && user.isFollowing" :class="$style.followed">{{ i18n.ts.mutuals }}</span>
<span v-else-if="$i && $i.id != user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
<span v-else-if="$i && $i.id != user.id && user.isFollowing" :class="$style.followed">{{ i18n.ts.following }}</span>
@@ -74,14 +74,14 @@ import * as Misskey from 'misskey-js';
import MkFollowButton from '@/components/MkFollowButton.vue';
import { userPage } from '@/filters/user.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { getUserMenu } from '@/scripts/get-user-menu.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { getUserMenu } from '@/utility/get-user-menu.js';
import number from '@/filters/number.js';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
-import { $i } from '@/account.js';
-import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
+import { prefer } from '@/preferences.js';
+import { $i } from '@/i.js';
+import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/utility/isFfVisibleForMe.js';
+import { getStaticImageUrl } from '@/utility/media-proxy.js';
const props = defineProps<{
showing: boolean;
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index 120f19cb7f..34ed1f8fc2 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -61,17 +61,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, computed, shallowRef } from 'vue';
+import { onMounted, ref, computed, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
+import { host as currentHost, hostname } from '@@/js/config.js';
import MkInput from '@/components/MkInput.vue';
import FormSplit from '@/components/form/split.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { instance } from '@/instance.js';
-import { host as currentHost, hostname } from '@@/js/config.js';
const emit = defineEmits<{
(ev: 'ok', selected: Misskey.entities.UserDetailed): void;
@@ -94,7 +94,7 @@ const host = ref('');
const users = ref<Misskey.entities.UserLite[]>([]);
const recentUsers = ref<Misskey.entities.UserDetailed[]>([]);
const selected = ref<Misskey.entities.UserLite | null>(null);
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
function search() {
if (username.value === '' && host.value === '') {
@@ -128,10 +128,10 @@ async function ok() {
dialogEl.value?.close();
// 最近使ったユーザー更新
- let recents = defaultStore.state.recentlyUsedUsers;
+ let recents = store.s.recentlyUsedUsers;
recents = recents.filter(x => x !== selected.value?.id);
recents.unshift(selected.value.id);
- defaultStore.set('recentlyUsedUsers', recents.splice(0, 16));
+ store.set('recentlyUsedUsers', recents.splice(0, 16));
}
function cancel() {
@@ -141,7 +141,7 @@ function cancel() {
onMounted(() => {
misskeyApi('users/show', {
- userIds: defaultStore.state.recentlyUsedUsers,
+ userIds: store.s.recentlyUsedUsers,
}).then(foundUsers => {
let _users = foundUsers;
_users = _users.filter((u) => {
@@ -198,7 +198,7 @@ onMounted(() => {
font-size: 14px;
&:hover {
- background: var(--MI_THEME-X7);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
&.selected {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
index 638bfb4372..52467893a0 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
index 5153c06139..67a06c70db 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
@@ -38,7 +38,8 @@ import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
import MkFolder from '@/components/MkFolder.vue';
import XUser from '@/components/MkUserSetupDialog.User.vue';
-import MkPagination, { type Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
const pinnedUsers: Paging = {
endpoint: 'pinned-users',
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
index 2a7947c6f8..0ada259d3f 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkUserSetupDialog_Privacy from './MkUserSetupDialog.Privacy.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
index fb4a2b1c78..5e68bfeff3 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
@@ -41,7 +41,7 @@ import { i18n } from '@/i18n.js';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkFolder from '@/components/MkFolder.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
const isLocked = ref(false);
const hideOnlineStatus = ref(true);
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
index c6088a5ae3..cefd48cb01 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkUserSetupDialog_Profile from './MkUserSetupDialog.Profile.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
index 7cb48f6afb..30925b854c 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
@@ -37,11 +37,11 @@ import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import FormSlot from '@/components/form/slot.vue';
import MkInfo from '@/components/MkInfo.vue';
-import { chooseFileFromPc } from '@/scripts/select-file.js';
+import { chooseFileFromPc } from '@/utility/select-file.js';
import * as os from '@/os.js';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const name = ref($i.name ?? '');
const description = ref($i.description ?? '');
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
index f0206e0cb4..b424632bdc 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../.storybook/fakes.js';
import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue
index 4c4f4989c5..dfaf21a6b8 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.User.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue
@@ -29,7 +29,7 @@ import * as Misskey from 'misskey-js';
import { ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
const props = defineProps<{
user: Misskey.entities.UserDetailed;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
index 3f5ae734bd..751391c2d8 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index b7261129ef..767f5c591a 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -128,7 +128,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, watch, nextTick, defineAsyncComponent } from 'vue';
+import { ref, useTemplateRef, watch, nextTick, defineAsyncComponent } from 'vue';
+import { host } from '@@/js/config.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
@@ -137,22 +138,20 @@ import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
import MkAnimBg from '@/components/MkAnimBg.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { host } from '@@/js/config.js';
import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
import * as os from '@/os.js';
const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
-
-// eslint-disable-next-line vue/no-setup-props-reactivity-loss
-const page = ref(defaultStore.state.accountSetupWizard);
+const dialog = useTemplateRef('dialog');
+
+const page = ref(store.s.accountSetupWizard);
watch(page, () => {
- defaultStore.set('accountSetupWizard', page.value);
+ store.set('accountSetupWizard', page.value);
});
async function close(skip: boolean) {
@@ -165,11 +164,11 @@ async function close(skip: boolean) {
}
dialog.value?.close();
- defaultStore.set('accountSetupWizard', -1);
+ store.set('accountSetupWizard', -1);
}
function setupComplete() {
- defaultStore.set('accountSetupWizard', -1);
+ store.set('accountSetupWizard', -1);
dialog.value?.close();
}
@@ -194,7 +193,7 @@ async function later(later: boolean) {
}
dialog.value?.close();
- defaultStore.set('accountSetupWizard', 0);
+ store.set('accountSetupWizard', 0);
}
</script>
diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index 5624abbd33..659a82d8f2 100644
--- a/packages/frontend/src/components/MkVisibilityPicker.vue
+++ b/packages/frontend/src/components/MkVisibilityPicker.vue
@@ -42,12 +42,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { nextTick, shallowRef, ref } from 'vue';
+import { nextTick, useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkModal from '@/components/MkModal.vue';
import { i18n } from '@/i18n.js';
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
const props = withDefaults(defineProps<{
currentVisibility: typeof Misskey.noteVisibilities[number];
diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
index d098dad9a1..79c9e739c4 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
@@ -13,19 +13,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef, ref, nextTick } from 'vue';
+import { onMounted, useTemplateRef, ref, nextTick } from 'vue';
import { Chart } from 'chart.js';
import gradient from 'chartjs-plugin-gradient';
import tinycolor from 'tinycolor2';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { initChart } from '@/scripts/init-chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { initChart } from '@/utility/init-chart.js';
initChart();
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const chartEl = useTemplateRef('chartEl');
const now = new Date();
let chartInstance: Chart | null = null;
const chartLimit = 30;
@@ -59,9 +59,9 @@ async function renderChart() {
await nextTick();
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
- const computedStyle = getComputedStyle(document.documentElement);
+ const computedStyle = getComputedStyle(window.document.documentElement);
const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
const colorRead = accent;
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index a1c860c473..9a87d480e3 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -66,7 +66,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import MkInfo from '@/components/MkInfo.vue';
import { instanceName } from '@@/js/config.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import MkNumber from '@/components/MkNumber.vue';
diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue
index 34fa6b0723..282da00ee1 100644
--- a/packages/frontend/src/components/MkWaitingDialog.vue
+++ b/packages/frontend/src/components/MkWaitingDialog.vue
@@ -14,10 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { watch, shallowRef } from 'vue';
+import { watch, useTemplateRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
const props = defineProps<{
success: boolean;
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index 2953f656d4..e5ac791d0b 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_window_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_window_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_window_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_window_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_window_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_window_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_window_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_window_leaveTo : ''"
appear
@afterLeave="emit('closed')"
>
@@ -53,12 +53,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue';
+import { onBeforeUnmount, onMounted, provide, useTemplateRef, ref } from 'vue';
import type { MenuItem } from '@/types/menu.js';
-import contains from '@/scripts/contains.js';
+import contains from '@/utility/contains.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
type WindowButton = {
title: string;
@@ -114,7 +114,7 @@ const emit = defineEmits<{
provide('inWindow', true);
-const rootEl = shallowRef<HTMLElement | null>();
+const rootEl = useTemplateRef('rootEl');
const showing = ref(true);
let beforeClickedAt = 0;
const maximized = ref(false);
@@ -240,7 +240,7 @@ function onHeaderMousedown(evt: MouseEvent | TouchEvent) {
const main = rootEl.value;
if (main == null) return;
- if (!contains(main, document.activeElement)) main.focus();
+ if (!contains(main, window.document.activeElement)) main.focus();
const position = main.getBoundingClientRect();
diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue
index 1122976436..ab62a5113d 100644
--- a/packages/frontend/src/components/MkYouTubePlayer.vue
+++ b/packages/frontend/src/components/MkYouTubePlayer.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<div class="poamfof">
- <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
<div v-if="player.url && (player.url.startsWith('http://') || player.url.startsWith('https://'))" class="player">
<iframe v-if="!fetching" :src="transformPlayerUrl(player.url)" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
</div>
@@ -25,10 +25,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import MkWindow from '@/components/MkWindow.vue';
import { versatileLang } from '@@/js/intl-const.js';
-import { transformPlayerUrl } from '@/scripts/player-url-transform.js';
-import { defaultStore } from '@/store.js';
+import MkWindow from '@/components/MkWindow.vue';
+import { transformPlayerUrl } from '@/utility/player-url-transform.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
url: string;
diff --git a/packages/frontend/src/components/global/MkA.stories.impl.ts b/packages/frontend/src/components/global/MkA.stories.impl.ts
index 02e5a7f98c..deb2b8a52b 100644
--- a/packages/frontend/src/components/global/MkA.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkA.stories.impl.ts
@@ -5,9 +5,9 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkA from './MkA.vue';
-import { tick } from '@/scripts/test-utils.js';
+import { tick } from '@/utility/test-utils.js';
export const Default = {
render(args) {
return {
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 23f049ebb4..9a51acc9dc 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -14,12 +14,12 @@ export type MkABehavior = 'window' | 'browser' | null;
</script>
<script lang="ts" setup>
-import { computed, inject, shallowRef } from 'vue';
-import * as os from '@/os.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { computed, inject, useTemplateRef } from 'vue';
import { url } from '@@/js/config.js';
+import * as os from '@/os.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const props = withDefaults(defineProps<{
to: string;
@@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{
const behavior = props.behavior ?? inject<MkABehavior>('linkNavigationBehavior', null);
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
defineExpose({ $el: el });
@@ -87,7 +87,7 @@ function openWindow() {
function nav(ev: MouseEvent) {
if (behavior === 'browser') {
- location.href = props.to;
+ window.location.href = props.to;
return;
}
diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
index 04960ec60c..02fc835709 100644
--- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkAcct from './MkAcct.vue';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index 2f4141b901..ff794d9b6e 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<span>
<span>@{{ user.username }}</span>
- <span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
+ <span v-if="user.host || detail" style="opacity: 0.5;">@{{ user.host || host }}</span>
</span>
</template>
@@ -14,7 +14,6 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'misskey-js';
import { toUnicode } from 'punycode.js';
import { host as hostRaw } from '@@/js/config.js';
-import { defaultStore } from '@/store.js';
defineProps<{
user: Misskey.entities.UserLite;
diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts
index 8c0b7ef52f..c5a928b5cf 100644
--- a/packages/frontend/src/components/global/MkAd.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkAd from './MkAd.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index fc6c64d2aa..b29bbb6392 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -45,9 +45,10 @@ import { url as local, host } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import MkButton from '@/components/MkButton.vue';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
+import { prefer } from '@/preferences.js';
type Ad = (typeof instance)['ads'][number];
@@ -66,7 +67,7 @@ const choseAd = (): Ad | null => {
return props.specify;
}
- const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? {
+ const allAds = instance.ads.map(ad => store.s.mutedAds.includes(ad.id) ? {
...ad,
ratio: 0,
} : ad);
@@ -107,12 +108,12 @@ const chosen = ref(choseAd());
const self = computed(() => chosen.value?.url.startsWith(local));
-const shouldHide = ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null));
+const shouldHide = ref(!prefer.s.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null));
function reduceFrequency(): void {
if (chosen.value == null) return;
- if (defaultStore.state.mutedAds.includes(chosen.value.id)) return;
- defaultStore.push('mutedAds', chosen.value.id);
+ if (store.s.mutedAds.includes(chosen.value.id)) return;
+ store.push('mutedAds', chosen.value.id);
os.success();
chosen.value = choseAd();
showMenu.value = false;
diff --git a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
index 9d2de9f0be..84221842e9 100644
--- a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkAvatar from './MkAvatar.vue';
const common = {
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index 1f78b068a2..603b6f12a1 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -35,6 +35,8 @@ SPDX-License-Identifier: AGPL-3.0-only
zIndex: getDecorationZIndex(decoration),
}"
alt=""
+ draggable="false"
+ style="-webkit-user-drag: none;"
>
</template>
</component>
@@ -46,14 +48,13 @@ import * as Misskey from 'misskey-js';
import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js';
import MkImgWithBlurhash from '../MkImgWithBlurhash.vue';
import MkA from './MkA.vue';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
+import { getStaticImageUrl } from '@/utility/media-proxy.js';
import { acct, userPage } from '@/filters/user.js';
import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
-const animation = ref(defaultStore.state.animation);
-const squareAvatars = ref(defaultStore.state.squareAvatars);
-const useBlurEffect = ref(defaultStore.state.useBlurEffect);
+const animation = ref(prefer.s.animation);
+const squareAvatars = ref(prefer.s.squareAvatars);
const props = withDefaults(defineProps<{
user: Misskey.entities.User;
@@ -76,7 +77,7 @@ const emit = defineEmits<{
(ev: 'click', v: MouseEvent): void;
}>();
-const showDecoration = props.forceShowDecoration || defaultStore.state.showAvatarDecorations;
+const showDecoration = props.forceShowDecoration || prefer.s.showAvatarDecorations;
const bound = computed(() => props.link
? { to: userPage(props.user), target: props.target }
@@ -84,7 +85,7 @@ const bound = computed(() => props.link
const url = computed(() => {
if (props.user.avatarUrl == null) return null;
- if (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) return getStaticImageUrl(props.user.avatarUrl);
+ if (prefer.s.disableShowingAnimatedImages || prefer.s.dataSaver.avatar) return getStaticImageUrl(props.user.avatarUrl);
return props.user.avatarUrl;
});
@@ -94,7 +95,7 @@ function onClick(ev: MouseEvent): void {
}
function getDecorationUrl(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
- if (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) return getStaticImageUrl(decoration.url);
+ if (prefer.s.disableShowingAnimatedImages || prefer.s.dataSaver.avatar) return getStaticImageUrl(decoration.url);
return decoration.url;
}
diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
index e15dcba760..15ae489ff8 100644
--- a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkCondensedLine from './MkCondensedLine.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts b/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
index 9e6177045d..eded13b686 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkCustomEmoji from './MkCustomEmoji.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index 90fa522f3d..70af2c7962 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -9,6 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
src="/client-assets/dummy.png"
:title="alt"
+ draggable="false"
+ style="-webkit-user-drag: none;"
/>
<span v-else-if="errored">:{{ customEmojiName }}:</span>
<img
@@ -18,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:alt="alt"
:title="alt"
decoding="async"
+ draggable="false"
@error="errored = true"
@load="errored = false"
@click="onClick"
@@ -27,16 +30,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
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 { getProxiedImageUrl, getStaticImageUrl } from '@/utility/media-proxy.js';
import { customEmojisMap } from '@/custom-emojis.js';
import * as os from '@/os.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 { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import * as sound from '@/utility/sound.js';
import { i18n } from '@/i18n.js';
import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
name: string;
@@ -77,7 +80,7 @@ const url = computed(() => {
false,
true,
);
- return defaultStore.reactiveState.disableShowingAnimatedImages.value
+ return prefer.s.disableShowingAnimatedImages
? getStaticImageUrl(proxied)
: proxied;
});
@@ -99,7 +102,6 @@ function onClick(ev: MouseEvent) {
icon: 'ti ti-copy',
action: () => {
copyToClipboard(`:${props.name}:`);
- os.success();
},
});
@@ -159,6 +161,7 @@ async function edit(name: string) {
.root {
height: 2em;
vertical-align: middle;
+ -webkit-user-drag: none;
transition: transform 0.2s ease;
&:hover {
diff --git a/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts b/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
index 6a8fcf4fe3..dafdcbd13f 100644
--- a/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import MkEllipsis from './MkEllipsis.vue';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkEmoji.stories.impl.ts b/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
index 309c015757..1a394ca6bb 100644
--- a/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkEmoji from './MkEmoji.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue
index bd9b1d665a..432de24478 100644
--- a/packages/frontend/src/components/global/MkEmoji.vue
+++ b/packages/frontend/src/components/global/MkEmoji.vue
@@ -12,12 +12,12 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, inject } from 'vue';
import { colorizeEmoji, getEmojiName } from '@@/js/emojilist.js';
import { char2fluentEmojiFilePath, char2twemojiFilePath, char2tossfaceFilePath } from '@@/js/emoji-base.js';
-import { defaultStore } from '@/store.js';
+import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import * as sound from '@/scripts/sound.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import * as sound from '@/utility/sound.js';
import { i18n } from '@/i18n.js';
-import type { MenuItem } from '@/types/menu.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
emoji: string;
@@ -27,9 +27,9 @@ const props = defineProps<{
const react = inject<((name: string) => void) | null>('react', null);
-const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : defaultStore.state.emojiStyle === 'tossface' ? char2tossfaceFilePath : char2fluentEmojiFilePath;
+const char2path = prefer.s.emojiStyle === 'twemoji' ? char2twemojiFilePath : prefer.s.emojiStyle === 'tossface' ? char2tossfaceFilePath : char2fluentEmojiFilePath;
-const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native');
+const useOsNativeEmojis = computed(() => prefer.s.emojiStyle === 'native');
const url = computed(() => char2path(props.emoji));
const colorizedNativeEmoji = computed(() => colorizeEmoji(props.emoji));
@@ -52,7 +52,6 @@ function onClick(ev: MouseEvent) {
icon: 'ti ti-copy',
action: () => {
copyToClipboard(props.emoji);
- os.success();
},
});
diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts
index daef04cd87..e150493a18 100644
--- a/packages/frontend/src/components/global/MkError.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkError.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
import { expect, waitFor } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkError from './MkError.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkError.stories.meta.ts b/packages/frontend/src/components/global/MkError.stories.meta.ts
index cd7fada189..940b445e90 100644
--- a/packages/frontend/src/components/global/MkError.stories.meta.ts
+++ b/packages/frontend/src/components/global/MkError.stories.meta.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Meta } from '@storybook/vue3';
+import type { Meta } from '@storybook/vue3';
import MkError from './MkError.vue';
export const argTypes = {
diff --git a/packages/frontend/src/components/global/MkError.vue b/packages/frontend/src/components/global/MkError.vue
index 77dddaff89..fadfbda7a6 100644
--- a/packages/frontend/src/components/global/MkError.vue
+++ b/packages/frontend/src/components/global/MkError.vue
@@ -4,9 +4,9 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
+<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
<div :class="$style.root">
- <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
+ <img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p>
<MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton>
</div>
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
import { serverErrorImageUrl } from '@/instance.js';
const emit = defineEmits<{
diff --git a/packages/frontend/src/components/global/MkFooterSpacer.vue b/packages/frontend/src/components/global/MkFooterSpacer.vue
deleted file mode 100644
index 1a75855fa1..0000000000
--- a/packages/frontend/src/components/global/MkFooterSpacer.vue
+++ /dev/null
@@ -1,32 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div :class="[$style.spacer, defaultStore.reactiveState.darkMode.value ? $style.dark : $style.light]"></div>
-</template>
-
-<script lang="ts" setup>
-import { defaultStore } from '@/store.js';
-</script>
-
-<style lang="scss" module>
-.spacer {
- box-sizing: border-box;
- padding: 32px;
- margin: 0 auto;
- height: 300px;
- background-clip: content-box;
- background-size: auto auto;
- background-color: rgba(255, 255, 255, 0);
-
- &.light {
- background-image: repeating-linear-gradient(135deg, transparent, transparent 16px, #00000010 16px, #00000010 20px );
- }
-
- &.dark {
- background-image: repeating-linear-gradient(135deg, transparent, transparent 16px, #FFFFFF16 16px, #FFFFFF16 20px );
- }
-}
-</style>
diff --git a/packages/frontend/src/components/global/MkLazy.vue b/packages/frontend/src/components/global/MkLazy.vue
index 29908f303d..c2be975c8c 100644
--- a/packages/frontend/src/components/global/MkLazy.vue
+++ b/packages/frontend/src/components/global/MkLazy.vue
@@ -11,9 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { nextTick, onMounted, onActivated, onBeforeUnmount, ref, shallowRef } from 'vue';
+import { nextTick, onMounted, onActivated, onBeforeUnmount, ref, useTemplateRef } from 'vue';
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
const showing = ref(false);
const emit = defineEmits<{
diff --git a/packages/frontend/src/components/global/MkLoading.stories.impl.ts b/packages/frontend/src/components/global/MkLoading.stories.impl.ts
index c781ad0479..8313f73e4b 100644
--- a/packages/frontend/src/components/global/MkLoading.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkLoading.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import MkLoading from './MkLoading.vue';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkMfm.stories.impl.ts b/packages/frontend/src/components/global/MkMfm.stories.impl.ts
index 1daf7a29cb..98da531ed4 100644
--- a/packages/frontend/src/components/global/MkMfm.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkMfm.stories.impl.ts
@@ -2,8 +2,8 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
-
-import { StoryObj } from '@storybook/vue3';
+
+import type { StoryObj } from '@storybook/vue3';
import { expect, within } from '@storybook/test';
import MkMfm from './MkMfm.js';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkMfm.ts b/packages/frontend/src/components/global/MkMfm.ts
index 9785bc0f07..424150e9a3 100644
--- a/packages/frontend/src/components/global/MkMfm.ts
+++ b/packages/frontend/src/components/global/MkMfm.ts
@@ -3,10 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { VNode, h, defineAsyncComponent, SetupContext } from 'vue';
-import * as mfm from '@transfem-org/sfm-js';
+import { h, defineAsyncComponent } from 'vue';
+import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
+import type { VNode, SetupContext } from 'vue';
+import type { MkABehavior } from '@/components/global/MkA.vue';
import CkFollowMouse from '../CkFollowMouse.vue';
import MkUrl from '@/components/global/MkUrl.vue';
import MkTime from '@/components/global/MkTime.vue';
@@ -18,8 +20,8 @@ import MkCode from '@/components/MkCode.vue';
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 { defaultStore } from '@/store.js';
+import MkA from '@/components/global/MkA.vue';
+import { prefer } from '@/preferences.js';
function safeParseFloat(str: unknown): number | null {
if (typeof str !== 'string' || str === '') return null;
@@ -76,13 +78,12 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
return t.match(/^\-?[0-9.]+s$/) ? t : null;
};
- const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : props.isAnim ? true : false;
-
const validColor = (c: unknown): string | null => {
if (typeof c !== 'string') return null;
return c.match(/^[0-9a-f]{3,6}$/i) ? c : null;
};
+ const useAnim = prefer.s.advancedMfm && prefer.s.animatedMfm;
const isBlock = props.isBlock ?? false;
const SkFormula = defineAsyncComponent(() => import('@/components/SkFormula.vue'));
@@ -192,17 +193,17 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
}
case 'x2': {
return h('span', {
- class: defaultStore.state.advancedMfm ? 'mfm-x2' : '',
+ class: prefer.s.advancedMfm ? 'mfm-x2' : '',
}, genEl(token.children, scale * 2));
}
case 'x3': {
return h('span', {
- class: defaultStore.state.advancedMfm ? 'mfm-x3' : '',
+ class: prefer.s.advancedMfm ? 'mfm-x3' : '',
}, genEl(token.children, scale * 3));
}
case 'x4': {
return h('span', {
- class: defaultStore.state.advancedMfm ? 'mfm-x4' : '',
+ class: prefer.s.advancedMfm ? 'mfm-x4' : '',
}, genEl(token.children, scale * 4));
}
case 'font': {
@@ -282,7 +283,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
}, genEl(token.children, scale));
}
case 'position': {
- if (!defaultStore.state.advancedMfm) break;
+ if (!prefer.s.advancedMfm) break;
const x = safeParseFloat(token.props.args.x) ?? 0;
const y = safeParseFloat(token.props.args.y) ?? 0;
style = `transform: translateX(${x}em) translateY(${y}em);`;
@@ -305,7 +306,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
break;
}
case 'scale': {
- if (!defaultStore.state.advancedMfm) {
+ if (!prefer.s.advancedMfm) {
style = '';
break;
}
diff --git a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
index 1d079edd2c..c9af5f4ea4 100644
--- a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { waitFor } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkPageHeader from './MkPageHeader.vue';
export const Empty = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
index ffa6f13ff6..832c7887cb 100644
--- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
@@ -8,13 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.tabsInner">
<button
v-for="t in tabs" :ref="(el) => tabRefs[t.key] = (el as HTMLElement)" v-tooltip.noDelay="t.title"
- class="_button" :class="[$style.tab, { [$style.active]: t.key != null && t.key === props.tab, [$style.animate]: defaultStore.reactiveState.animation.value }]"
+ class="_button" :class="[$style.tab, { [$style.active]: t.key != null && t.key === props.tab, [$style.animate]: prefer.s.animation }]"
@mousedown="(ev) => onTabMousedown(t, ev)" @click="(ev) => onTabClick(t, ev)"
>
<div :class="$style.tabInner">
<i v-if="t.icon" :class="[$style.tabIcon, t.icon]"></i>
<div
- v-if="!t.iconOnly || (!defaultStore.reactiveState.animation.value && t.key === tab)"
+ v-if="!t.iconOnly || (!prefer.s.animation && t.key === tab)"
:class="$style.tabTitle"
>
{{ t.title }}
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div
ref="tabHighlightEl"
- :class="[$style.tabHighlight, { [$style.animate]: defaultStore.reactiveState.animation.value }]"
+ :class="[$style.tabHighlight, { [$style.animate]: prefer.s.animation }]"
></div>
</div>
</template>
@@ -41,20 +41,20 @@ export type Tab = {
onClick?: (ev: MouseEvent) => void;
} & (
| {
- iconOnly?: false;
- title: string;
- icon?: string;
- }
+ iconOnly?: false;
+ title: string;
+ icon?: string;
+ }
| {
- iconOnly: true;
- icon: string;
- }
+ iconOnly: true;
+ icon: string;
+ }
);
</script>
<script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
-import { defaultStore } from '@/store.js';
+import { nextTick, onMounted, onUnmounted, useTemplateRef, watch } from 'vue';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
tabs?: Tab[];
@@ -69,9 +69,9 @@ const emit = defineEmits<{
(ev: 'tabClick', key: string);
}>();
-const el = shallowRef<HTMLElement | null>(null);
+const el = useTemplateRef('el');
+const tabHighlightEl = useTemplateRef('tabHighlightEl');
const tabRefs: Record<string, HTMLElement | null> = {};
-const tabHighlightEl = shallowRef<HTMLElement | null>(null);
function onTabMousedown(tab: Tab, ev: MouseEvent): void {
// ユーザビリティの観点からmousedown時にはonClickは呼ばない
@@ -170,7 +170,7 @@ onMounted(() => {
if (props.rootEl) {
ro2 = new ResizeObserver((entries, observer) => {
- if (document.body.contains(el.value as HTMLElement)) {
+ if (window.document.body.contains(el.value as HTMLElement)) {
nextTick(() => renderTab());
}
});
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 1a424f349f..54ce0f85eb 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -50,15 +50,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue';
+import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'vue';
import tinycolor from 'tinycolor2';
-import XTabs, { Tab } from './MkPageHeader.tabs.vue';
import { scrollToTop } from '@@/js/scroll.js';
-import { globalEvents } from '@/events.js';
-import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
-import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
+import XTabs from './MkPageHeader.tabs.vue';
+import type { Tab } from './MkPageHeader.tabs.vue';
import type { PageHeaderItem } from '@/types/page-header.js';
-import type { PageMetadata } from '@/scripts/page-metadata.js';
+import type { PageMetadata } from '@/page.js';
+import { globalEvents } from '@/events.js';
+import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
+import { $i } from '@/i.js';
+import { DI } from '@/di.js';
const props = withDefaults(defineProps<{
overridePageMetadata?: PageMetadata;
@@ -79,13 +81,15 @@ const emit = defineEmits<{
const displayBackButton = props.displayBackButton && history.state.key !== 'index' && history.length > 1 && inject('shouldBackButton', true);
-const injectedPageMetadata = injectReactiveMetadata();
+const viewId = inject(DI.viewId);
+const viewTransitionName = computed(() => `${viewId}---pageHeader`);
+const injectedPageMetadata = inject(DI.pageMetadata);
const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);
const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props.hideTitle);
const thin_ = props.thin || inject('shouldHeaderThin', false);
-const el = shallowRef<HTMLElement | undefined>(undefined);
+const el = useTemplateRef('el');
const bg = ref<string | undefined>(undefined);
const narrow = ref(false);
const hasTabs = computed(() => props.tabs.length > 0);
@@ -120,7 +124,7 @@ function goBack(): void {
const calcBg = () => {
const rawBg = 'var(--MI_THEME-bg)';
- const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
+ const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(window.document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
tinyBg.setAlpha(0.85);
bg.value = tinyBg.toRgbString();
};
@@ -129,12 +133,12 @@ let ro: ResizeObserver | null;
onMounted(() => {
calcBg();
- globalEvents.on('themeChanged', calcBg);
+ globalEvents.on('themeChanging', calcBg);
if (el.value && el.value.parentElement) {
narrow.value = el.value.parentElement.offsetWidth < 500;
ro = new ResizeObserver((entries, observer) => {
- if (el.value && el.value.parentElement && document.body.contains(el.value as HTMLElement)) {
+ if (el.value && el.value.parentElement && window.document.body.contains(el.value as HTMLElement)) {
narrow.value = el.value.parentElement.offsetWidth < 500;
}
});
@@ -143,7 +147,7 @@ onMounted(() => {
});
onUnmounted(() => {
- globalEvents.off('themeChanged', calcBg);
+ globalEvents.off('themeChanging', calcBg);
if (ro) ro.disconnect();
});
</script>
@@ -154,6 +158,7 @@ onUnmounted(() => {
backdrop-filter: var(--MI-blur, blur(15px));
border-bottom: solid 0.5px var(--MI_THEME-divider);
width: 100%;
+ view-transition-name: v-bind(viewTransitionName);
}
.upper,
diff --git a/packages/frontend/src/components/global/MkSpacer.vue b/packages/frontend/src/components/global/MkSpacer.vue
index db01c10eb0..6080bad9cd 100644
--- a/packages/frontend/src/components/global/MkSpacer.vue
+++ b/packages/frontend/src/components/global/MkSpacer.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { inject } from 'vue';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { deviceKind } from '@/utility/device-kind.js';
const props = withDefaults(defineProps<{
contentMax?: number | null;
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index 1aebf487bb..05245716c2 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -22,9 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, useTemplateRef } from 'vue';
-
-import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js';
+import { onMounted, onUnmounted, provide, inject, ref, watch, useTemplateRef } from 'vue';
+import { DI } from '@/di.js';
const rootEl = useTemplateRef('rootEl');
const headerEl = useTemplateRef('headerEl');
@@ -32,13 +31,13 @@ const footerEl = useTemplateRef('footerEl');
const headerHeight = ref<string | undefined>();
const childStickyTop = ref(0);
-const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0));
-provide(CURRENT_STICKY_TOP, childStickyTop);
+const parentStickyTop = inject(DI.currentStickyTop, ref(0));
+provide(DI.currentStickyTop, childStickyTop);
const footerHeight = ref<string | undefined>();
const childStickyBottom = ref(0);
-const parentStickyBottom = inject<Ref<number>>(CURRENT_STICKY_BOTTOM, ref(0));
-provide(CURRENT_STICKY_BOTTOM, childStickyBottom);
+const parentStickyBottom = inject(DI.currentStickyBottom, ref(0));
+provide(DI.currentStickyBottom, childStickyBottom);
const calc = () => {
// コンポーネントが表示されてないけどKeepAliveで残ってる場合などは null になる
diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts
index ccf7f200b5..5e62c3fbab 100644
--- a/packages/frontend/src/components/global/MkTime.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkTime from './MkTime.vue';
import { i18n } from '@/i18n.js';
import { dateTimeFormat } from '@@/js/intl-const.js';
diff --git a/packages/frontend/src/components/global/MkUrl.stories.impl.ts b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
index 34a4adfe49..ea02fdfdd0 100644
--- a/packages/frontend/src/components/global/MkUrl.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../../.storybook/mocks.js';
import MkUrl from './MkUrl.vue';
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 5196a63635..3a3e73978b 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -31,9 +31,9 @@ import { defineAsyncComponent, ref } from 'vue';
import { toUnicode as decodePunycode } from 'punycode.js';
import { url as local } from '@@/js/config.js';
import * as os from '@/os.js';
-import { useTooltip } from '@/scripts/use-tooltip.js';
+import { useTooltip } from '@/use/use-tooltip.js';
import { isEnabledUrlPreview } from '@/instance.js';
-import { MkABehavior } from '@/components/global/MkA.vue';
+import type { MkABehavior } from '@/components/global/MkA.vue';
import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
function safeURIDecode(str: string): string {
diff --git a/packages/frontend/src/components/global/MkUserName.stories.impl.ts b/packages/frontend/src/components/global/MkUserName.stories.impl.ts
index e39061c291..b46c91c903 100644
--- a/packages/frontend/src/components/global/MkUserName.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkUserName.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkUserName from './MkUserName.vue';
export const Default = {
diff --git a/packages/frontend/src/components/global/NestedRouterView.vue b/packages/frontend/src/components/global/NestedRouterView.vue
new file mode 100644
index 0000000000..af00347db8
--- /dev/null
+++ b/packages/frontend/src/components/global/NestedRouterView.vue
@@ -0,0 +1,60 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<Suspense :timeout="0">
+ <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
+
+ <template #fallback>
+ <MkLoading/>
+ </template>
+</Suspense>
+</template>
+
+<script lang="ts" setup>
+import { inject, provide, ref, shallowRef } from 'vue';
+import type { Router } from '@/router.js';
+import type { PathResolvedResult } from '@/lib/nirax.js';
+import MkLoadingPage from '@/pages/_loading_.vue';
+import { DI } from '@/di.js';
+
+const props = defineProps<{
+ router?: Router;
+}>();
+
+const router = props.router ?? inject(DI.router);
+
+if (router == null) {
+ throw new Error('no router provided');
+}
+
+const currentDepth = inject(DI.routerCurrentDepth, 0);
+provide(DI.routerCurrentDepth, currentDepth + 1);
+
+function resolveNested(current: PathResolvedResult, d = 0): PathResolvedResult | null {
+ if (d === currentDepth) {
+ return current;
+ } else {
+ if (current.child) {
+ return resolveNested(current.child, d + 1);
+ } else {
+ return null;
+ }
+ }
+}
+
+const current = resolveNested(router.current)!;
+const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
+const currentPageProps = ref(current.props);
+const key = ref(router.getCurrentFullPath());
+
+router.useListener('change', ({ resolved }) => {
+ const current = resolveNested(resolved);
+ if (current == null || 'redirect' in current.route) return;
+ currentPageComponent.value = current.route.component;
+ currentPageProps.value = current.props;
+ key.value = router.getCurrentFullPath();
+});
+</script>
diff --git a/packages/frontend/src/components/global/PageWithAnimBg.vue b/packages/frontend/src/components/global/PageWithAnimBg.vue
new file mode 100644
index 0000000000..a00b196a04
--- /dev/null
+++ b/packages/frontend/src/components/global/PageWithAnimBg.vue
@@ -0,0 +1,21 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+ <MkAnimBg style="position: absolute;"/>
+ <div class="_pageScrollable" style="position: absolute; top: 0; width: 100%; height: 100%;">
+ <slot></slot>
+ </div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import MkAnimBg from '@/components/MkAnimBg.vue';
+</script>
+
+<style lang="scss" module>
+
+</style>
diff --git a/packages/frontend/src/components/global/PageWithHeader.vue b/packages/frontend/src/components/global/PageWithHeader.vue
new file mode 100644
index 0000000000..fb813689ba
--- /dev/null
+++ b/packages/frontend/src/components/global/PageWithHeader.vue
@@ -0,0 +1,44 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
+ <MkStickyContainer>
+ <template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template>
+ <div :class="$style.body">
+ <slot></slot>
+ </div>
+ <template #footer><slot name="footer"></slot></template>
+ </MkStickyContainer>
+</div>
+</template>
+
+<script lang="ts" setup>
+import type { PageHeaderItem } from '@/types/page-header.js';
+import type { Tab } from './MkPageHeader.tabs.vue';
+
+const props = withDefaults(defineProps<{
+ tabs?: Tab[];
+ actions?: PageHeaderItem[] | null;
+ thin?: boolean;
+ hideTitle?: boolean;
+ displayMyAvatar?: boolean;
+ reversed?: boolean;
+}>(), {
+ tabs: () => ([] as Tab[]),
+});
+
+const tab = defineModel<string>('tab');
+</script>
+
+<style lang="scss" module>
+.root {
+
+}
+
+.body {
+ min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
+}
+</style>
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index 38bdfc52d4..1c0c35f34e 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -4,98 +4,108 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<KeepAlive
- :max="defaultStore.state.numberOfPageCache"
- :exclude="pageCacheController"
->
- <Suspense :timeout="0">
- <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
+<div ref="rootEl" class="_pageContainer" :class="$style.root">
+ <KeepAlive :max="prefer.s.numberOfPageCache">
+ <Suspense :timeout="0">
+ <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
- <template #fallback>
- <MkLoading/>
- </template>
- </Suspense>
-</KeepAlive>
+ <template #fallback>
+ <MkLoading/>
+ </template>
+ </Suspense>
+ </KeepAlive>
+</div>
</template>
<script lang="ts" setup>
-import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
-import { IRouter, Resolved, RouteDef } from '@/nirax.js';
-import { defaultStore } from '@/store.js';
-import { globalEvents } from '@/events.js';
+import { inject, nextTick, onMounted, provide, ref, shallowRef, useTemplateRef } from 'vue';
+import type { Router } from '@/router.js';
+import { prefer } from '@/preferences.js';
import MkLoadingPage from '@/pages/_loading_.vue';
+import { DI } from '@/di.js';
+import { randomId } from '@/utility/random-id.js';
+import { deepEqual } from '@/utility/deep-equal.js';
const props = defineProps<{
- router?: IRouter;
- nested?: boolean;
+ router?: Router;
}>();
-const router = props.router ?? inject('router');
+const router = props.router ?? inject(DI.router);
if (router == null) {
throw new Error('no router provided');
}
-const currentDepth = inject('routerCurrentDepth', 0);
-provide('routerCurrentDepth', currentDepth + 1);
+const viewId = randomId();
+provide(DI.viewId, viewId);
-function resolveNested(current: Resolved, d = 0): Resolved | null {
- if (!props.nested) return current;
+const currentDepth = inject(DI.routerCurrentDepth, 0);
+provide(DI.routerCurrentDepth, currentDepth + 1);
- if (d === currentDepth) {
- return current;
- } else {
- if (current.child) {
- return resolveNested(current.child, d + 1);
- } else {
- return null;
- }
- }
+const rootEl = useTemplateRef('rootEl');
+onMounted(() => {
+ rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入
+});
+
+// view-transition-newなどの<pt-name-selector>にはcss varが使えず、v-bindできないため直接スタイルを生成
+const viewTransitionStylesTag = window.document.createElement('style');
+viewTransitionStylesTag.textContent = `
+@keyframes ${viewId}-old {
+ to { transform: scale(0.95); opacity: 0; }
}
-const current = resolveNested(router.current)!;
-const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
-const currentPageProps = ref(current.props);
-const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props)));
+@keyframes ${viewId}-new {
+ from { transform: scale(0.95); opacity: 0; }
+}
-function onChange({ resolved, key: newKey }) {
- const current = resolveNested(resolved);
- if (current == null || 'redirect' in current.route) return;
- currentPageComponent.value = current.route.component;
- currentPageProps.value = current.props;
- key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
+::view-transition-old(${viewId}) {
+ animation-duration: 0.2s;
+ animation-name: ${viewId}-old;
+}
- nextTick(() => {
- // ページ遷移完了後に再びキャッシュを有効化
- if (clearCacheRequested.value) {
- clearCacheRequested.value = false;
- }
- });
+::view-transition-new(${viewId}) {
+ animation-duration: 0.2s;
+ animation-name: ${viewId}-new;
}
+`;
-router.addListener('change', onChange);
+window.document.head.appendChild(viewTransitionStylesTag);
-// #region キャッシュ制御
+const current = router.current!;
+const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
+const currentPageProps = ref(current.props);
+let currentRoutePath = current.route.path;
+const key = ref(router.getCurrentFullPath());
-/**
- * キャッシュクリアが有効になったら、全キャッシュをクリアする
- *
- * keepAlive側にwatcherがあるのですぐ消えるとはおもうけど、念のためページ遷移完了まではキャッシュを無効化しておく。
- * キャッシュ有効時向けにexcludeを使いたい場合は、pageCacheControllerに並列に突っ込むのではなく、下に追記すること
- */
-const pageCacheController = computed(() => clearCacheRequested.value ? /.*/ : undefined);
-const clearCacheRequested = ref(false);
+router.useListener('change', ({ resolved }) => {
+ if (resolved == null || 'redirect' in resolved.route) return;
+ if (resolved.route.path === currentRoutePath && deepEqual(resolved.props, currentPageProps.value)) return;
-globalEvents.on('requestClearPageCache', () => {
- if (_DEV_) console.log('clear page cache requested');
- if (!clearCacheRequested.value) {
- clearCacheRequested.value = true;
+ function _() {
+ currentPageComponent.value = resolved.route.component;
+ currentPageProps.value = resolved.props;
+ key.value = router.getCurrentFullPath();
+ currentRoutePath = resolved.route.path;
}
-});
-
-// #endregion
-onBeforeUnmount(() => {
- router.removeListener('change', onChange);
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (prefer.s.animation && window.document.startViewTransition) {
+ window.document.startViewTransition(() => new Promise((res) => {
+ _();
+ nextTick(() => {
+ res();
+ //setTimeout(res, 100);
+ });
+ }));
+ } else {
+ _();
+ }
});
</script>
+
+<style lang="scss" module>
+.root {
+ height: 100%;
+ background-color: var(--MI_THEME-bg);
+}
+</style>
diff --git a/packages/frontend/src/components/global/SearchKeyword.vue b/packages/frontend/src/components/global/SearchKeyword.vue
new file mode 100644
index 0000000000..27a284faf0
--- /dev/null
+++ b/packages/frontend/src/components/global/SearchKeyword.vue
@@ -0,0 +1,14 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<slot></slot>
+</template>
+
+<script lang="ts" setup>
+</script>
+
+<style lang="scss" module>
+</style>
diff --git a/packages/frontend/src/components/global/SearchLabel.vue b/packages/frontend/src/components/global/SearchLabel.vue
new file mode 100644
index 0000000000..27a284faf0
--- /dev/null
+++ b/packages/frontend/src/components/global/SearchLabel.vue
@@ -0,0 +1,14 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<slot></slot>
+</template>
+
+<script lang="ts" setup>
+</script>
+
+<style lang="scss" module>
+</style>
diff --git a/packages/frontend/src/components/global/SearchMarker.vue b/packages/frontend/src/components/global/SearchMarker.vue
new file mode 100644
index 0000000000..061ce3f47d
--- /dev/null
+++ b/packages/frontend/src/components/global/SearchMarker.vue
@@ -0,0 +1,116 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div ref="root" :class="[$style.root, { [$style.highlighted]: highlighted }]">
+ <slot></slot>
+</div>
+</template>
+
+<script lang="ts" setup>
+import {
+ onActivated,
+ onDeactivated,
+ onMounted,
+ onBeforeUnmount,
+ watch,
+ computed,
+ ref,
+ useTemplateRef,
+ inject,
+} from 'vue';
+import type { Ref } from 'vue';
+
+const props = defineProps<{
+ markerId?: string;
+ label?: string;
+ icon?: string;
+ keywords?: string[];
+ children?: string[];
+ inlining?: string[];
+}>();
+
+const rootEl = useTemplateRef('root');
+const rootElMutationObserver = new MutationObserver(() => {
+ checkChildren();
+});
+const injectedSearchMarkerId = inject<Ref<string | null> | null>('inAppSearchMarkerId', null);
+const searchMarkerId = computed(() => injectedSearchMarkerId?.value ?? window.location.hash.slice(1));
+const highlighted = ref(props.markerId === searchMarkerId.value);
+
+function checkChildren() {
+ if (props.children?.includes(searchMarkerId.value)) {
+ const el = window.document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`);
+ highlighted.value = el == null;
+ }
+}
+
+watch([
+ searchMarkerId,
+ () => props.children,
+], () => {
+ if (props.children != null && props.children.length > 0) {
+ checkChildren();
+ }
+}, { flush: 'post' });
+
+function init() {
+ checkChildren();
+
+ if (highlighted.value) {
+ rootEl.value?.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ });
+ }
+
+ if (rootEl.value != null) {
+ rootElMutationObserver.observe(rootEl.value, {
+ childList: true,
+ subtree: true,
+ });
+ }
+}
+
+function dispose() {
+ rootElMutationObserver.disconnect();
+}
+
+onMounted(init);
+onActivated(init);
+onDeactivated(dispose);
+onBeforeUnmount(dispose);
+</script>
+
+<style lang="scss" module>
+.root {
+ position: relative;
+}
+
+.highlighted {
+ &::after {
+ content: '';
+ position: absolute;
+ top: -8px;
+ left: -8px;
+ width: calc(100% + 16px);
+ height: calc(100% + 16px);
+ border-radius: 6px;
+ animation: blink 1s 3.5;
+ pointer-events: none;
+ }
+}
+
+@keyframes blink {
+ 0%, 100% {
+ background: color(from var(--MI_THEME-accent) srgb r g b / 0.05);
+ border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.7);
+ }
+ 50% {
+ background: transparent;
+ border: 1px solid transparent;
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/global/StackingRouterView.vue b/packages/frontend/src/components/global/StackingRouterView.vue
new file mode 100644
index 0000000000..c95c74aef3
--- /dev/null
+++ b/packages/frontend/src/components/global/StackingRouterView.vue
@@ -0,0 +1,243 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<TransitionGroup
+ :enterActiveClass="prefer.s.animation ? $style.transition_x_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_x_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_x_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_x_leaveTo : ''"
+ :moveClass="prefer.s.animation ? $style.transition_x_move : ''"
+ :duration="200"
+ tag="div" :class="$style.tabs"
+>
+ <div v-for="(tab, i) in tabs" :key="tab.fullPath" :class="$style.tab" :style="{ '--i': i - 1 }">
+ <div v-if="i > 0" :class="$style.tabBg" @click="back()"></div>
+ <div :class="$style.tabFg" @click.stop="back()">
+ <div v-if="i > 0" :class="$style.tabMenu">
+ <svg :class="$style.tabMenuShape" viewBox="0 0 24 16">
+ <g transform="matrix(2.04108e-17,-0.333333,0.222222,1.36072e-17,21.3333,15.9989)">
+ <path d="M23.997,-42C47.903,-23.457 47.997,12 47.997,12L-0.003,12L-0.003,-96C-0.003,-96 0.091,-60.543 23.997,-42Z" style="fill:var(--MI_THEME-panel);"/>
+ </g>
+ </svg>
+ <button :class="$style.tabMenuButton" class="_button" @click.stop="mount"><i class="ti ti-rectangle"></i></button>
+ <button :class="$style.tabMenuButton" class="_button" @click.stop="back"><i class="ti ti-x"></i></button>
+ </div>
+ <div v-if="i > 0" :class="$style.tabBorder"></div>
+ <div :class="$style.tabContent" class="_pageContainer" @click.stop="">
+ <Suspense :timeout="0">
+ <component :is="tab.component" v-bind="Object.fromEntries(tab.props)"/>
+
+ <template #fallback>
+ <MkLoading/>
+ </template>
+ </Suspense>
+ </div>
+ </div>
+ </div>
+</TransitionGroup>
+</template>
+
+<script lang="ts" setup>
+import { inject, provide, shallowRef } from 'vue';
+import type { Router } from '@/router.js';
+import { prefer } from '@/preferences.js';
+import MkLoadingPage from '@/pages/_loading_.vue';
+import { DI } from '@/di.js';
+import { deepEqual } from '@/utility/deep-equal.js';
+
+const props = defineProps<{
+ router?: Router;
+}>();
+
+const router = props.router ?? inject(DI.router);
+
+if (router == null) {
+ throw new Error('no router provided');
+}
+
+const currentDepth = inject(DI.routerCurrentDepth, 0);
+provide(DI.routerCurrentDepth, currentDepth + 1);
+
+const tabs = shallowRef([{
+ fullPath: router.getCurrentFullPath(),
+ routePath: router.current.route.path,
+ component: 'component' in router.current.route ? router.current.route.component : MkLoadingPage,
+ props: router.current.props,
+}]);
+
+function mount() {
+ const currentTab = tabs.value[tabs.value.length - 1];
+ tabs.value = [currentTab];
+}
+
+function back() {
+ const prev = tabs.value[tabs.value.length - 2];
+ tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)];
+ router.replace(prev.fullPath);
+}
+
+router.useListener('change', ({ resolved }) => {
+ const currentTab = tabs.value[tabs.value.length - 1];
+ const routePath = resolved.route.path;
+ if (resolved == null || 'redirect' in resolved.route) return;
+ if (resolved.route.path === currentTab.routePath && deepEqual(resolved.props, currentTab.props)) return;
+ const fullPath = router.getCurrentFullPath();
+
+ if (tabs.value.some(tab => tab.routePath === routePath && deepEqual(resolved.props, tab.props))) {
+ const newTabs = [];
+ for (const tab of tabs.value) {
+ newTabs.push(tab);
+
+ if (tab.routePath === routePath && deepEqual(resolved.props, tab.props)) {
+ break;
+ }
+ }
+ tabs.value = newTabs;
+ return;
+ }
+
+ tabs.value = tabs.value.length >= prefer.s.numberOfPageCache ? [
+ ...tabs.value.slice(1),
+ {
+ fullPath: fullPath,
+ routePath,
+ component: resolved.route.component,
+ props: resolved.props,
+ },
+ ] : [...tabs.value, {
+ fullPath: fullPath,
+ routePath,
+ component: resolved.route.component,
+ props: resolved.props,
+ }];
+});
+
+router.useListener('replace', ({ fullPath }) => {
+ const currentTab = tabs.value[tabs.value.length - 1];
+ currentTab.fullPath = fullPath;
+ tabs.value = [...tabs.value.slice(0, tabs.value.length - 1), currentTab];
+});
+</script>
+
+<style lang="scss" module>
+.transition_x_move,
+.transition_x_enterActive,
+.transition_x_leaveActive {
+ .tabBg {
+ transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important;
+ }
+
+ .tabFg {
+ transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important;
+ }
+}
+.transition_x_enterFrom,
+.transition_x_leaveTo {
+ .tabBg {
+ opacity: 0;
+ }
+
+ .tabFg {
+ opacity: 0;
+ transform: translateY(100px);
+ }
+}
+.transition_x_leaveActive {
+ .tabFg {
+ //position: absolute;
+ }
+}
+
+.tabs {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.tab {
+ overflow: clip;
+
+ &:first-child {
+ position: relative;
+ width: 100%;
+ height: 100%;
+
+ .tabFg {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+
+ .tabContent {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ &:not(:first-child) {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+
+ .tabBg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #0003;
+ -webkit-backdrop-filter: var(--MI-blur, blur(3px));
+ backdrop-filter: var(--MI-blur, blur(3px));
+ }
+
+ .tabFg {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: calc(100% - (10px + (20px * var(--i))));
+ display: flex;
+ flex-direction: column;
+ }
+
+ .tabContent {
+ flex: 1;
+ width: 100%;
+ height: 100%;
+ background: var(--MI_THEME-bg);
+ }
+ }
+}
+
+.tabMenu {
+ position: relative;
+ margin-left: auto;
+ padding: 0 4px;
+ background: var(--MI_THEME-panel);
+}
+
+.tabMenuShape {
+ position: absolute;
+ bottom: -1px;
+ left: -100%;
+ height: calc(100% + 1px);
+ width: 129%;
+ pointer-events: none;
+}
+
+.tabBorder {
+ height: 6px;
+ background: var(--MI_THEME-panel);
+}
+
+.tabMenuButton {
+ padding: 8px;
+ font-size: 13px;
+}
+</style>
diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue
index e473b7c1af..7c8a5d64d7 100644
--- a/packages/frontend/src/components/grid/MkDataCell.vue
+++ b/packages/frontend/src/components/grid/MkDataCell.vue
@@ -39,10 +39,12 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ cell.value }}
</div>
<div v-else-if="cellType === 'boolean'">
- <div :class="[$style.bool, {
- [$style.boolTrue]: cell.value === true,
- 'ti ti-check': cell.value === true,
- }]"></div>
+ <div
+ :class="[$style.bool, {
+ [$style.boolTrue]: cell.value === true,
+ 'ti ti-check': cell.value === true,
+ }]"
+ ></div>
</div>
<div v-else-if="cellType === 'image'">
<img
@@ -88,13 +90,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, toRefs, watch } from 'vue';
-import { GridEventEmitter, Size } from '@/components/grid/grid.js';
-import { useTooltip } from '@/scripts/use-tooltip.js';
+import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, useTemplateRef, toRefs, watch } from 'vue';
+import type { Size } from '@/components/grid/grid.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRowSetting } from '@/components/grid/row.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
+import { useTooltip } from '@/use/use-tooltip.js';
import * as os from '@/os.js';
-import { CellValue, GridCell } from '@/components/grid/cell.js';
import { equalCellAddress, getCellAddress } from '@/components/grid/grid-utils.js';
-import { GridRowSetting } from '@/components/grid/row.js';
const emit = defineEmits<{
(ev: 'operation:beginEdit', sender: GridCell): void;
@@ -110,9 +113,9 @@ const props = defineProps<{
const { cell, bus } = toRefs(props);
-const rootEl = shallowRef<InstanceType<typeof HTMLTableCellElement>>();
-const contentAreaEl = shallowRef<InstanceType<typeof HTMLDivElement>>();
-const inputAreaEl = shallowRef<InstanceType<typeof HTMLDivElement>>();
+const rootEl = useTemplateRef('rootEl');
+const contentAreaEl = useTemplateRef('contentAreaEl');
+const inputAreaEl = useTemplateRef('inputAreaEl');
/** 値が編集中かどうか */
const editing = ref<boolean>(false);
diff --git a/packages/frontend/src/components/grid/MkDataRow.vue b/packages/frontend/src/components/grid/MkDataRow.vue
index 280a14bc4a..a35f93b435 100644
--- a/packages/frontend/src/components/grid/MkDataRow.vue
+++ b/packages/frontend/src/components/grid/MkDataRow.vue
@@ -37,11 +37,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { GridEventEmitter, Size } from '@/components/grid/grid.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
import MkDataCell from '@/components/grid/MkDataCell.vue';
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
-import { CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridRow, GridRowSetting } from '@/components/grid/row.js';
+import type { Size } from '@/components/grid/grid.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRow, GridRowSetting } from '@/components/grid/row.js';
const emit = defineEmits<{
(ev: 'operation:beginEdit', sender: GridCell): void;
diff --git a/packages/frontend/src/components/grid/MkGrid.stories.impl.ts b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts
index 5801012f15..f85bf146e8 100644
--- a/packages/frontend/src/components/grid/MkGrid.stories.impl.ts
+++ b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts
@@ -5,14 +5,14 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { ref } from 'vue';
import { commonHandlers } from '../../../.storybook/mocks.js';
import { boolean, choose, country, date, firstName, integer, lastName, text } from '../../../.storybook/fake-utils.js';
import MkGrid from './MkGrid.vue';
-import { GridContext, GridEvent } from '@/components/grid/grid-event.js';
-import { DataSource, GridSetting } from '@/components/grid/grid.js';
-import { GridColumnSetting } from '@/components/grid/column.js';
+import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
+import type { DataSource, GridSetting } from '@/components/grid/grid.js';
+import type { GridColumnSetting } from '@/components/grid/column.js';
function d(p: {
check?: boolean,
diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue
index 4dbd4ebcae..94f4f3dab1 100644
--- a/packages/frontend/src/components/grid/MkGrid.vue
+++ b/packages/frontend/src/components/grid/MkGrid.vue
@@ -50,11 +50,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, onMounted, ref, toRefs, watch } from 'vue';
-import { DataSource, GridEventEmitter, GridSetting, GridState, Size } from '@/components/grid/grid.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
import MkDataRow from '@/components/grid/MkDataRow.vue';
import MkHeaderRow from '@/components/grid/MkHeaderRow.vue';
import { cellValidation } from '@/components/grid/cell-validators.js';
-import { CELL_ADDRESS_NONE, CellAddress, CellValue, createCell, GridCell, resetCell } from '@/components/grid/cell.js';
+import { CELL_ADDRESS_NONE, createCell, resetCell } from '@/components/grid/cell.js';
import {
copyGridDataToClipboard,
equalCellAddress,
@@ -63,18 +63,23 @@ import {
pasteToGridFromClipboard,
removeDataFromGrid,
} from '@/components/grid/grid-utils.js';
-import { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
-import { GridContext, GridEvent } from '@/components/grid/grid-event.js';
-import { createColumn, GridColumn } from '@/components/grid/column.js';
-import { createRow, defaultGridRowSetting, GridRow, GridRowSetting, resetRow } from '@/components/grid/row.js';
-import { handleKeyEvent } from '@/scripts/key-event.js';
+import { createColumn } from '@/components/grid/column.js';
+import { createRow, defaultGridRowSetting, resetRow } from '@/components/grid/row.js';
+import { handleKeyEvent } from '@/utility/key-event.js';
+
+import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js';
+import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRow, GridRowSetting } from '@/components/grid/row.js';
+import type { MenuItem } from '@/types/menu.js';
type RowHolder = {
row: GridRow,
cells: GridCell[],
origin: DataSource,
-}
+};
const emit = defineEmits<{
(ev: 'event', event: GridEvent, context: GridContext): void;
diff --git a/packages/frontend/src/components/grid/MkHeaderCell.vue b/packages/frontend/src/components/grid/MkHeaderCell.vue
index aecfe7eaa3..69a68b6f2c 100644
--- a/packages/frontend/src/components/grid/MkHeaderCell.vue
+++ b/packages/frontend/src/components/grid/MkHeaderCell.vue
@@ -32,8 +32,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, toRefs, watch } from 'vue';
-import { GridEventEmitter, Size } from '@/components/grid/grid.js';
-import { GridColumn } from '@/components/grid/column.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
+import type { Size } from '@/components/grid/grid.js';
+import type { GridColumn } from '@/components/grid/column.js';
const emit = defineEmits<{
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
diff --git a/packages/frontend/src/components/grid/MkHeaderRow.vue b/packages/frontend/src/components/grid/MkHeaderRow.vue
index 8affa08fd5..225f623b84 100644
--- a/packages/frontend/src/components/grid/MkHeaderRow.vue
+++ b/packages/frontend/src/components/grid/MkHeaderRow.vue
@@ -29,11 +29,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { GridEventEmitter, Size } from '@/components/grid/grid.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
import MkHeaderCell from '@/components/grid/MkHeaderCell.vue';
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
-import { GridColumn } from '@/components/grid/column.js';
-import { GridRowSetting } from '@/components/grid/row.js';
+import type { Size } from '@/components/grid/grid.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRowSetting } from '@/components/grid/row.js';
const emit = defineEmits<{
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
diff --git a/packages/frontend/src/components/grid/MkNumberCell.vue b/packages/frontend/src/components/grid/MkNumberCell.vue
index 674bba96bc..d3b5956ddd 100644
--- a/packages/frontend/src/components/grid/MkNumberCell.vue
+++ b/packages/frontend/src/components/grid/MkNumberCell.vue
@@ -19,8 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-
-import { GridRow } from '@/components/grid/row.js';
+import type { GridRow } from '@/components/grid/row.js';
defineProps<{
content: string,
diff --git a/packages/frontend/src/components/grid/cell-validators.ts b/packages/frontend/src/components/grid/cell-validators.ts
index 949cab2ec6..7310a82c9e 100644
--- a/packages/frontend/src/components/grid/cell-validators.ts
+++ b/packages/frontend/src/components/grid/cell-validators.ts
@@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridColumn } from '@/components/grid/column.js';
-import { GridRow } from '@/components/grid/row.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRow } from '@/components/grid/row.js';
import { i18n } from '@/i18n.js';
export type ValidatorParams = {
@@ -18,25 +18,25 @@ export type ValidatorParams = {
export type ValidatorResult = {
valid: boolean;
message?: string;
-}
+};
export type GridCellValidator = {
name?: string;
ignoreViolation?: boolean;
validate: (params: ValidatorParams) => ValidatorResult;
-}
+};
export type ValidateViolation = {
valid: boolean;
params: ValidatorParams;
violations: ValidateViolationItem[];
-}
+};
export type ValidateViolationItem = {
valid: boolean;
validator: GridCellValidator;
result: ValidatorResult;
-}
+};
export function cellValidation(allCells: GridCell[], cell: GridCell, newValue: CellValue): ValidateViolation {
const { column, row } = cell;
diff --git a/packages/frontend/src/components/grid/cell.ts b/packages/frontend/src/components/grid/cell.ts
index 71b7a3e3f1..d347d05bdb 100644
--- a/packages/frontend/src/components/grid/cell.ts
+++ b/packages/frontend/src/components/grid/cell.ts
@@ -3,19 +3,19 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { ValidateViolation } from '@/components/grid/cell-validators.js';
-import { Size } from '@/components/grid/grid.js';
-import { GridColumn } from '@/components/grid/column.js';
-import { GridRow } from '@/components/grid/row.js';
-import { MenuItem } from '@/types/menu.js';
-import { GridContext } from '@/components/grid/grid-event.js';
+import type { ValidateViolation } from '@/components/grid/cell-validators.js';
+import type { Size } from '@/components/grid/grid.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRow } from '@/components/grid/row.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { GridContext } from '@/components/grid/grid-event.js';
export type CellValue = string | boolean | number | undefined | null | Array<unknown> | NonNullable<unknown>;
export type CellAddress = {
row: number;
col: number;
-}
+};
export const CELL_ADDRESS_NONE: CellAddress = {
row: -1,
@@ -32,13 +32,13 @@ export type GridCell = {
contentSize: Size;
setting: GridCellSetting;
violation: ValidateViolation;
-}
+};
export type GridCellContextMenuFactory = (col: GridColumn, row: GridRow, value: CellValue, context: GridContext) => MenuItem[];
export type GridCellSetting = {
contextMenuFactory?: GridCellContextMenuFactory;
-}
+};
export function createCell(
column: GridColumn,
diff --git a/packages/frontend/src/components/grid/column.ts b/packages/frontend/src/components/grid/column.ts
index 2f505756fe..6a694b39ec 100644
--- a/packages/frontend/src/components/grid/column.ts
+++ b/packages/frontend/src/components/grid/column.ts
@@ -3,13 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { GridCellValidator } from '@/components/grid/cell-validators.js';
-import { Size, SizeStyle } from '@/components/grid/grid.js';
import { calcCellWidth } from '@/components/grid/grid-utils.js';
-import { CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridRow } from '@/components/grid/row.js';
-import { MenuItem } from '@/types/menu.js';
-import { GridContext } from '@/components/grid/grid-event.js';
+import type { GridCellValidator } from '@/components/grid/cell-validators.js';
+import type { Size, SizeStyle } from '@/components/grid/grid.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRow } from '@/components/grid/row.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { GridContext } from '@/components/grid/grid-event.js';
export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image' | 'hidden';
@@ -40,7 +40,7 @@ export type GridColumn = {
setting: GridColumnSetting;
width: string;
contentSize: Size;
-}
+};
export function createColumn(setting: GridColumnSetting, index: number): GridColumn {
return {
diff --git a/packages/frontend/src/components/grid/grid-event.ts b/packages/frontend/src/components/grid/grid-event.ts
index 074b72b956..e2f1e44055 100644
--- a/packages/frontend/src/components/grid/grid-event.ts
+++ b/packages/frontend/src/components/grid/grid-event.ts
@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridState } from '@/components/grid/grid.js';
-import { ValidateViolation } from '@/components/grid/cell-validators.js';
-import { GridColumn } from '@/components/grid/column.js';
-import { GridRow } from '@/components/grid/row.js';
+import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridState } from '@/components/grid/grid.js';
+import type { ValidateViolation } from '@/components/grid/cell-validators.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRow } from '@/components/grid/row.js';
export type GridContext = {
selectedCell?: GridCell;
diff --git a/packages/frontend/src/components/grid/grid-utils.ts b/packages/frontend/src/components/grid/grid-utils.ts
index a45bc88926..9e5402354e 100644
--- a/packages/frontend/src/components/grid/grid-utils.ts
+++ b/packages/frontend/src/components/grid/grid-utils.ts
@@ -3,13 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { isRef, Ref } from 'vue';
-import { DataSource, SizeStyle } from '@/components/grid/grid.js';
-import { CELL_ADDRESS_NONE, CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridRow } from '@/components/grid/row.js';
-import { GridContext } from '@/components/grid/grid-event.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { GridColumn, GridColumnSetting } from '@/components/grid/column.js';
+import { isRef } from 'vue';
+import type { Ref } from 'vue';
+import type { DataSource, SizeStyle } from '@/components/grid/grid.js';
+import { CELL_ADDRESS_NONE } from '@/components/grid/cell.js';
+import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRow } from '@/components/grid/row.js';
+import type { GridContext } from '@/components/grid/grid-event.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import type { GridColumn, GridColumnSetting } from '@/components/grid/column.js';
export function isCellElement(elem: HTMLElement): boolean {
return elem.hasAttribute('data-grid-cell');
diff --git a/packages/frontend/src/components/grid/grid.ts b/packages/frontend/src/components/grid/grid.ts
index b82e12b304..0428e6493c 100644
--- a/packages/frontend/src/components/grid/grid.ts
+++ b/packages/frontend/src/components/grid/grid.ts
@@ -4,9 +4,9 @@
*/
import { EventEmitter } from 'eventemitter3';
-import { CellValue, GridCellSetting } from '@/components/grid/cell.js';
-import { GridColumnSetting } from '@/components/grid/column.js';
-import { GridRowSetting } from '@/components/grid/row.js';
+import type { CellValue, GridCellSetting } from '@/components/grid/cell.js';
+import type { GridColumnSetting } from '@/components/grid/column.js';
+import type { GridRowSetting } from '@/components/grid/row.js';
export type GridSetting = {
root?: {
@@ -21,7 +21,7 @@ export type GridSetting = {
export type DataSource = Record<string, CellValue>;
-export type GridState =
+export type GridState = (
'normal' |
'cellSelecting' |
'cellEditing' |
@@ -29,19 +29,19 @@ export type GridState =
'colSelecting' |
'rowSelecting' |
'hidden'
- ;
+);
export type Size = {
width: number;
height: number;
-}
+};
export type SizeStyle = number | 'auto' | undefined;
export type AdditionalStyle = {
className?: string;
style?: Record<string, string | number>;
-}
+};
export class GridEventEmitter extends EventEmitter<{
'forceRefreshContentSize': void;
diff --git a/packages/frontend/src/components/grid/row.ts b/packages/frontend/src/components/grid/row.ts
index e0a317c9d3..42da22193f 100644
--- a/packages/frontend/src/components/grid/row.ts
+++ b/packages/frontend/src/components/grid/row.ts
@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { AdditionalStyle } from '@/components/grid/grid.js';
-import { GridCell } from '@/components/grid/cell.js';
-import { GridColumn } from '@/components/grid/column.js';
-import { MenuItem } from '@/types/menu.js';
-import { GridContext } from '@/components/grid/grid-event.js';
+import type { AdditionalStyle } from '@/components/grid/grid.js';
+import type { GridCell } from '@/components/grid/cell.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { GridContext } from '@/components/grid/grid-event.js';
export const defaultGridRowSetting: Required<GridRowSetting> = {
showNumber: true,
@@ -27,7 +27,7 @@ export type GridRowStyleRuleConditionParams = {
export type GridRowStyleRule = {
condition: (params: GridRowStyleRuleConditionParams) => boolean;
applyStyle: AdditionalStyle;
-}
+};
export type GridRowContextMenuFactory = (row: GridRow, context: GridContext) => MenuItem[];
@@ -40,7 +40,7 @@ export type GridRowSetting = {
events?: {
delete?: (rows: GridRow[]) => void;
}
-}
+};
export type GridRow = {
index: number;
@@ -48,7 +48,7 @@ export type GridRow = {
using: boolean;
setting: GridRowSetting;
additionalStyles: AdditionalStyle[];
-}
+};
export function createRow(index: number, using: boolean, setting: GridRowSetting): GridRow {
return {
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index b36625ed1b..6c6903c3a4 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -3,8 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { App } from 'vue';
-
import Mfm from './global/MkMfm.js';
import MkA from './global/MkA.vue';
import MkAcct from './global/MkAcct.vue';
@@ -18,14 +16,22 @@ import MkTime from './global/MkTime.vue';
import MkUrl from './global/MkUrl.vue';
import I18n from './global/I18n.vue';
import RouterView from './global/RouterView.vue';
+import NestedRouterView from './global/NestedRouterView.vue';
+import StackingRouterView from './global/StackingRouterView.vue';
import MkLoading from './global/MkLoading.vue';
import MkError from './global/MkError.vue';
import MkAd from './global/MkAd.vue';
import MkPageHeader from './global/MkPageHeader.vue';
import MkSpacer from './global/MkSpacer.vue';
-import MkFooterSpacer from './global/MkFooterSpacer.vue';
import MkStickyContainer from './global/MkStickyContainer.vue';
import MkLazy from './global/MkLazy.vue';
+import PageWithHeader from './global/PageWithHeader.vue';
+import PageWithAnimBg from './global/PageWithAnimBg.vue';
+import SearchMarker from './global/SearchMarker.vue';
+import SearchLabel from './global/SearchLabel.vue';
+import SearchKeyword from './global/SearchKeyword.vue';
+
+import type { App } from 'vue';
export default function(app: App) {
for (const [key, value] of Object.entries(components)) {
@@ -36,6 +42,8 @@ export default function(app: App) {
export const components = {
I18n: I18n,
RouterView: RouterView,
+ NestedRouterView: NestedRouterView,
+ StackingRouterView: StackingRouterView,
Mfm: Mfm,
MkA: MkA,
MkAcct: MkAcct,
@@ -52,15 +60,21 @@ export const components = {
MkAd: MkAd,
MkPageHeader: MkPageHeader,
MkSpacer: MkSpacer,
- MkFooterSpacer: MkFooterSpacer,
MkStickyContainer: MkStickyContainer,
MkLazy: MkLazy,
+ PageWithHeader: PageWithHeader,
+ PageWithAnimBg: PageWithAnimBg,
+ SearchMarker: SearchMarker,
+ SearchLabel: SearchLabel,
+ SearchKeyword: SearchKeyword,
};
declare module '@vue/runtime-core' {
export interface GlobalComponents {
I18n: typeof I18n;
RouterView: typeof RouterView;
+ NestedRouterView: typeof NestedRouterView;
+ StackingRouterView: typeof StackingRouterView;
Mfm: typeof Mfm;
MkA: typeof MkA;
MkAcct: typeof MkAcct;
@@ -77,8 +91,12 @@ declare module '@vue/runtime-core' {
MkAd: typeof MkAd;
MkPageHeader: typeof MkPageHeader;
MkSpacer: typeof MkSpacer;
- MkFooterSpacer: typeof MkFooterSpacer;
MkStickyContainer: typeof MkStickyContainer;
MkLazy: typeof MkLazy;
+ PageWithHeader: typeof PageWithHeader;
+ PageWithAnimBg: typeof PageWithAnimBg;
+ SearchMarker: typeof SearchMarker;
+ SearchLabel: typeof SearchLabel;
+ SearchKeyword: typeof SearchKeyword;
}
}
diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue
index 84436e7adb..df26874c17 100644
--- a/packages/frontend/src/components/page/page.note.vue
+++ b/packages/frontend/src/components/page/page.note.vue
@@ -15,7 +15,7 @@ import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkNote from '@/components/MkNote.vue';
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
const props = defineProps<{
block: Misskey.entities.PageBlock,
diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue
index e5b1eff294..d5c80aa5f8 100644
--- a/packages/frontend/src/components/page/page.text.vue
+++ b/packages/frontend/src/components/page/page.text.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { defineAsyncComponent } from 'vue';
import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js';
-import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
+import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
import { isEnabledUrlPreview } from '@/instance.js';
const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));
diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts
index 0d03282cee..45d4b40fd7 100644
--- a/packages/frontend/src/custom-emojis.ts
+++ b/packages/frontend/src/custom-emojis.ts
@@ -5,8 +5,8 @@
import { shallowRef, computed, markRaw, watch } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
-import { get, set } from '@/scripts/idb-proxy.js';
+import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
+import { get, set } from '@/utility/idb-proxy.js';
const storageCache = await get('emojis');
export const customEmojis = shallowRef<Misskey.entities.EmojiSimple[]>(Array.isArray(storageCache) ? storageCache : []);
diff --git a/packages/frontend/src/debug.ts b/packages/frontend/src/debug.ts
index 8bb8012ae3..3a8f6289d1 100644
--- a/packages/frontend/src/debug.ts
+++ b/packages/frontend/src/debug.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { type ComponentInternalInstance, getCurrentInstance } from 'vue';
+import { getCurrentInstance } from 'vue';
+import type { ComponentInternalInstance } from 'vue';
export function isDebuggerEnabled(id: number): boolean {
try {
diff --git a/packages/frontend/src/deck.ts b/packages/frontend/src/deck.ts
new file mode 100644
index 0000000000..9df56c52df
--- /dev/null
+++ b/packages/frontend/src/deck.ts
@@ -0,0 +1,353 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { notificationTypes } from 'misskey-js';
+import { ref } from 'vue';
+import { v4 as uuid } from 'uuid';
+import { i18n } from './i18n.js';
+import type { BasicTimelineType } from '@/timelines.js';
+import type { SoundStore } from '@/preferences/def.js';
+import type { MenuItem } from '@/types/menu.js';
+import { deepClone } from '@/utility/clone.js';
+import { prefer } from '@/preferences.js';
+import * as os from '@/os.js';
+
+export type DeckProfile = {
+ name: string;
+ id: string;
+ columns: Column[];
+ layout: Column['id'][][];
+};
+
+type ColumnWidget = {
+ name: string;
+ id: string;
+ data: Record<string, any>;
+};
+
+export const columnTypes = [
+ 'main',
+ 'widgets',
+ 'notifications',
+ 'tl',
+ 'antenna',
+ 'list',
+ 'channel',
+ 'mentions',
+ 'direct',
+ 'roleTimeline',
+] as const;
+
+export type ColumnType = typeof columnTypes[number];
+
+export type Column = {
+ id: string;
+ type: ColumnType;
+ name: string | null;
+ width: number;
+ widgets?: ColumnWidget[];
+ active?: boolean;
+ flexible?: boolean;
+ antennaId?: string;
+ listId?: string;
+ channelId?: string;
+ roleId?: string;
+ excludeTypes?: typeof notificationTypes[number][];
+ tl?: BasicTimelineType;
+ withRenotes?: boolean;
+ withReplies?: boolean;
+ withSensitive?: boolean;
+ onlyFiles?: boolean;
+ soundSetting?: SoundStore;
+};
+
+const _currentProfile = prefer.s['deck.profiles'].find(p => p.name === prefer.s['deck.profile']);
+const __currentProfile = _currentProfile ? deepClone(_currentProfile) : null;
+export const columns = ref(__currentProfile ? __currentProfile.columns : []);
+export const layout = ref(__currentProfile ? __currentProfile.layout : []);
+
+if (prefer.s['deck.profile'] == null) {
+ addProfile('Main');
+}
+
+export function forceSaveCurrentDeckProfile() {
+ const currentProfile = prefer.s['deck.profiles'].find(p => p.name === prefer.s['deck.profile']);
+ if (currentProfile == null) return;
+
+ const newProfile = deepClone(currentProfile);
+ newProfile.columns = columns.value;
+ newProfile.layout = layout.value;
+
+ const newProfiles = prefer.s['deck.profiles'].filter(p => p.name !== prefer.s['deck.profile']);
+ newProfiles.push(newProfile);
+ prefer.commit('deck.profiles', newProfiles);
+}
+
+export const saveCurrentDeckProfile = () => {
+ forceSaveCurrentDeckProfile();
+};
+
+function switchProfile(profile: DeckProfile) {
+ prefer.commit('deck.profile', profile.name);
+ const currentProfile = deepClone(profile);
+ columns.value = currentProfile.columns;
+ layout.value = currentProfile.layout;
+ forceSaveCurrentDeckProfile();
+}
+
+function addProfile(name: string) {
+ if (name.trim() === '') return;
+ if (prefer.s['deck.profiles'].find(p => p.name === name)) return;
+
+ const newProfile: DeckProfile = {
+ id: uuid(),
+ name,
+ columns: [],
+ layout: [],
+ };
+ prefer.commit('deck.profiles', [...prefer.s['deck.profiles'], newProfile]);
+ switchProfile(newProfile);
+}
+
+function createFirstProfile() {
+ addProfile('Main');
+}
+
+export function deleteProfile(name: string): void {
+ const newProfiles = prefer.s['deck.profiles'].filter(p => p.name !== name);
+ prefer.commit('deck.profiles', newProfiles);
+
+ if (prefer.s['deck.profiles'].length === 0) {
+ createFirstProfile();
+ } else {
+ switchProfile(prefer.s['deck.profiles'][0]);
+ }
+}
+
+export function addColumn(column: Column) {
+ if (column.name === undefined) column.name = null;
+ columns.value.push(column);
+ layout.value.push([column.id]);
+ saveCurrentDeckProfile();
+}
+
+export function removeColumn(id: Column['id']) {
+ columns.value = columns.value.filter(c => c.id !== id);
+ layout.value = layout.value.map(ids => ids.filter(_id => _id !== id)).filter(ids => ids.length > 0);
+ saveCurrentDeckProfile();
+}
+
+export function swapColumn(a: Column['id'], b: Column['id']) {
+ const aX = layout.value.findIndex(ids => ids.indexOf(a) !== -1);
+ const aY = layout.value[aX].findIndex(id => id === a);
+ const bX = layout.value.findIndex(ids => ids.indexOf(b) !== -1);
+ const bY = layout.value[bX].findIndex(id => id === b);
+ const newLayout = deepClone(layout.value);
+ newLayout[aX][aY] = b;
+ newLayout[bX][bY] = a;
+ layout.value = newLayout;
+ saveCurrentDeckProfile();
+}
+
+export function swapLeftColumn(id: Column['id']) {
+ const newLayout = deepClone(layout.value);
+ layout.value.some((ids, i) => {
+ if (ids.includes(id)) {
+ const left = layout.value[i - 1];
+ if (left) {
+ newLayout[i - 1] = layout.value[i];
+ newLayout[i] = left;
+ layout.value = newLayout;
+ }
+ return true;
+ }
+ return false;
+ });
+ saveCurrentDeckProfile();
+}
+
+export function swapRightColumn(id: Column['id']) {
+ const newLayout = deepClone(layout.value);
+ layout.value.some((ids, i) => {
+ if (ids.includes(id)) {
+ const right = layout.value[i + 1];
+ if (right) {
+ newLayout[i + 1] = layout.value[i];
+ newLayout[i] = right;
+ layout.value = newLayout;
+ }
+ return true;
+ }
+ return false;
+ });
+ saveCurrentDeckProfile();
+}
+
+export function swapUpColumn(id: Column['id']) {
+ const newLayout = deepClone(layout.value);
+ const idsIndex = layout.value.findIndex(ids => ids.includes(id));
+ const ids = deepClone(layout.value[idsIndex]);
+ ids.some((x, i) => {
+ if (x === id) {
+ const up = ids[i - 1];
+ if (up) {
+ ids[i - 1] = id;
+ ids[i] = up;
+
+ newLayout[idsIndex] = ids;
+ layout.value = newLayout;
+ }
+ return true;
+ }
+ return false;
+ });
+ saveCurrentDeckProfile();
+}
+
+export function swapDownColumn(id: Column['id']) {
+ const newLayout = deepClone(layout.value);
+ const idsIndex = layout.value.findIndex(ids => ids.includes(id));
+ const ids = deepClone(layout.value[idsIndex]);
+ ids.some((x, i) => {
+ if (x === id) {
+ const down = ids[i + 1];
+ if (down) {
+ ids[i + 1] = id;
+ ids[i] = down;
+
+ newLayout[idsIndex] = ids;
+ layout.value = newLayout;
+ }
+ return true;
+ }
+ return false;
+ });
+ saveCurrentDeckProfile();
+}
+
+export function stackLeftColumn(id: Column['id']) {
+ let newLayout = deepClone(layout.value);
+ const i = layout.value.findIndex(ids => ids.includes(id));
+ newLayout = newLayout.map(ids => ids.filter(_id => _id !== id));
+ newLayout[i - 1].push(id);
+ newLayout = newLayout.filter(ids => ids.length > 0);
+ layout.value = newLayout;
+ saveCurrentDeckProfile();
+}
+
+export function popRightColumn(id: Column['id']) {
+ let newLayout = deepClone(layout.value);
+ const i = layout.value.findIndex(ids => ids.includes(id));
+ const affected = newLayout[i];
+ newLayout = newLayout.map(ids => ids.filter(_id => _id !== id));
+ newLayout.splice(i + 1, 0, [id]);
+ newLayout = newLayout.filter(ids => ids.length > 0);
+ layout.value = newLayout;
+
+ const newColumns = deepClone(columns.value);
+ for (const column of newColumns) {
+ if (affected.includes(column.id)) {
+ column.active = true;
+ }
+ }
+ columns.value = newColumns;
+
+ saveCurrentDeckProfile();
+}
+
+export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
+ const newColumns = deepClone(columns.value);
+ const columnIndex = columns.value.findIndex(c => c.id === id);
+ const column = deepClone(columns.value[columnIndex]);
+ if (column == null) return;
+ if (column.widgets == null) column.widgets = [];
+ column.widgets.unshift(widget);
+ newColumns[columnIndex] = column;
+ columns.value = newColumns;
+ saveCurrentDeckProfile();
+}
+
+export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
+ const newColumns = deepClone(columns.value);
+ const columnIndex = columns.value.findIndex(c => c.id === id);
+ const column = deepClone(columns.value[columnIndex]);
+ if (column == null) return;
+ if (column.widgets == null) column.widgets = [];
+ column.widgets = column.widgets.filter(w => w.id !== widget.id);
+ newColumns[columnIndex] = column;
+ columns.value = newColumns;
+ saveCurrentDeckProfile();
+}
+
+export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
+ const newColumns = deepClone(columns.value);
+ const columnIndex = columns.value.findIndex(c => c.id === id);
+ const column = deepClone(columns.value[columnIndex]);
+ if (column == null) return;
+ column.widgets = widgets;
+ newColumns[columnIndex] = column;
+ columns.value = newColumns;
+ saveCurrentDeckProfile();
+}
+
+export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
+ const newColumns = deepClone(columns.value);
+ const columnIndex = columns.value.findIndex(c => c.id === id);
+ const column = deepClone(columns.value[columnIndex]);
+ if (column == null) return;
+ if (column.widgets == null) column.widgets = [];
+ column.widgets = column.widgets.map(w => w.id === widgetId ? {
+ ...w,
+ data: widgetData,
+ } : w);
+ newColumns[columnIndex] = column;
+ columns.value = newColumns;
+ saveCurrentDeckProfile();
+}
+
+export function updateColumn(id: Column['id'], column: Partial<Column>) {
+ const newColumns = deepClone(columns.value);
+ const columnIndex = columns.value.findIndex(c => c.id === id);
+ const currentColumn = deepClone(columns.value[columnIndex]);
+ if (currentColumn == null) return;
+ for (const [k, v] of Object.entries(column)) {
+ currentColumn[k] = v;
+ }
+ newColumns[columnIndex] = currentColumn;
+ columns.value = newColumns;
+ saveCurrentDeckProfile();
+}
+
+export function switchProfileMenu(ev: MouseEvent) {
+ const items: MenuItem[] = prefer.s['deck.profile'] ? [{
+ text: prefer.s['deck.profile'],
+ active: true,
+ action: () => {},
+ }] : [];
+
+ const profiles = prefer.s['deck.profiles'];
+
+ items.push(...(profiles.filter(p => p.name !== prefer.s['deck.profile']).map(p => ({
+ text: p.name,
+ action: () => {
+ switchProfile(p);
+ },
+ }))), { type: 'divider' as const }, {
+ text: i18n.ts._deck.newProfile,
+ icon: 'ti ti-plus',
+ action: async () => {
+ const { canceled, result: name } = await os.inputText({
+ title: i18n.ts._deck.profile,
+ minLength: 1,
+ });
+
+ if (canceled || name == null || name.trim() === '') return;
+
+ addProfile(name);
+ },
+ });
+
+ os.popupMenu(items, ev.currentTarget ?? ev.target);
+}
diff --git a/packages/frontend/src/di.ts b/packages/frontend/src/di.ts
new file mode 100644
index 0000000000..f9fc282315
--- /dev/null
+++ b/packages/frontend/src/di.ts
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { InjectionKey, Ref } from 'vue';
+import type { Router } from '@/router.js';
+
+export const DI = {
+ routerCurrentDepth: Symbol() as InjectionKey<number>,
+ router: Symbol() as InjectionKey<Router>,
+ mock: Symbol() as InjectionKey<boolean>,
+ pageMetadata: Symbol() as InjectionKey<Ref<Record<string, any>>>,
+ viewId: Symbol() as InjectionKey<string>,
+ currentStickyTop: Symbol() as InjectionKey<Ref<number>>,
+ currentStickyBottom: Symbol() as InjectionKey<Ref<number>>,
+};
diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts
index f88996019f..a68cd1b18b 100644
--- a/packages/frontend/src/directives/adaptive-bg.ts
+++ b/packages/frontend/src/directives/adaptive-bg.ts
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
-import { getBgColor } from '@/scripts/get-bg-color.js';
+import type { Directive } from 'vue';
+import { getBgColor } from '@/utility/get-bg-color.js';
export default {
mounted(src, binding, vn) {
diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts
index 1305f312bd..8072a1ffd9 100644
--- a/packages/frontend/src/directives/adaptive-border.ts
+++ b/packages/frontend/src/directives/adaptive-border.ts
@@ -3,19 +3,34 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
-import { getBgColor } from '@/scripts/get-bg-color.js';
+import type { Directive } from 'vue';
+import { getBgColor } from '@/utility/get-bg-color.js';
+import { globalEvents } from '@/events.js';
+
+const handlerMap = new WeakMap<any, any>();
export default {
mounted(src, binding, vn) {
- const parentBg = getBgColor(src.parentElement) ?? 'transparent';
+ function calc() {
+ const parentBg = getBgColor(src.parentElement) ?? 'transparent';
- const myBg = window.getComputedStyle(src).backgroundColor;
+ const myBg = window.getComputedStyle(src).backgroundColor;
- if (parentBg === myBg) {
- src.style.borderColor = 'var(--MI_THEME-divider)';
- } else {
- src.style.borderColor = myBg;
+ if (parentBg === myBg) {
+ src.style.borderColor = 'var(--MI_THEME-divider)';
+ } else {
+ src.style.borderColor = myBg;
+ }
}
+
+ handlerMap.set(src, calc);
+
+ calc();
+
+ globalEvents.on('themeChanged', calc);
+ },
+
+ unmounted(src, binding, vn) {
+ globalEvents.off('themeChanged', handlerMap.get(src));
},
} as Directive;
diff --git a/packages/frontend/src/directives/anim.ts b/packages/frontend/src/directives/anim.ts
index d5b6ae4287..ad0cb5ed81 100644
--- a/packages/frontend/src/directives/anim.ts
+++ b/packages/frontend/src/directives/anim.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
export default {
beforeMount(src, binding, vn) {
diff --git a/packages/frontend/src/directives/appear.ts b/packages/frontend/src/directives/appear.ts
index 706d4a9ee4..802477e00b 100644
--- a/packages/frontend/src/directives/appear.ts
+++ b/packages/frontend/src/directives/appear.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
export default {
mounted(src, binding, vn) {
diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts
index 5bb48bbcdd..c34f351fb3 100644
--- a/packages/frontend/src/directives/click-anime.ts
+++ b/packages/frontend/src/directives/click-anime.ts
@@ -3,12 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
-import { defaultStore } from '@/store.js';
+import type { Directive } from 'vue';
+import { prefer } from '@/preferences.js';
export default {
mounted(el: HTMLElement, binding, vn) {
- if (!defaultStore.state.animation) return;
+ if (!prefer.s.animation) return;
const target = el.children[0];
diff --git a/packages/frontend/src/directives/follow-append.ts b/packages/frontend/src/directives/follow-append.ts
index 615dd99fa8..f3eaac10e3 100644
--- a/packages/frontend/src/directives/follow-append.ts
+++ b/packages/frontend/src/directives/follow-append.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
import { getScrollContainer, getScrollPosition } from '@@/js/scroll.js';
export default {
diff --git a/packages/frontend/src/directives/get-size.ts b/packages/frontend/src/directives/get-size.ts
index 2655c76c48..488f201a0d 100644
--- a/packages/frontend/src/directives/get-size.ts
+++ b/packages/frontend/src/directives/get-size.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
const mountings = new Map<Element, {
resize: ResizeObserver;
diff --git a/packages/frontend/src/directives/hotkey.ts b/packages/frontend/src/directives/hotkey.ts
index 0e5c7ede24..63637ab2ba 100644
--- a/packages/frontend/src/directives/hotkey.ts
+++ b/packages/frontend/src/directives/hotkey.ts
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
-import { makeHotkey } from '@/scripts/hotkey.js';
+import type { Directive } from 'vue';
+import { makeHotkey } from '@/utility/hotkey.js';
export default {
mounted(el, binding) {
@@ -13,7 +13,7 @@ export default {
el._keyHandler = makeHotkey(binding.value);
if (el._hotkey_global) {
- document.addEventListener('keydown', el._keyHandler, { passive: false });
+ window.document.addEventListener('keydown', el._keyHandler, { passive: false });
} else {
el.addEventListener('keydown', el._keyHandler, { passive: false });
}
@@ -21,7 +21,7 @@ export default {
unmounted(el) {
if (el._hotkey_global) {
- document.removeEventListener('keydown', el._keyHandler);
+ window.document.removeEventListener('keydown', el._keyHandler);
} else {
el.removeEventListener('keydown', el._keyHandler);
}
diff --git a/packages/frontend/src/directives/index.ts b/packages/frontend/src/directives/index.ts
index bda7738ccd..9555045afe 100644
--- a/packages/frontend/src/directives/index.ts
+++ b/packages/frontend/src/directives/index.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { App } from 'vue';
+import type { App } from 'vue';
import userPreview from './user-preview.js';
import getSize from './get-size.js';
diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts
index aa26b94d0b..0af19e6ca3 100644
--- a/packages/frontend/src/directives/panel.ts
+++ b/packages/frontend/src/directives/panel.ts
@@ -3,14 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
-import { getBgColor } from '@/scripts/get-bg-color.js';
+import type { Directive } from 'vue';
+import { getBgColor } from '@/utility/get-bg-color.js';
export default {
mounted(src, binding, vn) {
const parentBg = getBgColor(src.parentElement) ?? 'transparent';
- const myBg = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel');
+ const myBg = getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel');
if (parentBg === myBg) {
src.style.backgroundColor = 'var(--MI_THEME-bg)';
diff --git a/packages/frontend/src/directives/ripple.ts b/packages/frontend/src/directives/ripple.ts
index a043ff212d..614cd37011 100644
--- a/packages/frontend/src/directives/ripple.ts
+++ b/packages/frontend/src/directives/ripple.ts
@@ -4,12 +4,14 @@
*/
import MkRippleEffect from '@/components/MkRippleEffect.vue';
+import { prefer } from '@/preferences.js';
import { popup } from '@/os.js';
export default {
mounted(el, binding, vn) {
// 明示的に false であればバインドしない
if (binding.value === false) return;
+ if (!prefer.s.animation) return;
el.addEventListener('click', () => {
const rect = el.getBoundingClientRect();
diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts
index 251ce5675f..750acd0588 100644
--- a/packages/frontend/src/directives/tooltip.ts
+++ b/packages/frontend/src/directives/tooltip.ts
@@ -6,8 +6,9 @@
// TODO: useTooltip関数使うようにしたい
// ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明
-import { defineAsyncComponent, Directive, ref } from 'vue';
-import { isTouchUsing } from '@/scripts/touch.js';
+import { defineAsyncComponent, ref } from 'vue';
+import type { Directive } from 'vue';
+import { isTouchUsing } from '@/utility/touch.js';
import { popup, alert } from '@/os.js';
const start = isTouchUsing ? 'touchstart' : 'mouseenter';
@@ -46,7 +47,7 @@ export default {
}
self.show = () => {
- if (!document.body.contains(el)) return;
+ if (!window.document.body.contains(el)) return;
if (self._close) return;
if (self.text == null) return;
diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts
index 278d842d09..94deea82c7 100644
--- a/packages/frontend/src/directives/user-preview.ts
+++ b/packages/frontend/src/directives/user-preview.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { defineAsyncComponent, Directive, ref } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
+import type { Directive } from 'vue';
import { popup } from '@/os.js';
export class UserPreview {
@@ -30,7 +31,7 @@ export class UserPreview {
}
private show() {
- if (!document.body.contains(this.el)) return;
+ if (!window.document.body.contains(this.el)) return;
if (this.promise) return;
const showing = ref(true);
@@ -57,7 +58,7 @@ export class UserPreview {
};
this.checkTimer = window.setInterval(() => {
- if (!document.body.contains(this.el)) {
+ if (!window.document.body.contains(this.el)) {
window.clearTimeout(this.showTimer);
window.clearTimeout(this.hideTimer);
this.close();
diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts
index d476aec04a..dfd3d4120c 100644
--- a/packages/frontend/src/events.ts
+++ b/packages/frontend/src/events.ts
@@ -7,7 +7,7 @@ import { EventEmitter } from 'eventemitter3';
import * as Misskey from 'misskey-js';
export const globalEvents = new EventEmitter<{
+ themeChanging: () => void;
themeChanged: () => void;
clientNotification: (notification: Misskey.entities.Notification) => void;
- requestClearPageCache: () => void;
}>();
diff --git a/packages/frontend/src/filters/number.ts b/packages/frontend/src/filters/number.ts
index 10fb64deb4..479afd58d4 100644
--- a/packages/frontend/src/filters/number.ts
+++ b/packages/frontend/src/filters/number.ts
@@ -5,4 +5,4 @@
import { numberFormat } from '@@/js/intl-const.js';
-export default n => n == null ? 'N/A' : numberFormat.format(n);
+export default (n?: number) => n == null ? 'N/A' : numberFormat.format(n);
diff --git a/packages/frontend/src/i.ts b/packages/frontend/src/i.ts
new file mode 100644
index 0000000000..a71ed1671f
--- /dev/null
+++ b/packages/frontend/src/i.ts
@@ -0,0 +1,34 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { reactive } from 'vue';
+import * as Misskey from 'misskey-js';
+import { miLocalStorage } from '@/local-storage.js';
+
+// TODO: 他のタブと永続化されたstateを同期
+
+type AccountWithToken = Misskey.entities.MeDetailed & { token: string };
+
+const accountData = miLocalStorage.getItem('account');
+
+// TODO: 外部からはreadonlyに
+export const $i = accountData ? reactive(JSON.parse(accountData) as AccountWithToken) : null;
+
+export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
+export const iAmAdmin = $i != null && $i.isAdmin;
+
+export function ensureSignin() {
+ if ($i == null) throw new Error('signin required');
+ return $i;
+}
+
+export let notesCount = $i == null ? 0 : $i.notesCount;
+export function incNotesCount() {
+ notesCount++;
+}
+
+if (_DEV_) {
+ (window as any).$i = $i;
+}
diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts
index 71cb42b30c..e75e3dfd34 100644
--- a/packages/frontend/src/instance.ts
+++ b/packages/frontend/src/instance.ts
@@ -5,14 +5,14 @@
import { computed, reactive } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { miLocalStorage } from '@/local-storage.js';
import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@@/js/const.js';
// TODO: 他のタブと永続化されたstateを同期
//#region loader
-const providedMetaEl = document.getElementById('misskey_meta');
+const providedMetaEl = window.document.getElementById('misskey_meta');
let cachedMeta = miLocalStorage.getItem('instance') ? JSON.parse(miLocalStorage.getItem('instance')!) : null;
let cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0;
diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/lib/nirax.ts
index 965bd6f0bc..a97803e879 100644
--- a/packages/frontend/src/nirax.ts
+++ b/packages/frontend/src/lib/nirax.ts
@@ -5,8 +5,9 @@
// NIRAX --- A lightweight router
-import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
+import { onBeforeUnmount, onMounted, shallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3';
+import type { Component, ShallowRef } from 'vue';
function safeURIDecode(str: string): string {
try {
@@ -22,7 +23,6 @@ interface RouteDefBase {
loginRequired?: boolean;
name?: string;
hash?: string;
- globalCacheKey?: string;
children?: RouteDef[];
}
@@ -45,31 +45,28 @@ type ParsedPath = (string | {
optional?: boolean;
})[];
-export type RouterEvent = {
+export type RouterEvents = {
change: (ctx: {
- beforePath: string;
- path: string;
- resolved: Resolved;
- key: string;
+ beforeFullPath: string;
+ fullPath: string;
+ resolved: PathResolvedResult;
}) => void;
replace: (ctx: {
- path: string;
- key: string;
+ fullPath: string;
}) => void;
push: (ctx: {
- beforePath: string;
- path: string;
+ beforeFullPath: string;
+ fullPath: string;
route: RouteDef | null;
props: Map<string, string> | null;
- key: string;
}) => void;
same: () => void;
-}
+};
-export type Resolved = {
+export type PathResolvedResult = {
route: RouteDef;
props: Map<string, string | boolean>;
- child?: Resolved;
+ child?: PathResolvedResult;
redirected?: boolean;
/** @internal */
@@ -105,124 +102,39 @@ function parsePath(path: string): ParsedPath {
return res;
}
-export interface IRouter extends EventEmitter<RouterEvent> {
- current: Resolved;
- currentRef: ShallowRef<Resolved>;
- currentRoute: ShallowRef<RouteDef>;
- navHook: ((path: string, flag?: RouterFlag) => boolean) | null;
-
- /**
- * ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
- */
- init(): void;
-
- resolve(path: string): Resolved | null;
-
- getCurrentPath(): string;
-
- getCurrentKey(): string;
-
- push(path: string, flag?: RouterFlag): void;
-
- replace(path: string, key?: string | null): void;
-
- /** @see EventEmitter */
- eventNames(): Array<EventEmitter.EventNames<RouterEvent>>;
-
- /** @see EventEmitter */
- listeners<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T
- ): Array<EventEmitter.EventListener<RouterEvent, T>>;
-
- /** @see EventEmitter */
- listenerCount(
- event: EventEmitter.EventNames<RouterEvent>
- ): number;
-
- /** @see EventEmitter */
- emit<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- ...args: EventEmitter.EventArgs<RouterEvent, T>
- ): boolean;
-
- /** @see EventEmitter */
- on<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any
- ): this;
-
- /** @see EventEmitter */
- addListener<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any
- ): this;
-
- /** @see EventEmitter */
- once<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any
- ): this;
-
- /** @see EventEmitter */
- removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn?: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- once?: boolean | undefined
- ): this;
-
- /** @see EventEmitter */
- off<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn?: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- once?: boolean | undefined
- ): this;
-
- /** @see EventEmitter */
- removeAllListeners(
- event?: EventEmitter.EventNames<RouterEvent>
- ): this;
-}
-
-export class Router extends EventEmitter<RouterEvent> implements IRouter {
- private routes: RouteDef[];
- public current: Resolved;
- public currentRef: ShallowRef<Resolved>;
+export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
+ private routes: DEF;
+ public current: PathResolvedResult;
+ public currentRef: ShallowRef<PathResolvedResult>;
public currentRoute: ShallowRef<RouteDef>;
- private currentPath: string;
+ private currentFullPath: string; // /foo/bar?baz=qux#hash
private isLoggedIn: boolean;
private notFoundPageComponent: Component;
- private currentKey = Date.now().toString();
private redirectCount = 0;
- public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null;
+ public navHook: ((fullPath: string, flag?: RouterFlag) => boolean) | null = null;
- constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
+ constructor(routes: DEF, currentFullPath: Nirax<DEF>['currentFullPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
super();
this.routes = routes;
- this.current = this.resolve(currentPath)!;
+ this.current = this.resolve(currentFullPath)!;
this.currentRef = shallowRef(this.current);
this.currentRoute = shallowRef(this.current.route);
- this.currentPath = currentPath;
+ this.currentFullPath = currentFullPath;
this.isLoggedIn = isLoggedIn;
this.notFoundPageComponent = notFoundPageComponent;
}
public init() {
- const res = this.navigate(this.currentPath, null, false);
+ const res = this.navigate(this.currentFullPath, false);
this.emit('replace', {
- path: res._parsedRoute.fullPath,
- key: this.currentKey,
+ fullPath: res._parsedRoute.fullPath,
});
}
- public resolve(path: string): Resolved | null {
- const fullPath = path;
+ public resolve(fullPath: string): PathResolvedResult | null {
+ let path = fullPath;
let queryString: string | null = null;
let hash: string | null = null;
if (path[0] === '/') path = path.substring(1);
@@ -241,9 +153,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
hash,
};
- if (_DEV_) console.log('Routing: ', path, queryString);
-
- function check(routes: RouteDef[], _parts: string[]): Resolved | null {
+ function check(routes: RouteDef[], _parts: string[]): PathResolvedResult | null {
forEachRouteLoop:
for (const route of routes) {
let parts = [..._parts];
@@ -346,14 +256,14 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
return check(this.routes, _parts);
}
- private navigate(path: string, key: string | null | undefined, emitChange = true, _redirected = false): Resolved {
- const beforePath = this.currentPath;
- this.currentPath = path;
+ private navigate(fullPath: string, emitChange = true, _redirected = false): PathResolvedResult {
+ const beforeFullPath = this.currentFullPath;
+ this.currentFullPath = fullPath;
- const res = this.resolve(this.currentPath);
+ const res = this.resolve(this.currentFullPath);
if (res == null) {
- throw new Error('no route found for: ' + path);
+ throw new Error('no route found for: ' + fullPath);
}
if ('redirect' in res.route) {
@@ -367,7 +277,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
if (_redirected && this.redirectCount++ > 10) {
throw new Error('redirect loop detected');
}
- return this.navigate(redirectPath, null, emitChange, true);
+ return this.navigate(redirectPath, emitChange, true);
}
if (res.route.loginRequired && !this.isLoggedIn) {
@@ -375,19 +285,15 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
res.props.set('showLoginPopup', true);
}
- const isSamePath = beforePath === path;
- if (isSamePath && key == null) key = this.currentKey;
this.current = res;
this.currentRef.value = res;
this.currentRoute.value = res.route;
- this.currentKey = res.route.globalCacheKey ?? key ?? path;
if (emitChange && res.route.path !== '/:(*)') {
this.emit('change', {
- beforePath,
- path,
+ beforeFullPath,
+ fullPath,
resolved: res,
- key: this.currentKey,
});
}
@@ -398,70 +304,45 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
};
}
- public getCurrentPath() {
- return this.currentPath;
- }
-
- public getCurrentKey() {
- return this.currentKey;
+ public getCurrentFullPath() {
+ return this.currentFullPath;
}
- public push(path: string, flag?: RouterFlag) {
- const beforePath = this.currentPath;
- if (path === beforePath) {
+ public push(fullPath: string, flag?: RouterFlag) {
+ const beforeFullPath = this.currentFullPath;
+ if (fullPath === beforeFullPath) {
this.emit('same');
return;
}
if (this.navHook) {
- const cancel = this.navHook(path, flag);
+ const cancel = this.navHook(fullPath, flag);
if (cancel) return;
}
- const res = this.navigate(path, null);
+ const res = this.navigate(fullPath);
if (res.route.path === '/:(*)') {
- location.href = path;
+ window.location.href = fullPath;
} else {
this.emit('push', {
- beforePath,
- path: res._parsedRoute.fullPath,
+ beforeFullPath,
+ fullPath: res._parsedRoute.fullPath,
route: res.route,
props: res.props,
- key: this.currentKey,
});
}
}
- public replace(path: string, key?: string | null) {
- const res = this.navigate(path, key);
+ public replace(fullPath: string) {
+ const res = this.navigate(fullPath);
this.emit('replace', {
- path: res._parsedRoute.fullPath,
- key: this.currentKey,
+ fullPath: res._parsedRoute.fullPath,
});
}
-}
-
-export function useScrollPositionManager(getScrollContainer: () => HTMLElement | null, router: IRouter) {
- const scrollPosStore = new Map<string, number>();
-
- onMounted(() => {
- const scrollContainer = getScrollContainer();
- if (scrollContainer == null) return;
- scrollContainer.addEventListener('scroll', () => {
- scrollPosStore.set(router.getCurrentKey(), scrollContainer.scrollTop);
- }, { passive: true });
+ public useListener<E extends keyof RouterEvents, L = RouterEvents[E]>(event: E, listener: L) {
+ this.addListener(event, listener);
- router.addListener('change', ctx => {
- const scrollPos = scrollPosStore.get(ctx.key) ?? 0;
- scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
- if (scrollPos !== 0) {
- window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
- scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
- }, 100);
- }
- });
-
- router.addListener('same', () => {
- scrollContainer.scroll({ top: 0, behavior: 'smooth' });
+ onBeforeUnmount(() => {
+ this.removeListener(event, listener);
});
- });
+ }
}
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/lib/pizzax.ts
index 7740fe0d39..a232ced75e 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/lib/pizzax.ts
@@ -5,15 +5,16 @@
// PIZZAX --- A lightweight store
-import { onUnmounted, Ref, ref, watch } from 'vue';
+import { onUnmounted, ref, watch } from 'vue';
import { BroadcastChannel } from 'broadcast-channel';
-import { $i } from '@/account.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { get, set } from '@/scripts/idb-proxy.js';
-import { defaultStore } from '@/store.js';
+import type { Ref } from 'vue';
+import { $i } from '@/i.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { get, set } from '@/utility/idb-proxy.js';
+import { store } from '@/store.js';
import { useStream } from '@/stream.js';
-import { deepClone } from '@/scripts/clone.js';
-import { deepMerge } from '@/scripts/merge.js';
+import { deepClone } from '@/utility/clone.js';
+import { deepMerge } from '@/utility/merge.js';
type StateDef = Record<string, {
where: 'account' | 'device' | 'deviceAccount';
@@ -32,7 +33,7 @@ type PizzaxChannelMessage<T extends StateDef> = {
userId?: string;
};
-export class Storage<T extends StateDef> {
+export class Pizzax<T extends StateDef> {
public readonly ready: Promise<void>;
public readonly loaded: Promise<void>;
@@ -44,8 +45,15 @@ export class Storage<T extends StateDef> {
public readonly def: T;
// TODO: これが実装されたらreadonlyにしたい: https://github.com/microsoft/TypeScript/issues/37487
- public readonly state: State<T>;
- public readonly reactiveState: ReactiveState<T>;
+ /**
+ * static / state の略 (static が予約語のため)
+ */
+ public readonly s: State<T>;
+
+ /**
+ * reactive の略
+ */
+ public readonly r: ReactiveState<T>;
private pizzaxChannel: BroadcastChannel<PizzaxChannelMessage<T>>;
@@ -69,12 +77,12 @@ export class Storage<T extends StateDef> {
this.pizzaxChannel = new BroadcastChannel(`pizzax::${key}`);
- this.state = {} as State<T>;
- this.reactiveState = {} as ReactiveState<T>;
+ this.s = {} as State<T>;
+ this.r = {} as ReactiveState<T>;
for (const [k, v] of Object.entries(def) as [keyof T, T[keyof T]['default']][]) {
- this.state[k] = v.default;
- this.reactiveState[k] = ref(v.default);
+ this.s[k] = v.default;
+ this.r[k] = ref(v.default);
}
this.ready = this.init();
@@ -105,14 +113,13 @@ export class Storage<T extends StateDef> {
for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) {
if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) {
- this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceState[k], v.default);
+ this.r[k].value = this.s[k] = this.mergeState<T[keyof T]['default']>(deviceState[k], v.default);
} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) {
- this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(registryCache[k], v.default);
+ this.r[k].value = this.s[k] = this.mergeState<T[keyof T]['default']>(registryCache[k], v.default);
} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) {
- this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
+ this.r[k].value = this.s[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
} else {
- this.reactiveState[k].value = this.state[k] = v.default;
- if (_DEV_) console.log('Use default value', k, v.default);
+ this.r[k].value = this.s[k] = v.default;
}
}
@@ -120,7 +127,7 @@ export class Storage<T extends StateDef> {
// アカウント変更すればunisonReloadが効くため、このreturnが発火することは
// まずないと思うけど一応弾いておく
if (where === 'deviceAccount' && !($i && userId !== $i.id)) return;
- this.reactiveState[key].value = this.state[key] = value;
+ this.r[key].value = this.s[key] = value;
});
if ($i) {
@@ -128,9 +135,9 @@ export class Storage<T extends StateDef> {
// streamingのuser storage updateイベントを監視して更新
connection.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => {
- if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return;
+ if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.s[key] === value) return;
- this.reactiveState[key].value = this.state[key] = value;
+ this.r[key].value = this.s[key] = value;
this.addIdbSetJob(async () => {
const cache = await get(this.registryCacheKeyName);
@@ -148,7 +155,7 @@ export class Storage<T extends StateDef> {
if ($i) {
// api関数と循環参照なので一応setTimeoutしておく
window.setTimeout(async () => {
- await defaultStore.ready;
+ await store.ready;
misskeyApi('i/registry/get-all', { scope: ['client', this.key] })
.then(kvs => {
@@ -156,10 +163,10 @@ export class Storage<T extends StateDef> {
for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) {
if (v.where === 'account') {
if (Object.prototype.hasOwnProperty.call(kvs, k)) {
- this.reactiveState[k].value = this.state[k] = (kvs as Partial<T>)[k];
+ this.r[k].value = this.s[k] = (kvs as Partial<T>)[k];
cache[k] = (kvs as Partial<T>)[k];
} else {
- this.reactiveState[k].value = this.state[k] = v.default;
+ this.r[k].value = this.s[k] = v.default;
}
}
}
@@ -179,12 +186,9 @@ export class Storage<T extends StateDef> {
// (JSON.parse(JSON.stringify(value))の代わり)
const rawValue = deepClone(value);
- if (_DEV_) console.log('set', key, rawValue, value);
-
- this.reactiveState[key].value = this.state[key] = rawValue;
+ this.r[key].value = this.s[key] = rawValue;
return this.addIdbSetJob(async () => {
- if (_DEV_) console.log(`set ${String(key)} start`);
switch (this.def[key].where) {
case 'device': {
this.pizzaxChannel.postMessage({
@@ -223,12 +227,11 @@ export class Storage<T extends StateDef> {
break;
}
}
- if (_DEV_) console.log(`set ${String(key)} complete`);
});
}
public push<K extends keyof T>(key: K, value: ArrayElement<T[K]['default']>): void {
- const currentState = this.state[key];
+ const currentState = this.s[key];
this.set(key, [...currentState, value]);
}
@@ -241,17 +244,18 @@ export class Storage<T extends StateDef> {
* 特定のキーの、簡易的なgetter/setterを作ります
* 主にvue上で設定コントロールのmodelとして使う用
*/
+ // TODO: 廃止
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]);
+ get: () => R;
+ set: (value: R) => void;
+ } {
+ const valueRef = ref(this.s[key]);
- const stop = watch(this.reactiveState[key], val => {
+ const stop = watch(this.r[key], val => {
valueRef.value = val;
});
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
index 89c0a4b849..c43bd7cd9a 100644
--- a/packages/frontend/src/local-storage.ts
+++ b/packages/frontend/src/local-storage.ts
@@ -3,13 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-export type Keys =
+export type Keys = (
'v' |
'lastVersion' |
'instance' |
'instanceCachedAt' |
'account' |
- 'accounts' |
'latestDonationInfoShownAt' |
'neverShowDonationInfo' |
'neverShowLocalOnlyInfo' |
@@ -19,7 +18,6 @@ export type Keys =
'drafts' |
'hashtags' |
'wallpaper' |
- 'theme' |
'colorScheme' |
'useSystemFont' |
'fontSize' |
@@ -29,18 +27,23 @@ export type Keys =
'locale' |
'localeVersion' |
'theme' |
+ 'themeId' |
'customCss' |
- 'message_drafts' |
+ 'chatMessageDrafts' |
'scratchpad' |
'debug' |
+ 'preferences' |
+ 'latestPreferencesUpdate' |
+ 'hidePreferencesRestoreSuggestion' |
`miux:${string}` |
`ui:folder:${string}` |
- `themes:${string}` |
+ `themes:${string}` | // DEPRECATED
`aiscript:${string}` |
'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~)
'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~);
`channelLastReadedAt:${string}` |
`idbfallback::${string}`
+);
// セッション毎に廃棄されるLocalStorage代替(セーフモードなどで使用できそう)
//const safeSessionStorage = new Map<Keys, string>();
diff --git a/packages/frontend/src/memory-storage.ts b/packages/frontend/src/memory-storage.ts
new file mode 100644
index 0000000000..df0dc1308f
--- /dev/null
+++ b/packages/frontend/src/memory-storage.ts
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export type MemoryStorage = {
+ has: (key: string) => boolean;
+ getItem: <T>(key: string) => T | null;
+ setItem: (key: string, value: unknown) => void;
+ removeItem: (key: string) => void;
+ clear: () => void;
+ size: number;
+};
+
+class MemoryStorageImpl implements MemoryStorage {
+ private readonly storage: Map<string, unknown>;
+
+ constructor() {
+ this.storage = new Map();
+ }
+
+ has(key: string): boolean {
+ return this.storage.has(key);
+ }
+
+ getItem<T>(key: string): T | null {
+ return this.storage.has(key) ? this.storage.get(key) as T : null;
+ }
+
+ setItem(key: string, value: unknown): void {
+ this.storage.set(key, value);
+ }
+
+ removeItem(key: string): void {
+ this.storage.delete(key);
+ }
+
+ clear(): void {
+ this.storage.clear();
+ }
+
+ get size(): number {
+ return this.storage.size;
+ }
+}
+
+export function createMemoryStorage(): MemoryStorage {
+ return new MemoryStorageImpl();
+}
+
+/**
+ * SessionStorageよりも更に短い期間でクリアされるストレージです
+ * - ブラウザの再読み込みやタブの閉じると内容が揮発します
+ * - このストレージは他のタブと共有されません
+ * - アカウント切り替えやログアウトを行うと内容が揮発します
+ */
+export const defaultMemoryStorage: MemoryStorage = createMemoryStorage();
diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts
index 2bcb69b145..9d4988338a 100644
--- a/packages/frontend/src/navbar.ts
+++ b/packages/frontend/src/navbar.ts
@@ -4,16 +4,16 @@
*/
import { computed, defineAsyncComponent, reactive } from 'vue';
-import { clearCache } from './scripts/clear-cache.js';
+import { ui } from '@@/js/config.js';
+import { clearCache } from './utility/clear-cache.js';
import { instance } from './instance.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { miLocalStorage } from '@/local-storage.js';
-import { openInstanceMenu } from '@/ui/_common_/common.js';
-import { lookup } from '@/scripts/lookup.js';
+import { openInstanceMenu, openToolsMenu } from '@/ui/_common_/common.js';
+import { lookup } from '@/utility/lookup.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { ui } from '@@/js/config.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { unisonReload } from '@/utility/unison-reload.js';
export const navbarItemDef = reactive({
notifications: {
@@ -130,6 +130,12 @@ export const navbarItemDef = reactive({
icon: 'ti ti-device-tv',
to: '/channels',
},
+ chat: {
+ title: i18n.ts.chat,
+ icon: 'ti ti-message',
+ to: '/chat',
+ indicated: computed(() => $i != null && $i.hasUnreadChatMessages),
+ },
achievements: {
title: i18n.ts.achievements,
icon: 'ti ti-medal',
@@ -180,7 +186,7 @@ export const navbarItemDef = reactive({
title: i18n.ts.reload,
icon: 'ti ti-refresh',
action: (ev) => {
- location.reload();
+ window.location.reload();
},
},
profile: {
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index 59af5ad2b3..dce2a8e910 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -5,30 +5,31 @@
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
-import { Component, markRaw, Ref, ref, defineAsyncComponent, nextTick } from 'vue';
+import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
import { EventEmitter } from 'eventemitter3';
import * as Misskey from 'misskey-js';
+import type { Component, Ref } from 'vue';
import type { ComponentProps as CP } from 'vue-component-type-helpers';
-import type { Form, GetFormResultType } from '@/scripts/form.js';
+import type { Form, GetFormResultType } from '@/utility/form.js';
import type { MenuItem } from '@/types/menu.js';
import type { PostFormProps } from '@/types/post-form.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { prefer } from '@/preferences.js';
import { i18n } from '@/i18n.js';
import MkPostFormDialog from '@/components/MkPostFormDialog.vue';
import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
import MkPageWindow from '@/components/MkPageWindow.vue';
import MkToast from '@/components/MkToast.vue';
import MkDialog from '@/components/MkDialog.vue';
-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 { 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 MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
+import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { pleaseLogin } from '@/utility/please-login.js';
+import { showMovedDialog } from '@/utility/show-moved-dialog.js';
+import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
+import { focusParent } from '@/utility/focus.js';
export const openingWindowsCount = ref(0);
@@ -62,7 +63,6 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Miss
});
if (result === 'copy') {
copyToClipboard(`Endpoint: ${endpoint}\nInfo: ${JSON.stringify(err.info)}\nDate: ${date}`);
- success();
}
return;
} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
@@ -188,7 +188,7 @@ type EmitsExtractor<T> = {
export function popup<T extends Component>(
component: T,
props: ComponentProps<T>,
- events: ComponentEmit<T> = {} as ComponentEmit<T>,
+ events: Partial<ComponentEmit<T>> = {},
): { dispose: () => void } {
markRaw(component);
@@ -318,6 +318,21 @@ export function inputText(props: {
} | {
canceled: false; result: string;
}>;
+// min lengthが指定されてたら result は null になり得ないことを保証する overload function
+export function inputText(props: {
+ type?: 'text' | 'email' | 'password' | 'url';
+ title?: string;
+ text?: string;
+ placeholder?: string | null;
+ autocomplete?: string;
+ default?: string;
+ minLength: number;
+ maxLength?: number;
+}): Promise<{
+ canceled: true; result: undefined;
+} | {
+ canceled: false; result: string;
+}>;
export function inputText(props: {
type?: 'text' | 'email' | 'password' | 'url';
title?: string;
@@ -454,7 +469,7 @@ export function authenticateDialog(): Promise<{
canceled: false; result: { password: string; token: string | null; };
}> {
return new Promise(resolve => {
- const { dispose } = popup(MkPasswordDialog, {}, {
+ const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkPasswordDialog.vue')), {}, {
done: result => {
resolve(result ? { canceled: false, result } : { canceled: true, result: undefined });
},
@@ -611,30 +626,26 @@ export async function selectDriveFolder(multiple: boolean): Promise<Misskey.enti
});
}
-export async function selectRole(params: {
- initialRoleIds?: string[],
- title?: string,
- infoMessage?: string,
- publicOnly?: boolean,
-}): Promise<
+export async function selectRole(params: ComponentProps<typeof MkRoleSelectDialog_TypeReferenceOnly>): Promise<
{ canceled: true; result: undefined; } |
{ canceled: false; result: Misskey.entities.Role[] }
> {
return new Promise((resolve) => {
- popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, {
+ const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, {
done: roles => {
resolve({ canceled: false, result: roles });
},
close: () => {
resolve({ canceled: true, result: undefined });
},
- }, 'dispose');
+ closed: () => dispose(),
+ });
});
}
-export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog>): Promise<string> {
+export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog_TypeReferenceOnly>): Promise<string> {
return new Promise(resolve => {
- const { dispose } = popup(MkEmojiPickerDialog, {
+ const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src,
...opts,
}, {
@@ -669,7 +680,11 @@ export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | n
width?: number;
onClosing?: () => void;
}): Promise<void> {
- let returnFocusTo = getHTMLElementOrNull(src) ?? getHTMLElementOrNull(document.activeElement);
+ if (!(src instanceof HTMLElement)) {
+ src = null;
+ }
+
+ let returnFocusTo = getHTMLElementOrNull(src) ?? getHTMLElementOrNull(window.document.activeElement);
return new Promise(resolve => nextTick(() => {
const { dispose } = popup(MkPopupMenu, {
items,
@@ -692,13 +707,13 @@ export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | n
export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
if (
- defaultStore.state.contextMenu === 'native' ||
- (defaultStore.state.contextMenu === 'appWithShift' && !ev.shiftKey)
+ prefer.s.contextMenu === 'native' ||
+ (prefer.s.contextMenu === 'appWithShift' && !ev.shiftKey)
) {
return Promise.resolve();
}
- let returnFocusTo = getHTMLElementOrNull(ev.currentTarget ?? ev.target) ?? getHTMLElementOrNull(document.activeElement);
+ let returnFocusTo = getHTMLElementOrNull(ev.currentTarget ?? ev.target) ?? getHTMLElementOrNull(window.document.activeElement);
ev.preventDefault();
return new Promise(resolve => nextTick(() => {
const { dispose } = popup(MkContextMenu, {
diff --git a/packages/frontend/src/scripts/page-metadata.ts b/packages/frontend/src/page.ts
index 0e3b093ecf..0107f17be4 100644
--- a/packages/frontend/src/scripts/page-metadata.ts
+++ b/packages/frontend/src/page.ts
@@ -4,7 +4,9 @@
*/
import * as Misskey from 'misskey-js';
-import { MaybeRefOrGetter, Ref, inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
+import { inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
+import { DI } from './di.js';
+import type { MaybeRefOrGetter, Ref } from 'vue';
export type PageMetadata = {
title: string;
@@ -30,11 +32,8 @@ const METADATA_KEY = Symbol('MetadataKey');
const setMetadata = (v: Ref<PageMetadata | null>): void => {
provide<Ref<PageMetadata | null>>(METADATA_KEY, v);
};
-const getMetadata = (): Ref<PageMetadata | null> | undefined => {
- return inject<Ref<PageMetadata | null>>(METADATA_KEY);
-};
-export const definePageMetadata = (maybeRefOrGetterMetadata: MaybeRefOrGetter<PageMetadata>): void => {
+export const definePage = (maybeRefOrGetterMetadata: MaybeRefOrGetter<PageMetadata>): void => {
const metadataRef = ref(toValue(maybeRefOrGetterMetadata));
const metadataGetter = () => metadataRef.value;
const receiver = getReceiver();
@@ -54,6 +53,8 @@ export const definePageMetadata = (maybeRefOrGetterMetadata: MaybeRefOrGetter<Pa
onActivated(() => {
receiver?.(metadataGetter);
});
+
+ provide(DI.pageMetadata, metadataRef);
};
export const provideMetadataReceiver = (receiver: PageMetadataReceiver): void => {
@@ -63,8 +64,3 @@ export const provideMetadataReceiver = (receiver: PageMetadataReceiver): void =>
export const provideReactiveMetadata = (metadataRef: Ref<PageMetadata | null>): void => {
setMetadata(metadataRef);
};
-
-export const injectReactiveMetadata = (): Ref<PageMetadata | null> => {
- const metadataRef = getMetadata();
- return isRef(metadataRef) ? metadataRef : ref(null);
-};
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue
index 0bafc385a6..22ed979282 100644
--- a/packages/frontend/src/pages/_error_.vue
+++ b/packages/frontend/src/pages/_error_.vue
@@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkLoading v-if="!loaded"/>
-<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
+<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
<div v-show="loaded" :class="$style.root">
- <img :src="serverErrorImageUrl" class="_ghost" :class="$style.img"/>
+ <img :src="serverErrorImageUrl" draggable="false" :class="$style.img"/>
<div class="_gaps">
<div><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></div>
<div v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</div>
@@ -27,15 +27,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
+import { version } from '@@/js/config.js';
import MkButton from '@/components/MkButton.vue';
import MkLink from '@/components/MkLink.vue';
-import { version } from '@@/js/config.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { unisonReload } from '@/utility/unison-reload.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { miLocalStorage } from '@/local-storage.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
import { serverErrorImageUrl } from '@/instance.js';
const props = withDefaults(defineProps<{
@@ -67,7 +67,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.error,
icon: 'ti ti-alert-triangle',
}));
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index d7d526f3ba..b166dfd940 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -44,7 +44,7 @@ import MkInput from '@/components/MkInput.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
const customEmojiTags = getCustomEmojiTags();
const q = ref('');
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index 2190be8bec..ea27f7d90a 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -56,7 +56,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, ref } from 'vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
import FormSplit from '@/components/form/split.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/about.overview.vue b/packages/frontend/src/pages/about.overview.vue
index 347214c8fa..ebd440ac8a 100644
--- a/packages/frontend/src/pages/about.overview.vue
+++ b/packages/frontend/src/pages/about.overview.vue
@@ -152,7 +152,7 @@ import { host, version } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import number from '@/filters/number.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import FormSplit from '@/components/form/split.vue';
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index 1f36589a49..1dee8c30c7 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
<XOverview/>
@@ -20,15 +19,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInstanceStats/>
</MkSpacer>
</MkHorizontalSwipe>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref, watch } from 'vue';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { definePage } from '@/page.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
const XOverview = defineAsyncComponent(() => import('@/pages/about.overview.vue'));
@@ -81,7 +80,7 @@ const headerTabs = computed(() => {
return items;
});
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.instanceInfo,
icon: 'ti ti-info-circle',
}));
diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue
index 77ab473ea2..423e709da4 100644
--- a/packages/frontend/src/pages/achievements.vue
+++ b/packages/frontend/src/pages/achievements.vue
@@ -4,21 +4,20 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
+<PageWithHeader>
<MkSpacer :contentMax="1200">
<MkAchievements :user="$i"/>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue';
import MkAchievements from '@/components/MkAchievements.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i } from '@/account.js';
-import { claimAchievement } from '@/scripts/achievements.js';
+import { definePage } from '@/page.js';
+import { $i } from '@/i.js';
+import { claimAchievement } from '@/utility/achievements.js';
let timer: number | null;
@@ -48,7 +47,7 @@ onDeactivated(() => {
}
});
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.achievements,
icon: 'ti ti-medal',
}));
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index 60f6be51d4..1e3e106842 100644
--- a/packages/frontend/src/pages/admin-file.vue
+++ b/packages/frontend/src/pages/admin-file.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer v-if="file" :contentMax="600" :marginMin="16" :marginMax="32">
<div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
<a class="thumbnail" :href="file.url" target="_blank">
@@ -36,8 +35,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`">
<MkUserCardMini :user="file.user"/>
</MkA>
+
<div>
- <MkSwitch v-model="isSensitive" @update:modelValue="toggleIsSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
+ <MkSwitch :modelValue="isSensitive" @update:modelValue="toggleSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
</div>
<div>
@@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkObjectView>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -82,10 +82,10 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue';
import bytes from '@/filters/bytes.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { iAmAdmin, iAmModerator } from '@/account.js';
+import { definePage } from '@/page.js';
+import { iAmAdmin, iAmModerator } from '@/i.js';
const tab = ref('overview');
const file = ref<Misskey.entities.DriveFile | null>(null);
@@ -117,9 +117,21 @@ async function del() {
});
}
-async function toggleIsSensitive(v) {
- await misskeyApi('drive/files/update', { fileId: props.fileId, isSensitive: v });
- isSensitive.value = v;
+async function toggleSensitive() {
+ if (!file.value) return;
+
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ text: isSensitive.value ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
+ });
+
+ if (canceled) return;
+ isSensitive.value = !isSensitive.value;
+
+ os.apiWithDialog('drive/files/update', {
+ fileId: file.value.id,
+ isSensitive: !file.value.isSensitive,
+ });
}
const headerActions = computed(() => [{
@@ -148,7 +160,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-code',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
icon: 'ti ti-file',
}));
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index ce1fbc46a1..d5f9f0073b 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="600" :marginMin="16" :marginMax="32">
<FormSuspense :p="init">
<div v-if="tab === 'overview'" class="_gaps_m">
@@ -39,18 +38,20 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
</MkKeyValue>
-->
- <MkKeyValue oneline>
- <template #key>{{ i18n.ts.createdAt }}</template>
- <template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
- </MkKeyValue>
- <MkKeyValue v-if="info" oneline>
- <template #key>{{ i18n.ts.lastActiveDate }}</template>
- <template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
- </MkKeyValue>
- <MkKeyValue v-if="info" oneline>
- <template #key>{{ i18n.ts.email }}</template>
- <template #value><span class="_monospace">{{ info.email }}</span></template>
- </MkKeyValue>
+ <template v-if="!isSystem">
+ <MkKeyValue oneline>
+ <template #key>{{ i18n.ts.createdAt }}</template>
+ <template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
+ </MkKeyValue>
+ <MkKeyValue v-if="info" oneline>
+ <template #key>{{ i18n.ts.lastActiveDate }}</template>
+ <template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
+ </MkKeyValue>
+ <MkKeyValue v-if="info" oneline>
+ <template #key>{{ i18n.ts.email }}</template>
+ <template #value><span class="_monospace">{{ info.email }}</span></template>
+ </MkKeyValue>
+ </template>
</div>
<MkTextarea v-model="moderationNote" manualSave @update:modelValue="onModerationNoteChanged">
@@ -77,7 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</FormSection>
- <FormSection>
+ <FormSection v-if="!isSystem">
<div class="_gaps">
<MkSwitch v-model="silenced" @update:modelValue="toggleSilence">{{ i18n.ts.silence }}</MkSwitch>
<MkSwitch v-if="!isSystem" v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
@@ -200,7 +201,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</FormSuspense>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -221,11 +222,11 @@ import FormSuspense from '@/components/form/suspense.vue';
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 { misskeyApi } from '@/utility/misskey-api.js';
import { acct } from '@/filters/user.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { iAmAdmin, $i, iAmModerator } from '@/account.js';
+import { iAmAdmin, $i, iAmModerator } from '@/i.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkInput from '@/components/MkInput.vue';
@@ -469,7 +470,7 @@ async function deleteAccount() {
}
async function assignRole() {
- const roles = await misskeyApi('admin/roles/list');
+ const roles = await misskeyApi('admin/roles/list').then(it => it.filter(r => r.target === 'manual'));
const { canceled, result: roleId } = await os.select({
title: i18n.ts._role.chooseRoleToAssign,
@@ -558,7 +559,15 @@ watch(user, () => {
const headerActions = computed(() => []);
-const headerTabs = computed(() => [{
+const headerTabs = computed(() => isSystem.value ? [{
+ key: 'overview',
+ title: i18n.ts.overview,
+ icon: 'ti ti-info-circle',
+}, {
+ key: 'raw',
+ title: 'Raw',
+ icon: 'ti ti-code',
+}] : [{
key: 'overview',
title: i18n.ts.overview,
icon: 'ti ti-info-circle',
@@ -584,7 +593,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-code',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: user.value ? acct(user.value) : i18n.ts.userInfo,
icon: 'ti ti-user-exclamation',
}));
diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
index 4762ef3f97..6c47e6397f 100644
--- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue
+++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
@@ -71,7 +71,7 @@ import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { deepClone } from '@/scripts/clone.js';
+import { deepClone } from '@/utility/clone.js';
import { rolesCache } from '@/cache.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index b0651150a6..03f70bc22b 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -33,13 +33,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, onMounted, onUnmounted, ref, shallowRef, watch, nextTick } from 'vue';
+import { computed, onMounted, onUnmounted, ref, useTemplateRef, watch, nextTick, inject } from 'vue';
import tinycolor from 'tinycolor2';
-import { popupMenu } from '@/os.js';
import { scrollToTop } from '@@/js/scroll.js';
+import { popupMenu } from '@/os.js';
import MkButton from '@/components/MkButton.vue';
import { globalEvents } from '@/events.js';
-import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
+import { DI } from '@/di.js';
type Tab = {
key?: string | null;
@@ -66,11 +66,11 @@ const emit = defineEmits<{
(ev: 'update:tab', key: string);
}>();
-const pageMetadata = injectReactiveMetadata();
+const pageMetadata = inject(DI.pageMetadata);
-const el = shallowRef<HTMLElement>(null);
+const el = useTemplateRef('el');
+const tabHighlightEl = useTemplateRef('tabHighlightEl');
const tabRefs = {};
-const tabHighlightEl = shallowRef<HTMLElement | null>(null);
const bg = ref<string | null>(null);
const height = ref(0);
const hasTabs = computed(() => {
@@ -119,15 +119,15 @@ function onTabClick(tab: Tab, ev: MouseEvent): void {
}
const calcBg = () => {
- const rawBg = pageMetadata.value?.bg ?? 'var(--MI_THEME-bg)';
- const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
+ const rawBg = pageMetadata.value.bg ?? 'var(--MI_THEME-bg)';
+ const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(window.document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
tinyBg.setAlpha(0.85);
bg.value = tinyBg.toRgbString();
};
onMounted(() => {
calcBg();
- globalEvents.on('themeChanged', calcBg);
+ globalEvents.on('themeChanging', calcBg);
watch(() => [props.tab, props.tabs], () => {
nextTick(() => {
@@ -147,7 +147,7 @@ onMounted(() => {
});
onUnmounted(() => {
- globalEvents.off('themeChanged', calcBg);
+ globalEvents.off('themeChanging', calcBg);
});
</script>
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 eef24afd32..a56a24ff7d 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
@@ -71,15 +71,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
+import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
import { entities } from 'misskey-js';
+import type { MkSystemWebhookResult } from '@/components/MkSystemWebhookEditor.impl.js';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
import MkInput from '@/components/MkInput.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkSelect from '@/components/MkSelect.vue';
-import { MkSystemWebhookResult, showSystemWebhookEditorDialog } from '@/components/MkSystemWebhookEditor.impl.js';
+import { showSystemWebhookEditorDialog } from '@/components/MkSystemWebhookEditor.impl.js';
import MkSwitch from '@/components/MkSwitch.vue';
import MkDivider from '@/components/MkDivider.vue';
import * as os from '@/os.js';
@@ -99,7 +100,7 @@ const props = defineProps<{
const { mode, id } = toRefs(props);
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
const loading = ref<number>(0);
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
index f5249261be..ee87fae606 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
@@ -49,7 +49,7 @@ import { entities } from 'misskey-js';
import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
import XRecipient from './notification-recipient.item.vue';
import XHeader from '@/pages/admin/_header_.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index a164ecb1fe..67d54a85ad 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -12,7 +12,7 @@ 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()">
+ <MkInfo v-if="!store.r.abusesTutorial.value" closable @close="closeTutorial()">
{{ i18n.ts._abuseUserReport.resolveTutorial }}
</MkInfo>
@@ -59,18 +59,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, shallowRef, ref } from 'vue';
+import { computed, useTemplateRef, ref } from 'vue';
import XHeader from './_header_.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
import XAbuseReport from '@/components/MkAbuseReport.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
-const reports = shallowRef<InstanceType<typeof MkPagination>>();
+const reports = useTemplateRef('reports');
const state = ref('unresolved');
const reporterOrigin = ref('combined');
@@ -93,14 +93,14 @@ function resolved(reportId) {
}
function closeTutorial() {
- defaultStore.set('abusesTutorial', false);
+ store.set('abusesTutorial', false);
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.abuseReports,
icon: 'ti ti-exclamation-circle',
}));
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index 0d67359e47..ebc3d23296 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -96,9 +96,9 @@ import MkFolder from '@/components/MkFolder.vue';
import MkSelect from '@/components/MkSelect.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const ads = ref<Misskey.entities.Ad[]>([]);
@@ -255,7 +255,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.ads,
icon: 'ti ti-ad',
}));
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index e420586017..f6b331455f 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -94,9 +94,9 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkFolder from '@/components/MkFolder.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -199,7 +199,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index e37df40f2f..c8853c5ae4 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -196,14 +196,14 @@ import MkRadios from '@/components/MkRadios.vue';
import MkInput from '@/components/MkInput.vue';
import FormSlot from '@/components/form/slot.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { useForm } from '@/scripts/use-form.js';
+import { useForm } from '@/use/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
-import { ApiWithDialogCustomErrors } from '@/os.js';
+import type { ApiWithDialogCustomErrors } from '@/os.js';
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index cc05466832..84d8ea85da 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -128,10 +128,10 @@ import MkTextarea from '@/components/MkTextarea.vue';
import FromSlot from '@/components/form/slot.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { instance, fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import MkColorInput from '@/components/MkColorInput.vue';
import { host } from '@@/js/config.js';
@@ -210,7 +210,7 @@ function chooseNewLike(ev: MouseEvent) {
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.branding,
icon: 'ti ti-paint',
}));
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
index c4ea3b93e3..260177c894 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
@@ -71,25 +71,25 @@ export type EmojiSearchQuery = {
<script setup lang="ts">
import { computed, defineAsyncComponent, onMounted, ref, nextTick, useCssModule } from 'vue';
import * as Misskey from 'misskey-js';
+import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
+import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
+import type { GridSetting } from '@/components/grid/grid.js';
import * as os from '@/os.js';
import {
emptyStrToEmptyArray,
emptyStrToNull,
emptyStrToUndefined,
- RequestLogItem,
roleIdsParser,
} from '@/pages/admin/custom-emojis-manager.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import { validators } from '@/components/grid/cell-validators.js';
-import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkPagingButtons from '@/components/MkPagingButtons.vue';
-import { GridSetting } from '@/components/grid/grid.js';
-import { selectFile } from '@/scripts/select-file.js';
+import { selectFile } from '@/utility/select-file.js';
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
-import { useLoading } from "@/components/hook/useLoading.js";
+import { useLoading } from '@/components/hook/useLoading.js';
type GridItem = {
checked: boolean;
@@ -108,7 +108,7 @@ type GridItem = {
publicUrl?: string | null;
originalUrl?: string | null;
type: string | null;
-}
+};
function setupGrid(): GridSetting {
const $style = useCssModule();
@@ -464,8 +464,8 @@ async function refreshCustomEmojis() {
aliases: emptyStrToUndefined(searchQuery.value.aliases),
category: emptyStrToUndefined(searchQuery.value.category),
license: emptyStrToUndefined(searchQuery.value.license),
- isSensitive: searchQuery.value.sensitive ? Boolean(searchQuery.value.sensitive).valueOf() : undefined,
- localOnly: searchQuery.value.localOnly ? Boolean(searchQuery.value.localOnly).valueOf() : undefined,
+ isSensitive: searchQuery.value.sensitive != null ? Boolean(searchQuery.value.sensitive).valueOf() : undefined,
+ localOnly: searchQuery.value.localOnly != null ? Boolean(searchQuery.value.localOnly).valueOf() : undefined,
updatedAtFrom: emptyStrToUndefined(searchQuery.value.updatedAtFrom),
updatedAtTo: emptyStrToUndefined(searchQuery.value.updatedAtTo),
roleIds: searchQuery.value.roles.map(it => it.id),
@@ -592,7 +592,7 @@ const headerActions = computed(() => [{
dispose();
},
});
- }
+ },
}]);
</script>
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
index cc8b625cd5..666e3c95ac 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
@@ -78,30 +78,32 @@ SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as Misskey from 'misskey-js';
import { onMounted, ref, useCssModule } from 'vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
+import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
+import type { DroppedFile } from '@/utility/file-drop.js';
+import type { GridSetting } from '@/components/grid/grid.js';
+import type { GridRow } from '@/components/grid/row.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import {
emptyStrToEmptyArray,
emptyStrToNull,
- RequestLogItem,
roleIdsParser,
} from '@/pages/admin/custom-emojis-manager.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue';
import { i18n } from '@/i18n.js';
import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import { defaultStore } from '@/store.js';
import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { validators } from '@/components/grid/cell-validators.js';
-import { chooseFileFromDrive, chooseFileFromPc } from '@/scripts/select-file.js';
-import { uploadFile } from '@/scripts/upload.js';
-import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
-import { DroppedFile, extractDroppedItems, flattenDroppedFiles } from '@/scripts/file-drop.js';
+import { chooseFileFromDrive, chooseFileFromPc } from '@/utility/select-file.js';
+import { uploadFile } from '@/utility/upload.js';
+import { extractDroppedItems, flattenDroppedFiles } from '@/utility/file-drop.js';
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
-import { GridSetting } from '@/components/grid/grid.js';
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
-import { GridRow } from '@/components/grid/row.js';
+
+import { prefer } from '@/preferences.js';
const MAXIMUM_EMOJI_REGISTER_COUNT = 100;
@@ -122,7 +124,7 @@ type GridItem = {
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: { id: string, name: string }[];
type: string | null;
-}
+};
function setupGrid(): GridSetting {
const $style = useCssModule();
@@ -242,8 +244,8 @@ function setupGrid(): GridSetting {
const uploadFolders = ref<FolderItem[]>([]);
const gridItems = ref<GridItem[]>([]);
-const selectedFolderId = ref(defaultStore.state.uploadFolder);
-const keepOriginalUploading = ref(defaultStore.state.keepOriginalUploading);
+const selectedFolderId = ref(prefer.s.uploadFolder);
+const keepOriginalUploading = ref(prefer.s.keepOriginalUploading);
const directoryToCategory = ref<boolean>(false);
const registerButtonDisabled = ref<boolean>(false);
const requestLogs = ref<RequestLogItem[]>([]);
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
index eecf8d7390..c868a700f1 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
@@ -143,35 +143,32 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, onMounted, ref, useCssModule } from 'vue';
import * as Misskey from 'misskey-js';
import MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkGrid from '@/components/grid/MkGrid.vue';
-import {
- emptyStrToUndefined,
- GridSortOrderKey,
- gridSortOrderKeys,
- RequestLogItem,
-} from '@/pages/admin/custom-emojis-manager.impl.js';
-import { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
+import { emptyStrToUndefined, gridSortOrderKeys } from '@/pages/admin/custom-emojis-manager.impl.js';
import MkFolder from '@/components/MkFolder.vue';
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
import * as os from '@/os.js';
-import { GridSetting } from '@/components/grid/grid.js';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { deviceKind } from '@/utility/device-kind.js';
import MkPagingButtons from '@/components/MkPagingButtons.vue';
import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
-import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
import { useLoading } from '@/components/hook/useLoading.js';
+import type { GridSortOrderKey, RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
+import type { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
+import type { GridSetting } from '@/components/grid/grid.js';
+import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
+
type GridItem = {
checked: boolean;
id: string;
url: string;
name: string;
host: string;
-}
+};
function setupGrid(): GridSetting {
const $style = useCssModule();
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts b/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts
index f62304277a..3384a71f0f 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { delay, http, HttpResponse } from 'msw';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { entities } from 'misskey-js';
import { commonHandlers } from '../../../.storybook/mocks.js';
import { emoji } from '../../../.storybook/fakes.js';
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager2.vue b/packages/frontend/src/pages/admin/custom-emojis-manager2.vue
index fb930064ff..7667206fa8 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager2.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager2.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, ref } from 'vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import XGridLocalComponent from '@/pages/admin/custom-emojis-manager.local.vue';
import XGridRemoteComponent from '@/pages/admin/custom-emojis-manager.remote.vue';
import MkPageHeader from '@/components/global/MkPageHeader.vue';
@@ -36,7 +36,7 @@ const headerTabs = computed(() => [{
title: i18n.ts.remote,
}]);
-definePageMetadata(computed(() => ({
+definePage(computed(() => ({
title: i18n.ts.customEmojis,
icon: 'ti ti-icons',
needWideArea: true,
diff --git a/packages/frontend/src/pages/admin/database.vue b/packages/frontend/src/pages/admin/database.vue
index e092efd92c..6691142a64 100644
--- a/packages/frontend/src/pages/admin/database.vue
+++ b/packages/frontend/src/pages/admin/database.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800" :marginMin="16" :marginMax="32">
<FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
<MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;">
@@ -14,18 +13,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkKeyValue>
</FormSuspense>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import FormSuspense from '@/components/form/suspense.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import bytes from '@/filters/bytes.js';
import number from '@/filters/number.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const databasePromiseFactory = () => misskeyApi('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
@@ -33,7 +32,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.database,
icon: 'ti ti-database',
}));
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index 2f1d12ebb5..d018977bda 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -73,10 +73,10 @@ import FormSuspense from '@/components/form/suspense.vue';
import FormSplit from '@/components/form/split.vue';
import FormSection from '@/components/form/section.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance, instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
const enableEmail = ref<boolean>(false);
@@ -130,7 +130,7 @@ function save() {
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.emailServer,
icon: 'ti ti-mail',
}));
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index 8cff014104..ab168c138d 100644
--- a/packages/frontend/src/pages/admin/external-services.vue
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -10,6 +10,18 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormSuspense :p="init">
<div class="_gaps_m">
<MkFolder>
+ <template #label>Google Analytics<span class="_beta">{{ i18n.ts.beta }}</span></template>
+
+ <div class="_gaps_m">
+ <MkInput v-model="googleAnalyticsMeasurementId">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>Measurement ID</template>
+ </MkInput>
+ <MkButton primary @click="save_googleAnalytics">Save</MkButton>
+ </div>
+ </MkFolder>
+
+ <MkFolder>
<template #label>DeepL Translation</template>
<div class="_gaps_m">
@@ -65,10 +77,10 @@ import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkFolder from '@/components/MkFolder.vue';
const deeplAuthKey = ref<string | null>('');
@@ -78,14 +90,17 @@ const deeplFreeInstance = ref<string | null>('');
const libreTranslateURL = ref<string | null>('');
const libreTranslateKey = ref<string | null>('');
+const googleAnalyticsMeasurementId = ref<string>('');
+
async function init() {
const meta = await misskeyApi('admin/meta');
- deeplAuthKey.value = meta.deeplAuthKey;
+ deeplAuthKey.value = meta.deeplAuthKey ?? '';
deeplIsPro.value = meta.deeplIsPro;
deeplFreeMode.value = meta.deeplFreeMode;
deeplFreeInstance.value = meta.deeplFreeInstance;
libreTranslateURL.value = meta.libreTranslateURL;
libreTranslateKey.value = meta.libreTranslateKey;
+ googleAnalyticsMeasurementId.value = meta.googleAnalyticsMeasurementId ?? '';
}
function save_deepl() {
@@ -108,11 +123,19 @@ function save_libre() {
});
}
+function save_googleAnalytics() {
+ os.apiWithDialog('admin/update-meta', {
+ googleAnalyticsMeasurementId: googleAnalyticsMeasurementId.value,
+ }).then(() => {
+ fetchInstance(true);
+ });
+}
+
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.externalServices,
icon: 'ph-arrow-square-out ph-bold ph-lg',
}));
diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue
index 188678c183..6c8979c5b5 100644
--- a/packages/frontend/src/pages/admin/federation.vue
+++ b/packages/frontend/src/pages/admin/federation.vue
@@ -69,7 +69,7 @@ import MkPagination from '@/components/MkPagination.vue';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
import FormSplit from '@/components/form/split.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const host = ref('');
const state = ref('federating');
@@ -116,7 +116,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.federation,
icon: 'ti ti-whirl',
}));
diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue
index 4cc859227f..e15724c2a7 100644
--- a/packages/frontend/src/pages/admin/files.vue
+++ b/packages/frontend/src/pages/admin/files.vue
@@ -42,9 +42,9 @@ import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
import * as os from '@/os.js';
-import { lookupFile } from '@/scripts/admin-lookup.js';
+import { lookupFile } from '@/utility/admin-lookup.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const origin = ref('local');
const type = ref<string | null>(null);
@@ -85,7 +85,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.files,
icon: 'ti ti-cloud',
}));
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 3a95e0a5a6..3142c5a45d 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -27,24 +27,25 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSpacer>
</div>
<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
- <RouterView nested/>
+ <NestedRouterView/>
</div>
</div>
</template>
<script lang="ts" setup>
import { onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
+import type { SuperMenuDef } from '@/components/MkSuperMenu.vue';
+import type { PageMetadata } from '@/page.js';
import { i18n } from '@/i18n.js';
import MkSuperMenu from '@/components/MkSuperMenu.vue';
-import type { SuperMenuDef } from '@/components/MkSuperMenu.vue';
import MkInfo from '@/components/MkInfo.vue';
import { instance } from '@/instance.js';
-import { lookup } from '@/scripts/lookup.js';
+import { lookup } from '@/utility/lookup.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { lookupUser, lookupUserByEmail, lookupFile } from '@/scripts/admin-lookup.js';
-import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import { useRouter } from '@/router/supplier.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { lookupUser, lookupUserByEmail, lookupFile } from '@/utility/admin-lookup.js';
+import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
+import { useRouter } from '@/router.js';
const isEmpty = (x: string | null) => x == null || x === '';
@@ -339,7 +340,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => INFO.value);
+definePage(() => INFO.value);
defineExpose({
header: {
diff --git a/packages/frontend/src/pages/admin/invites.vue b/packages/frontend/src/pages/admin/invites.vue
index c4f2c292e0..7dae1db839 100644
--- a/packages/frontend/src/pages/admin/invites.vue
+++ b/packages/frontend/src/pages/admin/invites.vue
@@ -55,21 +55,22 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, ref, shallowRef } from 'vue';
+import { computed, ref, useTemplateRef } from 'vue';
import XHeader from './_header_.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
import MkInviteCode from '@/components/MkInviteCode.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
const type = ref('all');
const sort = ref('+createdAt');
@@ -113,7 +114,7 @@ function deleted(id: string) {
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.invite,
icon: 'ti ti-user-plus',
}));
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index 27cbfda078..d6be7a5cf4 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -162,10 +162,10 @@ import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import FormLink from '@/components/form/link.vue';
import MkFolder from '@/components/MkFolder.vue';
@@ -319,7 +319,7 @@ function save_mediaSilencedHosts() {
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.moderation,
icon: 'ti ti-shield',
}));
diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue
index 9ce6499e2d..67db9ed6d3 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -274,6 +274,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-else-if="log.type === 'removeRelay'">
<div>{{ i18n.ts.inboxUrl }}: {{ log.info.inbox }}</div>
</template>
+ <template v-else-if="log.type === 'updateProxyAccountDescription'">
+ <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>
diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue
index 35f939f1be..32a20e6dd4 100644
--- a/packages/frontend/src/pages/admin/modlog.vue
+++ b/packages/frontend/src/pages/admin/modlog.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, shallowRef, ref } from 'vue';
+import { computed, useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import XHeader from './_header_.vue';
import XModLog from './modlog.ModLog.vue';
@@ -38,10 +38,10 @@ import MkSelect from '@/components/MkSelect.vue';
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 { definePage } from '@/page.js';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
-const logs = shallowRef<InstanceType<typeof MkPagination>>();
+const logs = useTemplateRef('logs');
const type = ref<string | null>(null);
const moderatorId = ref('');
@@ -59,7 +59,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.moderationLogs,
icon: 'ti ti-list-search',
}));
diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue
index d5a664934c..da96eb4881 100644
--- a/packages/frontend/src/pages/admin/object-storage.vue
+++ b/packages/frontend/src/pages/admin/object-storage.vue
@@ -90,10 +90,10 @@ import MkInput from '@/components/MkInput.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
const useObjectStorage = ref<boolean>(false);
@@ -149,7 +149,7 @@ function save() {
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.objectStorage,
icon: 'ti ti-cloud',
}));
diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue
index 79dd6fd5fd..5b7f669f6b 100644
--- a/packages/frontend/src/pages/admin/overview.active-users.vue
+++ b/packages/frontend/src/pages/admin/overview.active-users.vue
@@ -13,18 +13,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
import { Chart } from 'chart.js';
import gradient from 'chartjs-plugin-gradient';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { initChart } from '@/scripts/init-chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { initChart } from '@/utility/init-chart.js';
initChart();
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = useTemplateRef('chartEl');
const now = new Date();
let chartInstance: Chart = null;
const chartLimit = 7;
@@ -54,7 +54,7 @@ async function renderChart() {
const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const colorRead = '#3498db';
const colorWrite = '#2ecc71';
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts b/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts
index 584cd3e4d9..88747ef4ed 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http, HttpResponse } from 'msw';
import { action } from '@storybook/addon-actions';
import { commonHandlers } from '../../../.storybook/mocks.js';
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue
index 570fcddc07..4c06d94d6d 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.vue
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue
@@ -20,22 +20,22 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
import { Chart } from 'chart.js';
import gradient from 'chartjs-plugin-gradient';
import isChromatic from 'chromatic';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { defaultStore } from '@/store.js';
-import { alpha } from '@/scripts/color.js';
-import { initChart } from '@/scripts/init-chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { store } from '@/store.js';
+import { alpha } from '@/utility/color.js';
+import { initChart } from '@/utility/init-chart.js';
initChart();
const chartLimit = 50;
-const chartEl = shallowRef<HTMLCanvasElement>();
-const chartEl2 = shallowRef<HTMLCanvasElement>();
+const chartEl = useTemplateRef('chartEl');
+const chartEl2 = useTemplateRef('chartEl2');
const fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip();
@@ -68,7 +68,7 @@ onMounted(async () => {
const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const succColor = '#87e000';
const failColor = '#ff4400';
diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue
index 9062c73ff6..34007d309d 100644
--- a/packages/frontend/src/pages/admin/overview.federation.vue
+++ b/packages/frontend/src/pages/admin/overview.federation.vue
@@ -47,13 +47,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
-import XPie, { type InstanceForPie } from './overview.pie.vue';
+import XPie from './overview.pie.vue';
+import type { InstanceForPie } from './overview.pie.vue';
import * as os from '@/os.js';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
import number from '@/filters/number.js';
import MkNumberDiff from '@/components/MkNumberDiff.vue';
import { i18n } from '@/i18n.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
const topSubInstancesForPie = ref<InstanceForPie[] | null>(null);
const topPubInstancesForPie = ref<InstanceForPie[] | null>(null);
diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue
index 292e2e1dbc..c8291459fe 100644
--- a/packages/frontend/src/pages/admin/overview.instances.vue
+++ b/packages/frontend/src/pages/admin/overview.instances.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
- <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
<MkLoading v-if="fetching"/>
<div v-else :class="$style.instances">
<MkA v-for="(instance, i) in instances" :key="instance.id" v-tooltip.mfm.noDelay="`${instance.name}\n${instance.host}\n${instance.softwareName} ${instance.softwareVersion}`" :to="`/instance-info/${instance.host}`" :class="$style.instance">
@@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const instances = ref<Misskey.entities.FederationInstance[]>([]);
const fetching = ref(true);
diff --git a/packages/frontend/src/pages/admin/overview.moderators.vue b/packages/frontend/src/pages/admin/overview.moderators.vue
index f0691534c8..fb2c5ea13c 100644
--- a/packages/frontend/src/pages/admin/overview.moderators.vue
+++ b/packages/frontend/src/pages/admin/overview.moderators.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
- <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
<MkLoading v-if="fetching"/>
<div v-else :class="$style.root" class="_panel">
<MkA v-for="user in moderators" :key="user.id" class="user" :to="`/admin/user/${user.id}`">
@@ -18,9 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
import * as Misskey from 'misskey-js';
-import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { prefer } from '@/preferences.js';
const moderators = ref<Misskey.entities.UserDetailed[] | null>(null);
const fetching = ref(true);
diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue
index a21ec6c464..86c5eff4da 100644
--- a/packages/frontend/src/pages/admin/overview.pie.vue
+++ b/packages/frontend/src/pages/admin/overview.pie.vue
@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
import { Chart } from 'chart.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { initChart } from '@/scripts/init-chart.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { initChart } from '@/utility/init-chart.js';
export type InstanceForPie = {
name: string,
@@ -26,7 +26,7 @@ const props = defineProps<{
data: InstanceForPie[];
}>();
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = useTemplateRef('chartEl');
const { handler: externalTooltipHandler } = useChartTooltip({
position: 'middle',
@@ -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('--MI_THEME-panel'),
+ borderColor: getComputedStyle(window.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.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue
index 2efc17c888..6fc941a848 100644
--- a/packages/frontend/src/pages/admin/overview.queue.chart.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue
@@ -8,13 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
import { Chart } from 'chart.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { alpha } from '@/scripts/color.js';
-import { initChart } from '@/scripts/init-chart.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { alpha } from '@/utility/color.js';
+import { initChart } from '@/utility/init-chart.js';
initChart();
@@ -22,7 +22,7 @@ const props = defineProps<{
type: string;
}>();
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = useTemplateRef('chartEl');
const { handler: externalTooltipHandler } = useChartTooltip();
@@ -67,7 +67,7 @@ const color =
'?' as never;
onMounted(() => {
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
chartInstance = new Chart(chartEl.value, {
type: 'line',
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index de6b254412..cf07cddced 100644
--- a/packages/frontend/src/pages/admin/overview.queue.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.vue
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
+import { markRaw, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import XChart from './overview.queue.chart.vue';
import type { ApQueueDomain } from '@/pages/admin/queue.vue';
@@ -48,10 +48,10 @@ const activeSincePrevTick = ref(0);
const active = ref(0);
const delayed = ref(0);
const waiting = ref(0);
-const chartProcess = shallowRef<InstanceType<typeof XChart>>();
-const chartActive = shallowRef<InstanceType<typeof XChart>>();
-const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
-const chartWaiting = shallowRef<InstanceType<typeof XChart>>();
+const chartProcess = useTemplateRef('chartProcess');
+const chartActive = useTemplateRef('chartActive');
+const chartDelayed = useTemplateRef('chartDelayed');
+const chartWaiting = useTemplateRef('chartWaiting');
const props = defineProps<{
domain: ApQueueDomain;
diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue
index a967b305f7..9284d886d9 100644
--- a/packages/frontend/src/pages/admin/overview.stats.vue
+++ b/packages/frontend/src/pages/admin/overview.stats.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
- <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
<MkLoading v-if="fetching"/>
<div v-else :class="$style.root">
<div class="item _panel users">
@@ -63,12 +63,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
+import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
import MkNumberDiff from '@/components/MkNumberDiff.vue';
import MkNumber from '@/components/MkNumber.vue';
import { i18n } from '@/i18n.js';
import { customEmojis } from '@/custom-emojis.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const stats = ref<Misskey.entities.StatsResponse | null>(null);
const usersComparedToThePrevDay = ref<number>();
diff --git a/packages/frontend/src/pages/admin/overview.users.vue b/packages/frontend/src/pages/admin/overview.users.vue
index 8c9d7a8197..6a39f4561f 100644
--- a/packages/frontend/src/pages/admin/overview.users.vue
+++ b/packages/frontend/src/pages/admin/overview.users.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
- <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in">
<MkLoading v-if="fetching"/>
<div v-else class="users">
<MkA v-for="(user, i) in newUsers" :key="user.id" :to="`/admin/user/${user.id}`" class="user">
@@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
const fetching = ref(true);
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index 1de4dc0dc8..616815a6a6 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { markRaw, onMounted, onBeforeUnmount, nextTick, shallowRef, ref, computed } from 'vue';
+import { markRaw, onMounted, onBeforeUnmount, nextTick, shallowRef, ref, computed, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import XFederation from './overview.federation.vue';
import XInstances from './overview.instances.vue';
@@ -79,13 +79,13 @@ import XModerators from './overview.moderators.vue';
import XHeatmap from './overview.heatmap.vue';
import type { InstanceForPie } from './overview.pie.vue';
import * as os from '@/os.js';
-import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
+import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
-const rootEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
const serverInfo = ref<Misskey.entities.ServerInfoResponse | null>(null);
const topSubInstancesForPie = ref<InstanceForPie[] | null>(null);
const topPubInstancesForPie = ref<InstanceForPie[] | null>(null);
@@ -184,7 +184,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.dashboard,
icon: 'ti ti-dashboard',
}));
diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue
index 12338f0bf9..6bb0918fea 100644
--- a/packages/frontend/src/pages/admin/performance.vue
+++ b/packages/frontend/src/pages/admin/performance.vue
@@ -111,15 +111,15 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, computed } from 'vue';
import XHeader from './_header_.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkSwitch from '@/components/MkSwitch.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkInput from '@/components/MkInput.vue';
import MkLink from '@/components/MkLink.vue';
-import { useForm } from '@/scripts/use-form.js';
+import { useForm } from '@/use/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue';
const meta = await misskeyApi('admin/meta');
@@ -202,7 +202,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.other,
icon: 'ti ti-adjustments',
}));
diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue
index cc18898172..5dd2887024 100644
--- a/packages/frontend/src/pages/admin/queue.chart.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue
@@ -8,13 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
import { Chart } from 'chart.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { alpha } from '@/scripts/color.js';
-import { initChart } from '@/scripts/init-chart.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { alpha } from '@/utility/color.js';
+import { initChart } from '@/utility/init-chart.js';
initChart();
@@ -22,7 +22,7 @@ const props = defineProps<{
type: string;
}>();
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = useTemplateRef('chartEl');
const { handler: externalTooltipHandler } = useChartTooltip();
@@ -67,7 +67,7 @@ const color =
'?' as never;
onMounted(() => {
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
chartInstance = new Chart(chartEl.value, {
type: 'line',
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index 7c171ba0e1..1ba02d6e0e 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -48,12 +48,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
+import { markRaw, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import XChart from './queue.chart.chart.vue';
import type { ApQueueDomain } from '@/pages/admin/queue.vue';
import number from '@/filters/number.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import MkFolder from '@/components/MkFolder.vue';
@@ -65,10 +65,10 @@ const active = ref(0);
const delayed = ref(0);
const waiting = ref(0);
const jobs = ref<Misskey.Endpoints[`admin/queue/${ApQueueDomain}-delayed`]['res']>([]);
-const chartProcess = shallowRef<InstanceType<typeof XChart>>();
-const chartActive = shallowRef<InstanceType<typeof XChart>>();
-const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
-const chartWaiting = shallowRef<InstanceType<typeof XChart>>();
+const chartProcess = useTemplateRef('chartProcess');
+const chartActive = useTemplateRef('chartActive');
+const chartDelayed = useTemplateRef('chartDelayed');
+const chartWaiting = useTemplateRef('chartWaiting');
const props = defineProps<{
domain: ApQueueDomain;
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index 512039242e..b5aee1e51e 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -16,13 +16,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, computed, type Ref } from 'vue';
+import { ref, computed } from 'vue';
+import * as config from '@@/js/config.js';
import XQueue from './queue.chart.vue';
import XHeader from './_header_.vue';
+import type { Ref } from 'vue';
import * as os from '@/os.js';
-import * as config from '@@/js/config.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
export type ApQueueDomain = 'deliver' | 'inbox';
@@ -53,14 +54,7 @@ function promoteAllQueues() {
});
}
-const headerActions = computed(() => [{
- asFullButton: true,
- icon: 'ti ti-external-link',
- text: i18n.ts.dashboard,
- handler: () => {
- window.open(config.url + '/queue', '_blank', 'noopener');
- },
-}]);
+const headerActions = computed(() => []);
const headerTabs = computed(() => [{
key: 'deliver',
@@ -70,7 +64,7 @@ const headerTabs = computed(() => [{
title: 'Inbox',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.jobQueue,
icon: 'ti ti-clock-play',
}));
diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index 17e99e6593..a6280e7075 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -29,9 +29,9 @@ import * as Misskey from 'misskey-js';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const relays = ref<Misskey.entities.AdminRelaysListResponse>([]);
@@ -84,7 +84,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.relays,
icon: 'ti ti-planet',
}));
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index 2b4006c3f7..7741064685 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -28,12 +28,12 @@ import { v4 as uuid } from 'uuid';
import XHeader from './_header_.vue';
import XEditor from './roles.editor.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import { rolesCache } from '@/cache.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -87,7 +87,7 @@ async function save() {
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: role.value ? `${i18n.ts._role.edit}: ${role.value.name}` : i18n.ts._role.new,
icon: 'ti ti-badge',
}));
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 6bab594d36..f6c24c13da 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -219,6 +219,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkFolder>
+ <MkFolder v-if="matchQuery([i18n.ts._role._options.canChat, 'canChat'])">
+ <template #label>{{ i18n.ts._role._options.canChat }}</template>
+ <template #suffix>
+ <span v-if="role.policies.canChat.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+ <span v-else>{{ role.policies.canChat.value ? i18n.ts.yes : i18n.ts.no }}</span>
+ <span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canChat)"></i></span>
+ </template>
+ <div class="_gaps">
+ <MkSwitch v-model="role.policies.canChat.useDefault" :readonly="readonly">
+ <template #label>{{ i18n.ts._role.useBaseValue }}</template>
+ </MkSwitch>
+ <MkSwitch v-model="role.policies.canChat.value" :disabled="role.policies.canChat.useDefault" :readonly="readonly">
+ <template #label>{{ i18n.ts.enable }}</template>
+ </MkSwitch>
+ <MkRange v-model="role.policies.canChat.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+ <template #label>{{ i18n.ts._role.priority }}</template>
+ </MkRange>
+ </div>
+ </MkFolder>
+
<MkFolder v-if="matchQuery([i18n.ts._role._options.mentionMax, 'mentionLimit'])">
<template #label>{{ i18n.ts._role._options.mentionMax }}</template>
<template #suffix>
@@ -769,7 +789,7 @@ import MkRange from '@/components/MkRange.vue';
import FormSlot from '@/components/form/slot.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { deepClone } from '@/scripts/clone.js';
+import { deepClone } from '@/utility/clone.js';
const emit = defineEmits<{
(ev: 'update:modelValue', v: any): void;
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index d1c7be39d6..9e21aba8a0 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkPagination :pagination="usersPagination" :displayLimit="50">
<template #empty>
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.noUsers }}</div>
</div>
</template>
@@ -67,15 +67,15 @@ import XHeader from './_header_.vue';
import XEditor from './roles.editor.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkPagination from '@/components/MkPagination.vue';
import { infoImageUrl } from '@/instance.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -170,7 +170,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: `${i18n.ts.role}: ${role.name}`,
icon: 'ti ti-badge',
}));
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index f67b1cd582..6038a1237f 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -77,6 +77,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkInput>
</MkFolder>
+ <MkFolder v-if="matchQuery([i18n.ts._role._options.canChat, 'canChat'])">
+ <template #label>{{ i18n.ts._role._options.canChat }}</template>
+ <template #suffix>{{ policies.canChat ? i18n.ts.yes : i18n.ts.no }}</template>
+ <MkSwitch v-model="policies.canChat">
+ <template #label>{{ i18n.ts.enable }}</template>
+ </MkSwitch>
+ </MkFolder>
+
<MkFolder v-if="matchQuery([i18n.ts._role._options.mentionMax, 'mentionLimit'])">
<template #label>{{ i18n.ts._role._options.mentionMax }}</template>
<template #suffix>{{ policies.mentionLimit }}</template>
@@ -317,12 +325,12 @@ import MkRange from '@/components/MkRange.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkRolePreview from '@/components/MkRolePreview.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { instance, fetchInstance } from '@/instance.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
const baseRoleQ = ref('');
@@ -365,7 +373,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.roles,
icon: 'ti ti-badges',
}));
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index 38986dc977..84827b799f 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -103,11 +103,11 @@ import MkRange from '@/components/MkRange.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { useForm } from '@/scripts/use-form.js';
+import { definePage } from '@/page.js';
+import { useForm } from '@/use/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue';
const meta = await misskeyApi('admin/meta');
@@ -162,7 +162,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.security,
icon: 'ti ti-lock',
}));
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index 6c1227b3cf..3b0f1066f5 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -46,7 +46,7 @@ import XHeader from './_header_.vue';
import * as os from '@/os.js';
import { fetchInstance, instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@@ -67,7 +67,7 @@ const remove = (index: number): void => {
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.serverRules,
icon: 'ti ti-checkbox',
}));
@@ -122,7 +122,7 @@ definePageMetadata(() => ({
border-radius: var(--MI-radius-sm);
&:hover {
- background: var(--MI_THEME-X5);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
}
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 0e9c0a7d38..1f5fcc2d33 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -270,15 +270,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFolder>
<template #icon><i class="ti ti-ghost"></i></template>
<template #label>{{ i18n.ts.proxyAccount }}</template>
+ <template v-if="proxyAccountForm.modified.value" #footer>
+ <MkFormFooter :form="proxyAccountForm"/>
+ </template>
<div class="_gaps">
<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
- <MkKeyValue>
- <template #key>{{ i18n.ts.proxyAccount }}</template>
- <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template>
- </MkKeyValue>
- <MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton>
+ <MkTextarea v-model="proxyAccountForm.state.description" :max="500" tall mfmAutocomplete :mfmPreview="true">
+ <template #label>{{ i18n.ts._profile.description }}</template>
+ <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
+ </MkTextarea>
</div>
</MkFolder>
</div>
@@ -288,7 +290,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, reactive } from 'vue';
import XHeader from './_header_.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue';
@@ -296,20 +298,20 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkInfo from '@/components/MkInfo.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { fetchInstance, instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
-import { useForm } from '@/scripts/use-form.js';
+import { useForm } from '@/use/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue';
import MkRadios from '@/components/MkRadios.vue';
const meta = await misskeyApi('admin/meta');
-const proxyAccount = ref(meta.proxyAccountId ? await misskeyApi('users/show', { userId: meta.proxyAccountId }) : null);
+const proxyAccount = await misskeyApi('users/show', { userId: meta.proxyAccountId });
const infoForm = useForm({
name: meta.name ?? '',
@@ -425,16 +427,14 @@ const federationForm = useForm({
fetchInstance(true);
});
-function chooseProxyAccount() {
- os.selectUser({ localOnly: true }).then(user => {
- proxyAccount.value = user;
- os.apiWithDialog('admin/update-meta', {
- proxyAccountId: user.id,
- }).then(() => {
- fetchInstance(true);
- });
+const proxyAccountForm = useForm({
+ description: proxyAccount.description,
+}, async (state) => {
+ await os.apiWithDialog('admin/update-proxy-account', {
+ description: state.description,
});
-}
+ fetchInstance(true);
+});
async function genKeys() {
if (serviceWorkerForm.savedState.swPrivateKey) {
@@ -450,7 +450,7 @@ async function genKeys() {
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.general,
icon: 'ti ti-settings',
}));
diff --git a/packages/frontend/src/pages/admin/system-webhook.vue b/packages/frontend/src/pages/admin/system-webhook.vue
index c59abda24a..d8eb9b92ee 100644
--- a/packages/frontend/src/pages/admin/system-webhook.vue
+++ b/packages/frontend/src/pages/admin/system-webhook.vue
@@ -30,11 +30,11 @@ import { computed, onMounted, ref } from 'vue';
import { entities } from 'misskey-js';
import XItem from './system-webhook.item.vue';
import FormSection from '@/components/form/section.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import XHeader from '@/pages/admin/_header_.vue';
import MkButton from '@/components/MkButton.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { showSystemWebhookEditorDialog } from '@/components/MkSystemWebhookEditor.impl.js';
import * as os from '@/os.js';
@@ -82,7 +82,7 @@ onMounted(async () => {
await fetchWebhooks();
});
-definePageMetadata(() => ({
+definePage(() => ({
title: 'SystemWebhook',
icon: 'ti ti-webhook',
}));
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index c693eed850..e33ec8a1e6 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -10,6 +10,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="900">
<div class="_gaps">
<div :class="$style.inputs">
+ <MkButton style="margin-left: auto" @click="resetQuery">{{ i18n.ts.reset }}</MkButton>
+ </div>
+ <div :class="$style.inputs">
<MkSelect v-model="sort" style="flex: 1;">
<template #label>{{ i18n.ts.sort }}</template>
<option value="-createdAt">{{ i18n.ts.registeredDate }} ({{ i18n.ts.ascendingOrder }})</option>
@@ -58,25 +61,36 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, shallowRef, ref } from 'vue';
+import { computed, useTemplateRef, ref, watchEffect } from 'vue';
import XHeader from './_header_.vue';
+import { defaultMemoryStorage } from '@/memory-storage';
+import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
import * as os from '@/os.js';
-import { lookupUser } from '@/scripts/admin-lookup.js';
+import { lookupUser } from '@/utility/admin-lookup.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import { dateString } from '@/filters/date.js';
-const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
+type SearchQuery = {
+ sort?: string;
+ state?: string;
+ origin?: string;
+ username?: string;
+ hostname?: string;
+};
+
+const paginationComponent = useTemplateRef('paginationComponent');
+const storedQuery = JSON.parse(defaultMemoryStorage.getItem('admin-users-query') ?? '{}') as SearchQuery;
-const sort = ref('+createdAt');
-const state = ref('all');
-const origin = ref('local');
-const searchUsername = ref('');
-const searchHost = ref('');
+const sort = ref(storedQuery.sort ?? '+createdAt');
+const state = ref(storedQuery.state ?? 'all');
+const origin = ref(storedQuery.origin ?? 'local');
+const searchUsername = ref(storedQuery.username ?? '');
+const searchHost = ref(storedQuery.hostname ?? '');
const pagination = {
endpoint: 'admin/show-users' as const,
limit: 10,
@@ -120,6 +134,14 @@ function show(user) {
os.pageWindow(`/admin/user/${user.id}`);
}
+function resetQuery() {
+ sort.value = '+createdAt';
+ state.value = 'all';
+ origin.value = 'local';
+ searchUsername.value = '';
+ searchHost.value = '';
+}
+
const headerActions = computed(() => [{
icon: 'ti ti-search',
text: i18n.ts.search,
@@ -138,7 +160,17 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+watchEffect(() => {
+ defaultMemoryStorage.setItem('admin-users-query', JSON.stringify({
+ sort: sort.value,
+ state: state.value,
+ origin: origin.value,
+ username: searchUsername.value,
+ hostname: searchHost.value,
+ }));
+});
+
+definePage(() => ({
title: i18n.ts.users,
icon: 'ti ti-users',
}));
diff --git a/packages/frontend/src/pages/ads.vue b/packages/frontend/src/pages/ads.vue
index b31807f9f5..700ac0bd1a 100644
--- a/packages/frontend/src/pages/ads.vue
+++ b/packages/frontend/src/pages/ads.vue
@@ -4,23 +4,21 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
-
+<PageWithHeader>
<MkSpacer :contentMax="500">
<div class="_gaps">
<MkAd v-for="ad in instance.ads" :key="ad.id" :specify="ad"/>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.ads,
icon: 'ti ti-ad',
}));
diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue
index 56c10fb292..2e0c7d2f42 100644
--- a/packages/frontend/src/pages/announcement.vue
+++ b/packages/frontend/src/pages/announcement.vue
@@ -4,14 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.fadeEnterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.fadeLeaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.fadeEnterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.fadeLeaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.fadeEnterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.fadeLeaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.fadeEnterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.fadeLeaveTo : ''"
mode="out-in"
>
<div v-if="announcement" :key="announcement.id" class="_panel" :class="$style.announcement">
@@ -44,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-else/>
</Transition>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -52,11 +51,12 @@ import { ref, computed, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i, updateAccountPartial } from '@/account.js';
-import { defaultStore } from '@/store.js';
+import { definePage } from '@/page.js';
+import { $i } from '@/i.js';
+import { prefer } from '@/preferences.js';
+import { updateCurrentAccountPartial } from '@/accounts.js';
const props = defineProps<{
announcementId: string;
@@ -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) {
- updateAccountPartial({
+ updateCurrentAccountPartial({
unreadAnnouncements: $i.unreadAnnouncements.filter((a: { id: string; }) => a.id !== target.id),
});
}
@@ -102,7 +102,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: announcement.value ? announcement.value.title : i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));
diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index 79b9e7607d..36e9e4342a 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div :key="tab" class="_gaps">
+ <div class="_gaps">
<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
<MkPagination ref="paginationEl" :key="tab" v-slot="{items}" :pagination="tab === 'current' ? paginationCurrent : paginationPast" class="_gaps">
<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
@@ -43,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -53,10 +52,11 @@ import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i, updateAccountPartial } from '@/account.js';
+import { definePage } from '@/page.js';
+import { $i } from '@/i.js';
+import { updateCurrentAccountPartial } from '@/accounts.js';
const paginationCurrent = {
endpoint: 'announcements' as const,
@@ -94,7 +94,7 @@ async function read(target) {
return a;
});
misskeyApi('i/read-announcement', { announcementId: target.id });
- updateAccountPartial({
+ updateCurrentAccountPartial({
unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id),
});
}
@@ -111,7 +111,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-point',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.announcements,
icon: 'ti ti-speakerphone',
}));
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 350f772d65..400c19ce93 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<div ref="rootEl">
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
@@ -20,19 +19,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { computed, watch, ref, shallowRef } from 'vue';
+import { computed, watch, ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
-import MkTimeline from '@/components/MkTimeline.vue';
import { scroll } from '@@/js/scroll.js';
+import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -42,8 +41,8 @@ const props = defineProps<{
const antenna = ref<Misskey.entities.Antenna | null>(null);
const queue = ref(0);
-const rootEl = shallowRef<HTMLElement>();
-const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
+const rootEl = useTemplateRef('rootEl');
+const tlEl = useTemplateRef('tlEl');
function queueUpdated(q) {
queue.value = q;
@@ -88,7 +87,7 @@ const headerActions = computed(() => antenna.value ? [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: antenna.value ? antenna.value.name : i18n.ts.antennas,
icon: 'ti ti-antenna',
}));
diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue
index 1e3bb0de6b..53020bfb08 100644
--- a/packages/frontend/src/pages/api-console.vue
+++ b/packages/frontend/src/pages/api-console.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<div class="_gaps_m">
<div class="_gaps_m">
@@ -30,19 +29,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import JSON5 from 'json5';
-import { Endpoints } from 'misskey-js';
+import type { Endpoints } from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
const body = ref('{}');
const endpoint = ref('');
@@ -87,7 +86,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: 'API console',
icon: 'ti ti-terminal-2',
}));
diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue
index f4fb2ef4d5..5b1fd1a386 100644
--- a/packages/frontend/src/pages/auth.form.vue
+++ b/packages/frontend/src/pages/auth.form.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
@@ -38,7 +38,7 @@ const emit = defineEmits<{
const app = computed(() => props.session.app);
const name = computed(() => {
- const el = document.createElement('div');
+ const el = window.document.createElement('div');
el.textContent = app.value.name;
return el.innerHTML;
});
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index 30e0e99326..898fa7996e 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="500">
<div v-if="state == 'fetch-session-error'">
<p>{{ i18n.ts.somethingHappened }}</p>
@@ -38,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSignin @login="onLogin"/>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -46,10 +45,11 @@ import { onMounted, ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import XForm from './auth.form.vue';
import MkSignin from '@/components/MkSignin.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { $i, login } from '@/account.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { $i } from '@/i.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
+import { login } from '@/accounts.js';
const props = defineProps<{
token: string;
@@ -84,7 +84,7 @@ function accepted() {
} else if (session.value && session.value.app.callbackUrl) {
const url = new URL(session.value.app.callbackUrl);
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}`;
+ window.location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`;
}
}
@@ -118,7 +118,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts._auth.shareAccessTitle,
icon: 'ti ti-apps',
}));
diff --git a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue
index a834f1c5fd..12fd867407 100644
--- a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue
+++ b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue
@@ -68,14 +68,14 @@ 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 { misskeyApi } from '@/utility/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';
+import { ensureSignin } from '@/i.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const props = defineProps<{
avatarDecoration?: any,
diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue
index a5cafb1678..2bab449089 100644
--- a/packages/frontend/src/pages/avatar-decorations.vue
+++ b/packages/frontend/src/pages/avatar-decorations.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="900">
<div class="_gaps">
<div :class="$style.decorations">
@@ -22,19 +21,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { ref, computed, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
@@ -86,7 +85,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.avatarDecorations,
icon: 'ti ti-sparkles',
}));
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 6d8274a55c..084fee15cf 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<div v-if="channelId == null || channel != null" class="_gaps_m">
<MkInput v-model="name">
@@ -65,7 +64,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -74,15 +73,15 @@ import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkColorInput from '@/components/MkColorInput.vue';
-import { selectFile } from '@/scripts/select-file.js';
+import { selectFile } from '@/utility/select-file.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkTextarea from '@/components/MkTextarea.vue';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@@ -202,7 +201,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: props.channelId ? i18n.ts._channel.edit : i18n.ts._channel.create,
icon: 'ti ti-device-tv',
}));
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index fb99379a0a..303558a621 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
- <MkSpacer :contentMax="700" :class="$style.main">
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
+ <MkSpacer :contentMax="700">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="channel && tab === 'overview'" key="overview" class="_gaps">
+ <div v-if="channel && tab === 'overview'" class="_gaps">
<div class="_panel" :class="$style.bannerContainer">
<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
@@ -33,18 +32,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkFoldableSection>
</div>
- <div v-if="channel && tab === 'timeline'" key="timeline" class="_gaps">
+ <div v-if="channel && tab === 'timeline'" class="_gaps">
<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
- <MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
+ <MkPostForm v-if="$i && prefer.r.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
<MkTimeline :key="channelId + withRenotes + onlyFiles" src="channel" :channel="channelId" :withRenotes="withRenotes" :onlyFiles="onlyFiles" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
</div>
- <div v-else-if="tab === 'featured'" key="featured">
+ <div v-else-if="tab === 'featured'">
<MkNotes :pagination="featuredPagination"/>
</div>
- <div v-else-if="tab === 'search'" key="search">
+ <div v-else-if="tab === 'search'">
<div v-if="notesSearchAvailable" class="_gaps">
<div>
<MkInput v-model="searchQuery" @enter="search()">
@@ -69,38 +68,38 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSpacer>
</div>
</template>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
+import { url } from '@@/js/config.js';
+import type { PageHeaderItem } from '@/types/page-header.js';
import MkPostForm from '@/components/MkPostForm.vue';
import MkTimeline from '@/components/MkTimeline.vue';
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { $i, iAmModerator } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { $i, iAmModerator } from '@/i.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { definePage } from '@/page.js';
+import { deviceKind } from '@/utility/device-kind.js';
import MkNotes from '@/components/MkNotes.vue';
-import { url } from '@@/js/config.js';
import { favoritedChannelsCache } from '@/cache.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
import MkNote from '@/components/MkNote.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { PageHeaderItem } from '@/types/page-header.js';
-import { isSupportShare } from '@/scripts/navigator.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { notesSearchAvailable } from '@/scripts/check-permissions.js';
+import { isSupportShare } from '@/utility/navigator.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { notesSearchAvailable } from '@/utility/check-permissions.js';
import { miLocalStorage } from '@/local-storage.js';
-import { useRouter } from '@/router/supplier.js';
-import { deepMerge } from '@/scripts/merge.js';
+import { useRouter } from '@/router.js';
+import { deepMerge } from '@/utility/merge.js';
const router = useRouter();
@@ -241,7 +240,6 @@ const headerActions = computed(() => {
return;
}
copyToClipboard(`${url}/channels/${channel.value.id}`);
- os.success();
},
});
@@ -296,17 +294,13 @@ const headerTabs = computed(() => [{
icon: 'ti ti-search',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: channel.value ? channel.value.name : i18n.ts.channel,
icon: 'ti ti-device-tv',
}));
</script>
<style lang="scss" module>
-.main {
- min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
-}
-
.footer {
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
backdrop-filter: var(--MI-blur, blur(15px));
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 6830c1ace4..76800aaf70 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="1200">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'search'" key="search" :class="$style.searchRoot">
+ <div v-if="tab === 'search'" :class="$style.searchRoot">
<div class="_gaps">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
<template #prefix><i class="ti ti-search"></i></template>
@@ -25,28 +24,28 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkChannelList :key="key" :pagination="channelPagination"/>
</MkFoldableSection>
</div>
- <div v-if="tab === 'featured'" key="featured">
+ <div v-if="tab === 'featured'">
<MkPagination v-slot="{items}" :pagination="featuredPagination">
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
</div>
</MkPagination>
</div>
- <div v-else-if="tab === 'favorites'" key="favorites">
+ <div v-else-if="tab === 'favorites'">
<MkPagination v-slot="{items}" :pagination="favoritesPagination">
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
</div>
</MkPagination>
</div>
- <div v-else-if="tab === 'following'" key="following">
+ <div v-else-if="tab === 'following'">
<MkPagination v-slot="{items}" :pagination="followingPagination">
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
</div>
</MkPagination>
</div>
- <div v-else-if="tab === 'owned'" key="owned">
+ <div v-else-if="tab === 'owned'">
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="ownedPagination">
<div :class="$style.root">
@@ -56,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -69,9 +68,9 @@ import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -161,7 +160,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-edit',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.channel,
icon: 'ti ti-device-tv',
}));
diff --git a/packages/frontend/src/pages/chat/XMessage.vue b/packages/frontend/src/pages/chat/XMessage.vue
new file mode 100644
index 0000000000..1e7f8e20ea
--- /dev/null
+++ b/packages/frontend/src/pages/chat/XMessage.vue
@@ -0,0 +1,245 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.root, { [$style.isMe]: isMe }]">
+ <MkAvatar :class="$style.avatar" :user="message.fromUser" :link="!isMe" :preview="false"/>
+ <div :class="$style.body">
+ <MkFukidashi :class="$style.fukidashi" :tail="isMe ? 'right' : 'left'" :accented="isMe">
+ <div v-if="!message.isDeleted" :class="$style.content">
+ <Mfm v-if="message.text" ref="text" class="_selectable" :text="message.text" :i="$i"/>
+ <MkMediaList v-if="message.file" :mediaList="[message.file]" :class="$style.file"/>
+ </div>
+ <div v-else :class="$style.content">
+ <p>{{ i18n.ts.deleted }}</p>
+ </div>
+ </MkFukidashi>
+ <MkUrlPreview v-for="url in urls" :key="url" :url="url" style="margin: 8px 0;"/>
+ <div :class="$style.footer">
+ <button class="_textButton" style="color: currentColor;" @click="showMenu"><i class="ti ti-dots-circle-horizontal"></i></button>
+ <MkTime :class="$style.time" :time="message.createdAt"/>
+ <MkA v-if="isSearchResult && message.toRoomId" :to="`/chat/room/${message.toRoomId}`">{{ message.toRoom.name }}</MkA>
+ <MkA v-if="isSearchResult && message.toUserId && isMe" :to="`/chat/user/${message.toUserId}`">@{{ message.toUser.username }}</MkA>
+ </div>
+ <TransitionGroup
+ :enterActiveClass="prefer.s.animation ? $style.transition_reaction_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_reaction_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_reaction_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_reaction_leaveTo : ''"
+ :moveClass="prefer.s.animation ? $style.transition_reaction_move : ''"
+ tag="div" :class="$style.reactions"
+ >
+ <div v-for="record in message.reactions" :key="record.reaction + record.user.id" :class="$style.reaction">
+ <MkAvatar :user="record.user" :link="false" :class="$style.reactionAvatar"/>
+ <MkReactionIcon
+ :withTooltip="true"
+ :reaction="record.reaction.replace(/^:(\w+):$/, ':$1@.:')"
+ :noStyle="true"
+ :class="$style.reactionIcon"
+ />
+ </div>
+ </TransitionGroup>
+ </div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, defineAsyncComponent } from 'vue';
+import * as mfm from 'mfm-js';
+import * as Misskey from 'misskey-js';
+import { url } from '@@/js/config.js';
+import type { MenuItem } from '@/types/menu.js';
+import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
+import MkUrlPreview from '@/components/MkUrlPreview.vue';
+import { ensureSignin } from '@/i.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { i18n } from '@/i18n.js';
+import MkFukidashi from '@/components/MkFukidashi.vue';
+import * as os from '@/os.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import MkMediaList from '@/components/MkMediaList.vue';
+import { reactionPicker } from '@/utility/reaction-picker.js';
+import * as sound from '@/utility/sound.js';
+import MkReactionIcon from '@/components/MkReactionIcon.vue';
+import { prefer } from '@/preferences.js';
+
+const $i = ensureSignin();
+
+const props = defineProps<{
+ message: Misskey.entities.ChatMessageLite | Misskey.entities.ChatMessage;
+ isSearchResult?: boolean;
+}>();
+
+const isMe = computed(() => props.message.fromUserId === $i.id);
+const urls = computed(() => props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : []);
+
+function react(ev: MouseEvent) {
+ reactionPicker.show(ev.currentTarget ?? ev.target, null, async (reaction) => {
+ sound.playMisskeySfx('reaction');
+
+ misskeyApi('chat/messages/react', {
+ messageId: props.message.id,
+ reaction: reaction,
+ });
+ });
+}
+
+function showMenu(ev: MouseEvent) {
+ const menu: MenuItem[] = [];
+
+ if (!isMe.value) {
+ menu.push({
+ text: i18n.ts.reaction,
+ icon: 'ti ti-mood-plus',
+ action: (ev) => {
+ react(ev);
+ },
+ });
+
+ menu.push({
+ type: 'divider',
+ });
+ }
+
+ menu.push({
+ text: i18n.ts.copyContent,
+ icon: 'ti ti-copy',
+ action: () => {
+ copyToClipboard(props.message.text);
+ },
+ });
+
+ menu.push({
+ type: 'divider',
+ });
+
+ if (isMe.value) {
+ menu.push({
+ text: i18n.ts.delete,
+ icon: 'ti ti-trash',
+ danger: true,
+ action: () => {
+ misskeyApi('chat/messages/delete', {
+ messageId: props.message.id,
+ });
+ },
+ });
+ } else {
+ menu.push({
+ text: i18n.ts.reportAbuse,
+ icon: 'ti ti-exclamation-circle',
+ action: () => {
+ const localUrl = `${url}/chat/messages/${props.message.id}`;
+ const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
+ user: props.message.fromUser,
+ initialComment: `${localUrl}\n-----\n`,
+ }, {
+ closed: () => dispose(),
+ });
+ },
+ });
+ }
+
+ os.popupMenu(menu, ev.currentTarget ?? ev.target);
+}
+</script>
+
+<style lang="scss" module>
+.transition_reaction_move,
+.transition_reaction_enterActive,
+.transition_reaction_leaveActive {
+ transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important;
+}
+.transition_reaction_enterFrom,
+.transition_reaction_leaveTo {
+ opacity: 0;
+ transform: scale(0.7);
+}
+.transition_reaction_leaveActive {
+ position: absolute;
+}
+
+.root {
+ position: relative;
+ display: flex;
+
+ &.isMe {
+ flex-direction: row-reverse;
+ text-align: right;
+
+ .content {
+ color: var(--MI_THEME-fgOnAccent);
+ }
+
+ .footer {
+ flex-direction: row-reverse;
+ }
+ }
+}
+
+.avatar {
+ position: sticky;
+ top: calc(16px + var(--MI-stickyTop, 0px));
+ display: block;
+ width: 52px;
+ height: 52px;
+}
+
+.body {
+ margin: 0 12px;
+}
+
+.content {
+ overflow: clip;
+ overflow-wrap: break-word;
+ word-break: break-word;
+}
+
+.file {
+}
+
+.footer {
+ display: flex;
+ flex-direction: row;
+ gap: 0.5em;
+ margin-top: 4px;
+ font-size: 75%;
+}
+
+.time {
+ opacity: 0.5;
+}
+
+.reactions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ margin-top: 8px;
+
+ &:empty {
+ display: none;
+ }
+}
+
+.reaction {
+ display: flex;
+ align-items: center;
+ border: solid 1px var(--MI_THEME-divider);
+ border-radius: 999px;
+ padding: 8px;
+}
+
+.reactionAvatar {
+ width: 24px;
+ height: 24px;
+ margin-right: 8px;
+}
+
+.reactionIcon {
+ width: 24px;
+ height: 24px;
+}
+</style>
diff --git a/packages/frontend/src/pages/chat/XRoom.vue b/packages/frontend/src/pages/chat/XRoom.vue
new file mode 100644
index 0000000000..b063a0cdd1
--- /dev/null
+++ b/packages/frontend/src/pages/chat/XRoom.vue
@@ -0,0 +1,41 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkA :to="`/chat/room/${room.id}`" class="_panel _gaps_s" :class="$style.root">
+ <div :class="$style.header">
+ <div style="font-weight: bold;">{{ room.name }}</div>
+ <MkAvatar :user="room.owner" :link="false" :class="$style.headerAvatar"/>
+ </div>
+ <hr>
+ <div>{{ room.description }}</div>
+</MkA>
+</template>
+
+<script lang="ts" setup>
+import * as Misskey from 'misskey-js';
+
+const props = defineProps<{
+ room: Misskey.entities.ChatRoom;
+}>();
+
+</script>
+
+<style lang="scss" module>
+.root {
+ padding: 16px;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+}
+
+.headerAvatar {
+ width: 30px;
+ height: 30px;
+ margin-left: auto;
+}
+</style>
diff --git a/packages/frontend/src/pages/chat/home.home.vue b/packages/frontend/src/pages/chat/home.home.vue
new file mode 100644
index 0000000000..1d0605136c
--- /dev/null
+++ b/packages/frontend/src/pages/chat/home.home.vue
@@ -0,0 +1,252 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+ <MkButton primary gradate rounded :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton>
+
+ <MkAd :prefer="['horizontal', 'horizontal-big']"/>
+
+ <MkInput
+ v-model="searchQuery"
+ :placeholder="i18n.ts._chat.searchMessages"
+ type="search"
+ >
+ <template #prefix><i class="ti ti-search"></i></template>
+ </MkInput>
+
+ <MkButton v-if="searchQuery.length > 0" primary rounded @click="search">{{ i18n.ts.search }}</MkButton>
+
+ <MkFoldableSection v-if="searched">
+ <template #header>{{ i18n.ts.searchResult }}</template>
+
+ <div class="_gaps_s">
+ <div v-for="message in searchResults" :key="message.id" :class="$style.searchResultItem">
+ <XMessage :message="message" :isSearchResult="true"/>
+ </div>
+ </div>
+ </MkFoldableSection>
+
+ <MkFoldableSection>
+ <template #header>{{ i18n.ts._chat.history }}</template>
+
+ <div v-if="history.length > 0" class="_gaps_s">
+ <MkA
+ v-for="item in history"
+ :key="item.id"
+ :class="[$style.message, { [$style.isMe]: item.isMe, [$style.isRead]: item.message.isRead }]"
+ class="_panel"
+ :to="item.message.toRoomId ? `/chat/room/${item.message.toRoomId}` : `/chat/user/${item.other!.id}`"
+ >
+ <MkAvatar v-if="item.other" :class="$style.messageAvatar" :user="item.other" indicator :preview="false"/>
+ <div :class="$style.messageBody">
+ <header v-if="item.message.toRoom" :class="$style.messageHeader">
+ <span :class="$style.messageHeaderName">{{ item.message.toRoom.name }}</span>
+ <MkTime :time="item.message.createdAt" :class="$style.messageHeaderTime"/>
+ </header>
+ <header v-else :class="$style.messageHeader">
+ <MkUserName :class="$style.messageHeaderName" :user="item.other!"/>
+ <MkAcct :class="$style.messageHeaderUsername" :user="item.other!"/>
+ <MkTime :time="item.message.createdAt" :class="$style.messageHeaderTime"/>
+ </header>
+ <div :class="$style.messageBodyText"><span v-if="item.isMe" :class="$style.youSaid">{{ i18n.ts.you }}:</span>{{ item.message.text }}</div>
+ </div>
+ </MkA>
+ </div>
+ <div v-if="!fetching && history.length == 0" class="_fullinfo">
+ <div>{{ i18n.ts._chat.noHistory }}</div>
+ </div>
+ <MkLoading v-if="fetching"/>
+ </MkFoldableSection>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import XMessage from './XMessage.vue';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { ensureSignin } from '@/i.js';
+import { useRouter } from '@/router.js';
+import * as os from '@/os.js';
+import { updateCurrentAccountPartial } from '@/accounts.js';
+import MkInput from '@/components/MkInput.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
+
+const $i = ensureSignin();
+
+const router = useRouter();
+
+const fetching = ref(true);
+const history = ref<{
+ id: string;
+ message: Misskey.entities.ChatMessage;
+ other: Misskey.entities.ChatMessage['fromUser'] | Misskey.entities.ChatMessage['toUser'] | null;
+ isMe: boolean;
+}[]>([]);
+
+const searchQuery = ref('');
+const searched = ref(false);
+const searchResults = ref<Misskey.entities.ChatMessage[]>([]);
+
+function start(ev: MouseEvent) {
+ os.popupMenu([{
+ text: i18n.ts._chat.individualChat,
+ caption: i18n.ts._chat.individualChat_description,
+ icon: 'ti ti-user',
+ action: () => { startUser(); },
+ }, { type: 'divider' }, {
+ type: 'parent',
+ text: i18n.ts._chat.roomChat,
+ caption: i18n.ts._chat.roomChat_description,
+ icon: 'ti ti-users-group',
+ children: [{
+ text: i18n.ts._chat.createRoom,
+ icon: 'ti ti-plus',
+ action: () => { createRoom(); },
+ }],
+ }], ev.currentTarget ?? ev.target);
+}
+
+async function startUser() {
+ os.selectUser().then(user => {
+ router.push(`/chat/user/${user.id}`);
+ });
+}
+
+async function createRoom() {
+ const { canceled, result } = await os.inputText({
+ title: i18n.ts.name,
+ minLength: 1,
+ });
+ if (canceled) return;
+
+ const room = await misskeyApi('chat/rooms/create', {
+ name: result,
+ });
+
+ router.push(`/chat/room/${room.id}`);
+}
+
+async function search() {
+ const res = await misskeyApi('chat/messages/search', {
+ query: searchQuery.value,
+ });
+
+ searchResults.value = res;
+ searched.value = true;
+}
+
+async function fetchHistory() {
+ fetching.value = true;
+
+ const [userMessages, roomMessages] = await Promise.all([
+ misskeyApi('chat/history', { room: false }),
+ misskeyApi('chat/history', { room: true }),
+ ]);
+
+ history.value = [...userMessages, ...roomMessages]
+ .toSorted((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
+ .map(m => ({
+ id: m.id,
+ message: m,
+ other: m.room == null ? (m.fromUserId === $i.id ? m.toUser : m.fromUser) : null,
+ isMe: m.fromUserId === $i.id,
+ }));
+
+ fetching.value = false;
+
+ updateCurrentAccountPartial({ hasUnreadChatMessages: false });
+}
+
+onMounted(() => {
+ fetchHistory();
+});
+</script>
+
+<style lang="scss" module>
+.start {
+ margin: 0 auto;
+}
+
+.message {
+ position: relative;
+ display: flex;
+ padding: 16px 24px;
+
+ &.isRead,
+ &.isMe {
+ opacity: 0.8;
+ }
+
+ &:not(.isMe):not(.isRead) {
+ &::before {
+ content: '';
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ width: 8px;
+ height: 8px;
+ border-radius: 100%;
+ background-color: var(--MI_THEME-accent);
+ }
+ }
+}
+
+.messageAvatar {
+ width: 50px;
+ height: 50px;
+ margin: 0 16px 0 0;
+}
+
+.messageBody {
+ flex: 1;
+ min-width: 0;
+}
+
+.messageHeader {
+ display: flex;
+ align-items: center;
+ margin-bottom: 2px;
+ white-space: nowrap;
+ overflow: clip;
+}
+
+.messageHeaderName {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 1em;
+ font-weight: bold;
+}
+
+.messageHeaderUsername {
+ margin: 0 8px;
+}
+
+.messageHeaderTime {
+ margin-left: auto;
+}
+
+.messageBodyText {
+ overflow: hidden;
+ overflow-wrap: break-word;
+ font-size: 1.1em;
+}
+
+.youSaid {
+ font-weight: bold;
+ margin-right: 0.5em;
+}
+
+.searchResultItem {
+ padding: 12px;
+ border: solid 1px var(--MI_THEME-divider);
+ border-radius: 12px;
+}
+</style>
diff --git a/packages/frontend/src/pages/chat/home.invitations.vue b/packages/frontend/src/pages/chat/home.invitations.vue
new file mode 100644
index 0000000000..4c3c0b282e
--- /dev/null
+++ b/packages/frontend/src/pages/chat/home.invitations.vue
@@ -0,0 +1,98 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+ <div v-if="invitations.length > 0" class="_gaps_s">
+ <MkFolder v-for="invitation in invitations" :key="invitation.id" :defaultOpen="true">
+ <template #icon><i class="ti ti-users-group"></i></template>
+ <template #label>{{ invitation.room.name }}</template>
+ <template #suffix><MkTime :time="invitation.createdAt"/></template>
+ <template #footer>
+ <div class="_buttons">
+ <MkButton primary @click="join(invitation)"><i class="ti ti-plus"></i> {{ i18n.ts._chat.join }}</MkButton>
+ <MkButton danger @click="ignore(invitation)"><i class="ti ti-x"></i> {{ i18n.ts._chat.ignore }}</MkButton>
+ </div>
+ </template>
+
+ <div :class="$style.invitationBody">
+ <MkAvatar :user="invitation.room.owner" :class="$style.invitationBodyAvatar" link/>
+ <div style="flex: 1;" class="_gaps_s">
+ <MkUserName :user="invitation.room.owner"/>
+ <hr>
+ <div>{{ invitation.room.description === '' ? i18n.ts.noDescription : invitation.room.description }}</div>
+ </div>
+ </div>
+ </MkFolder>
+ </div>
+ <div v-if="!fetching && invitations.length == 0" class="_fullinfo">
+ <div>{{ i18n.ts._chat.noInvitations }}</div>
+ </div>
+ <MkLoading v-if="fetching"/>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { ensureSignin } from '@/i.js';
+import { useRouter } from '@/router.js';
+import * as os from '@/os.js';
+import MkFolder from '@/components/MkFolder.vue';
+
+const $i = ensureSignin();
+
+const router = useRouter();
+
+const fetching = ref(true);
+const invitations = ref<Misskey.entities.ChatRoomInvitation[]>([]);
+
+async function fetchInvitations() {
+ fetching.value = true;
+
+ const res = await misskeyApi('chat/rooms/invitations/inbox', {
+ });
+
+ invitations.value = res;
+
+ fetching.value = false;
+}
+
+async function join(invitation: Misskey.entities.ChatRoomInvitation) {
+ await misskeyApi('chat/rooms/join', {
+ roomId: invitation.room.id,
+ });
+
+ router.push(`/chat/room/${invitation.room.id}`);
+}
+
+async function ignore(invitation: Misskey.entities.ChatRoomInvitation) {
+ await misskeyApi('chat/rooms/invitations/ignore', {
+ roomId: invitation.room.id,
+ });
+
+ invitations.value = invitations.value.filter(i => i.id !== invitation.id);
+}
+
+onMounted(() => {
+ fetchInvitations();
+});
+</script>
+
+<style lang="scss" module>
+.invitationBody {
+ display: flex;
+ align-items: center;
+}
+
+.invitationBodyAvatar {
+ margin-right: 12px;
+ width: 45px;
+ height: 45px;
+}
+</style>
diff --git a/packages/frontend/src/pages/chat/home.joiningRooms.vue b/packages/frontend/src/pages/chat/home.joiningRooms.vue
new file mode 100644
index 0000000000..63e4d2adf8
--- /dev/null
+++ b/packages/frontend/src/pages/chat/home.joiningRooms.vue
@@ -0,0 +1,54 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+ <div v-if="memberships.length > 0" class="_gaps_s">
+ <XRoom v-for="membership in memberships" :key="membership.id" :room="membership.room"/>
+ </div>
+ <div v-if="!fetching && memberships.length == 0" class="_fullinfo">
+ <div>{{ i18n.ts._chat.noRooms }}</div>
+ </div>
+ <MkLoading v-if="fetching"/>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import XRoom from './XRoom.vue';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { ensureSignin } from '@/i.js';
+import { useRouter } from '@/router.js';
+import * as os from '@/os.js';
+
+const $i = ensureSignin();
+
+const router = useRouter();
+
+const fetching = ref(true);
+const memberships = ref<Misskey.entities.ChatRoomMembership[]>([]);
+
+async function fetchRooms() {
+ fetching.value = true;
+
+ const res = await misskeyApi('chat/rooms/joining', {
+ });
+
+ memberships.value = res;
+
+ fetching.value = false;
+}
+
+onMounted(() => {
+ fetchRooms();
+});
+</script>
+
+<style lang="scss" module>
+
+</style>
diff --git a/packages/frontend/src/pages/chat/home.ownedRooms.vue b/packages/frontend/src/pages/chat/home.ownedRooms.vue
new file mode 100644
index 0000000000..b0449fb373
--- /dev/null
+++ b/packages/frontend/src/pages/chat/home.ownedRooms.vue
@@ -0,0 +1,54 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+ <div v-if="rooms.length > 0" class="_gaps_s">
+ <XRoom v-for="room in rooms" :key="room.id" :room="room"/>
+ </div>
+ <div v-if="!fetching && rooms.length == 0" class="_fullinfo">
+ <div>{{ i18n.ts._chat.noRooms }}</div>
+ </div>
+ <MkLoading v-if="fetching"/>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import XRoom from './XRoom.vue';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { ensureSignin } from '@/i.js';
+import { useRouter } from '@/router.js';
+import * as os from '@/os.js';
+
+const $i = ensureSignin();
+
+const router = useRouter();
+
+const fetching = ref(true);
+const rooms = ref<Misskey.entities.ChatRoom[]>([]);
+
+async function fetchRooms() {
+ fetching.value = true;
+
+ const res = await misskeyApi('chat/rooms/owned', {
+ });
+
+ rooms.value = res;
+
+ fetching.value = false;
+}
+
+onMounted(() => {
+ fetchRooms();
+});
+</script>
+
+<style lang="scss" module>
+
+</style>
diff --git a/packages/frontend/src/pages/chat/home.vue b/packages/frontend/src/pages/chat/home.vue
new file mode 100644
index 0000000000..c2b272a42d
--- /dev/null
+++ b/packages/frontend/src/pages/chat/home.vue
@@ -0,0 +1,60 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
+ <MkPolkadots v-if="tab === 'home'" accented/>
+ <MkSpacer :contentMax="700">
+ <MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+ <XHome v-if="tab === 'home'"/>
+ <XInvitations v-else-if="tab === 'invitations'"/>
+ <XJoiningRooms v-else-if="tab === 'joiningRooms'"/>
+ <XOwnedRooms v-else-if="tab === 'ownedRooms'"/>
+ </MkHorizontalSwipe>
+ </MkSpacer>
+</PageWithHeader>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue';
+import XHome from './home.home.vue';
+import XInvitations from './home.invitations.vue';
+import XJoiningRooms from './home.joiningRooms.vue';
+import XOwnedRooms from './home.ownedRooms.vue';
+import { i18n } from '@/i18n.js';
+import { definePage } from '@/page.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkPolkadots from '@/components/MkPolkadots.vue';
+
+const tab = ref('home');
+
+const headerActions = computed(() => []);
+
+const headerTabs = computed(() => [{
+ key: 'home',
+ title: i18n.ts._chat.home,
+ icon: 'ti ti-home',
+}, {
+ key: 'invitations',
+ title: i18n.ts._chat.invitations,
+ icon: 'ti ti-ticket',
+}, {
+ key: 'joiningRooms',
+ title: i18n.ts._chat.joiningRooms,
+ icon: 'ti ti-users-group',
+}, {
+ key: 'ownedRooms',
+ title: i18n.ts._chat.yourRooms,
+ icon: 'ti ti-settings',
+}]);
+
+definePage(() => ({
+ title: i18n.ts.chat + ' (beta)',
+ icon: 'ti ti-message',
+}));
+</script>
+
+<style lang="scss" module>
+</style>
diff --git a/packages/frontend/src/pages/chat/message.vue b/packages/frontend/src/pages/chat/message.vue
new file mode 100644
index 0000000000..be8be7e5d1
--- /dev/null
+++ b/packages/frontend/src/pages/chat/message.vue
@@ -0,0 +1,55 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<PageWithHeader>
+ <MkSpacer :contentMax="700">
+ <div v-if="initializing">
+ <MkLoading/>
+ </div>
+ <div v-else>
+ <XMessage :message="message"/>
+ </div>
+ </MkSpacer>
+</PageWithHeader>
+</template>
+
+<script lang="ts" setup>
+import { ref, useTemplateRef, computed, watch, onMounted, nextTick, onBeforeUnmount, onDeactivated, onActivated } from 'vue';
+import * as Misskey from 'misskey-js';
+import XMessage from './XMessage.vue';
+import * as os from '@/os.js';
+import { useStream } from '@/stream.js';
+import { i18n } from '@/i18n.js';
+import { ensureSignin } from '@/i.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
+import MkButton from '@/components/MkButton.vue';
+
+const props = defineProps<{
+ messageId?: string;
+}>();
+
+const initializing = ref(true);
+const message = ref<Misskey.entities.ChatMessage>();
+
+async function initialize() {
+ initializing.value = true;
+
+ message.value = await misskeyApi('chat/messages/show', {
+ messageId: props.messageId,
+ });
+
+ initializing.value = false;
+}
+
+onMounted(() => {
+ initialize();
+});
+
+definePage({
+ title: i18n.ts.chat,
+});
+</script>
diff --git a/packages/frontend/src/pages/chat/room.form.vue b/packages/frontend/src/pages/chat/room.form.vue
new file mode 100644
index 0000000000..aba9d6061f
--- /dev/null
+++ b/packages/frontend/src/pages/chat/room.form.vue
@@ -0,0 +1,333 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+ :class="$style.root"
+ @dragover.stop="onDragover"
+ @drop.stop="onDrop"
+>
+ <textarea
+ ref="textareaEl"
+ v-model="text"
+ :class="$style.textarea"
+ class="_acrylic"
+ :placeholder="i18n.ts.inputMessageHere"
+ :readonly="textareaReadOnly"
+ @keydown="onKeydown"
+ @paste="onPaste"
+ ></textarea>
+ <footer :class="$style.footer">
+ <div v-if="file" :class="$style.file" @click="file = null">{{ file.name }}</div>
+ <div :class="$style.buttons">
+ <button class="_button" :class="$style.button" @click="chooseFile"><i class="ti ti-photo-plus"></i></button>
+ <button class="_button" :class="$style.button" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
+ <button class="_button" :class="[$style.button, $style.send]" :disabled="!canSend || sending" :title="i18n.ts.send" @click="send">
+ <template v-if="!sending"><i class="ti ti-send"></i></template><template v-if="sending"><MkLoading :em="true"/></template>
+ </button>
+ </div>
+ </footer>
+ <input ref="fileEl" style="display: none;" type="file" @change="onChangeFile"/>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, watch, ref, shallowRef, computed, nextTick, readonly } from 'vue';
+import * as Misskey from 'misskey-js';
+//import insertTextAtCursor from 'insert-text-at-cursor';
+import { throttle } from 'throttle-debounce';
+import { formatTimeString } from '@/utility/format-time-string.js';
+import { selectFile } from '@/utility/select-file.js';
+import * as os from '@/os.js';
+import { useStream } from '@/stream.js';
+import { i18n } from '@/i18n.js';
+import { uploadFile } from '@/utility/upload.js';
+import { miLocalStorage } from '@/local-storage.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { prefer } from '@/preferences.js';
+import { Autocomplete } from '@/utility/autocomplete.js';
+import { emojiPicker } from '@/utility/emoji-picker.js';
+
+const props = defineProps<{
+ user?: Misskey.entities.UserDetailed | null;
+ room?: Misskey.entities.ChatRoom | null;
+}>();
+
+const textareaEl = shallowRef<HTMLTextAreaElement>();
+const fileEl = shallowRef<HTMLInputElement>();
+
+const text = ref<string>('');
+const file = ref<Misskey.entities.DriveFile | null>(null);
+const sending = ref(false);
+const textareaReadOnly = ref(false);
+
+const canSend = computed(() => (text.value != null && text.value !== '') || file.value != null);
+
+function getDraftKey() {
+ return props.user ? 'user:' + props.user.id : 'room:' + props.room?.id;
+}
+
+watch([text, file], saveDraft);
+
+async function onPaste(ev: ClipboardEvent) {
+ if (!ev.clipboardData) return;
+
+ const pastedFileName = 'yyyy-MM-dd HH-mm-ss [{{number}}]';
+
+ const clipboardData = ev.clipboardData;
+ const items = clipboardData.items;
+
+ if (items.length === 1) {
+ if (items[0].kind === 'file') {
+ const pastedFile = items[0].getAsFile();
+ if (!pastedFile) return;
+ const lio = pastedFile.name.lastIndexOf('.');
+ const ext = lio >= 0 ? pastedFile.name.slice(lio) : '';
+ const formatted = formatTimeString(new Date(pastedFile.lastModified), pastedFileName).replace(/{{number}}/g, '1') + ext;
+ if (formatted) upload(pastedFile, formatted);
+ }
+ } else {
+ if (items[0].kind === 'file') {
+ os.alert({
+ type: 'error',
+ text: i18n.ts.onlyOneFileCanBeAttached,
+ });
+ }
+ }
+}
+
+function onDragover(ev: DragEvent) {
+ if (!ev.dataTransfer) return;
+
+ const isFile = ev.dataTransfer.items[0].kind === 'file';
+ const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
+ if (isFile || isDriveFile) {
+ ev.preventDefault();
+ switch (ev.dataTransfer.effectAllowed) {
+ case 'all':
+ case 'uninitialized':
+ case 'copy':
+ case 'copyLink':
+ case 'copyMove':
+ ev.dataTransfer.dropEffect = 'copy';
+ break;
+ case 'linkMove':
+ case 'move':
+ ev.dataTransfer.dropEffect = 'move';
+ break;
+ default:
+ ev.dataTransfer.dropEffect = 'none';
+ break;
+ }
+ }
+}
+
+function onDrop(ev: DragEvent): void {
+ if (!ev.dataTransfer) return;
+
+ // ファイルだったら
+ if (ev.dataTransfer.files.length === 1) {
+ ev.preventDefault();
+ upload(ev.dataTransfer.files[0]);
+ return;
+ } else if (ev.dataTransfer.files.length > 1) {
+ ev.preventDefault();
+ os.alert({
+ type: 'error',
+ text: i18n.ts.onlyOneFileCanBeAttached,
+ });
+ return;
+ }
+
+ //#region ドライブのファイル
+ const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+ if (driveFile != null && driveFile !== '') {
+ file.value = JSON.parse(driveFile);
+ ev.preventDefault();
+ }
+ //#endregion
+}
+
+function onKeydown(ev: KeyboardEvent) {
+ if ((ev.key === 'Enter') && (ev.ctrlKey || ev.metaKey)) {
+ send();
+ }
+}
+
+function chooseFile(ev: MouseEvent) {
+ selectFile(ev.currentTarget ?? ev.target, i18n.ts.selectFile).then(selectedFile => {
+ file.value = selectedFile;
+ });
+}
+
+function onChangeFile() {
+ if (fileEl.value.files![0]) upload(fileEl.value.files[0]);
+}
+
+function upload(fileToUpload: File, name?: string) {
+ uploadFile(fileToUpload, prefer.s.uploadFolder, name).then(res => {
+ file.value = res;
+ });
+}
+
+function send() {
+ if (!canSend.value) return;
+
+ sending.value = true;
+
+ if (props.user) {
+ misskeyApi('chat/messages/create-to-user', {
+ toUserId: props.user.id,
+ text: text.value ? text.value : undefined,
+ fileId: file.value ? file.value.id : undefined,
+ }).then(message => {
+ clear();
+ }).catch(err => {
+ console.error(err);
+ }).then(() => {
+ sending.value = false;
+ });
+ } else if (props.room) {
+ misskeyApi('chat/messages/create-to-room', {
+ toRoomId: props.room.id,
+ text: text.value ? text.value : undefined,
+ fileId: file.value ? file.value.id : undefined,
+ }).then(message => {
+ clear();
+ }).catch(err => {
+ console.error(err);
+ }).then(() => {
+ sending.value = false;
+ });
+ }
+}
+
+function clear() {
+ text.value = '';
+ file.value = null;
+ deleteDraft();
+}
+
+function saveDraft() {
+ const drafts = JSON.parse(miLocalStorage.getItem('chatMessageDrafts') || '{}');
+
+ drafts[getDraftKey()] = {
+ updatedAt: new Date(),
+ data: {
+ text: text.value,
+ file: file.value,
+ },
+ };
+
+ miLocalStorage.setItem('chatMessageDrafts', JSON.stringify(drafts));
+}
+
+function deleteDraft() {
+ const drafts = JSON.parse(miLocalStorage.getItem('chatMessageDrafts') || '{}');
+
+ delete drafts[getDraftKey()];
+
+ miLocalStorage.setItem('chatMessageDrafts', JSON.stringify(drafts));
+}
+
+async function insertEmoji(ev: MouseEvent) {
+ textareaReadOnly.value = true;
+ const target = ev.currentTarget ?? ev.target;
+ if (target == null) return;
+
+ // emojiPickerはダイアログが閉じずにtextareaとやりとりするので、
+ // focustrapをかけているとinsertTextAtCursorが効かない
+ // そのため、投稿フォームのテキストに直接注入する
+ // See: https://github.com/misskey-dev/misskey/pull/14282
+ // https://github.com/misskey-dev/misskey/issues/14274
+
+ let pos = textareaEl.value?.selectionStart ?? 0;
+ let posEnd = textareaEl.value?.selectionEnd ?? text.value.length;
+ emojiPicker.show(
+ target as HTMLElement,
+ emoji => {
+ const textBefore = text.value.substring(0, pos);
+ const textAfter = text.value.substring(posEnd);
+ text.value = textBefore + emoji + textAfter;
+ pos += emoji.length;
+ posEnd += emoji.length;
+ },
+ () => {
+ textareaReadOnly.value = false;
+ nextTick(() => focus());
+ },
+ );
+}
+
+onMounted(() => {
+ // TODO: detach when unmount
+ new Autocomplete(textareaEl.value, text);
+
+ // 書きかけの投稿を復元
+ const draft = JSON.parse(miLocalStorage.getItem('chatMessageDrafts') || '{}')[getDraftKey()];
+ if (draft) {
+ text.value = draft.data.text;
+ file.value = draft.data.file;
+ }
+});
+</script>
+
+<style lang="scss" module>
+.root {
+ position: relative;
+ border-bottom: none;
+ border-radius: 14px 14px 0 0;
+ overflow: clip;
+}
+
+.textarea {
+ cursor: auto;
+ display: block;
+ width: 100%;
+ min-width: 100%;
+ max-width: 100%;
+ min-height: 80px;
+ margin: 0;
+ padding: 16px 16px 0 16px;
+ resize: none;
+ font-size: 1em;
+ font-family: inherit;
+ outline: none;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ box-sizing: border-box;
+ color: var(--MI_THEME-fg);
+ field-sizing: content;
+}
+
+.footer {
+ position: sticky;
+ bottom: 0;
+ background: var(--MI_THEME-panel);
+}
+
+.file {
+ padding: 8px;
+ cursor: pointer;
+}
+
+.buttons {
+ display: flex;
+}
+
+.button {
+ height: 50px;
+ aspect-ratio: 1;
+
+ &:hover {
+ color: var(--MI_THEME-accent);
+ }
+}
+.send {
+ margin-left: auto;
+ color: var(--MI_THEME-accent);
+}
+</style>
diff --git a/packages/frontend/src/pages/chat/room.info.vue b/packages/frontend/src/pages/chat/room.info.vue
new file mode 100644
index 0000000000..7d38d07b3a
--- /dev/null
+++ b/packages/frontend/src/pages/chat/room.info.vue
@@ -0,0 +1,87 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+ <MkInput v-model="name_" :disabled="!isOwner">
+ <template #label>{{ i18n.ts.name }}</template>
+ </MkInput>
+
+ <MkTextarea v-model="description_" :disabled="!isOwner">
+ <template #label>{{ i18n.ts.description }}</template>
+ </MkTextarea>
+
+ <MkButton v-if="isOwner" primary @click="save">{{ i18n.ts.save }}</MkButton>
+
+ <hr>
+
+ <MkSwitch v-if="!isOwner" v-model="isMuted">
+ <template #label>{{ i18n.ts._chat.muteThisRoom }}</template>
+ </MkSwitch>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref, watch } from 'vue';
+import * as Misskey from 'misskey-js';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import * as os from '@/os.js';
+import { ensureSignin } from '@/i.js';
+import MkInput from '@/components/MkInput.vue';
+import MkTextarea from '@/components/MkTextarea.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+
+const $i = ensureSignin();
+
+const props = defineProps<{
+ room: Misskey.entities.ChatRoom;
+}>();
+
+const isOwner = computed(() => {
+ return props.room.ownerId === $i.id;
+});
+
+const name_ = ref(props.room.name);
+const description_ = ref(props.room.description);
+
+function save() {
+ os.apiWithDialog('chat/rooms/update', {
+ roomId: props.room.id,
+ name: name_.value,
+ description: description_.value,
+ });
+}
+
+const isMuted = ref(props.room.isMuted);
+
+watch(isMuted, async () => {
+ await os.apiWithDialog('chat/rooms/mute', {
+ roomId: props.room.id,
+ mute: isMuted.value,
+ });
+});
+
+onMounted(async () => {
+
+});
+</script>
+
+<style lang="scss" module>
+.membership {
+ display: flex;
+}
+
+.membershipBody {
+ flex: 1;
+ min-width: 0;
+ margin-right: 8px;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+</style>
diff --git a/packages/frontend/src/pages/chat/room.members.vue b/packages/frontend/src/pages/chat/room.members.vue
new file mode 100644
index 0000000000..d20216a81c
--- /dev/null
+++ b/packages/frontend/src/pages/chat/room.members.vue
@@ -0,0 +1,73 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+ <MkButton v-if="isOwner" primary rounded style="margin: 0 auto;" @click="emit('inviteUser')"><i class="ti ti-plus"></i> {{ i18n.ts._chat.inviteUser }}</MkButton>
+
+ <MkA :class="$style.membershipBody" :to="`${userPage(room.owner)}`">
+ <MkUserCardMini :user="room.owner"/>
+ </MkA>
+
+ <hr>
+
+ <div v-for="membership in memberships" :key="membership.id" :class="$style.membership">
+ <MkA :class="$style.membershipBody" :to="`${userPage(membership.user)}`">
+ <MkUserCardMini :user="membership.user"/>
+ </MkA>
+ </div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import * as os from '@/os.js';
+import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import { userPage } from '@/filters/user.js';
+import { ensureSignin } from '@/i.js';
+
+const $i = ensureSignin();
+
+const props = defineProps<{
+ room: Misskey.entities.ChatRoom;
+}>();
+
+const emit = defineEmits<{
+ (ev: 'inviteUser'): void,
+}>();
+
+const isOwner = computed(() => {
+ return props.room.ownerId === $i.id;
+});
+
+const memberships = ref<Misskey.entities.ChatRoomMembership[]>([]);
+
+onMounted(async () => {
+ memberships.value = await misskeyApi('chat/rooms/members', {
+ roomId: props.room.id,
+ limit: 50,
+ });
+});
+</script>
+
+<style lang="scss" module>
+.membership {
+ display: flex;
+}
+
+.membershipBody {
+ flex: 1;
+ min-width: 0;
+ margin-right: 8px;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+</style>
diff --git a/packages/frontend/src/pages/chat/room.search.vue b/packages/frontend/src/pages/chat/room.search.vue
new file mode 100644
index 0000000000..de5e7156ca
--- /dev/null
+++ b/packages/frontend/src/pages/chat/room.search.vue
@@ -0,0 +1,68 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+ <MkInput
+ v-model="searchQuery"
+ :placeholder="i18n.ts._chat.searchMessages"
+ type="search"
+ >
+ <template #prefix><i class="ti ti-search"></i></template>
+ </MkInput>
+
+ <MkButton v-if="searchQuery.length > 0" primary rounded @click="search">{{ i18n.ts.search }}</MkButton>
+
+ <MkFoldableSection v-if="searched">
+ <template #header>{{ i18n.ts.searchResult }}</template>
+
+ <div class="_gaps_s">
+ <div v-for="message in searchResults" :key="message.id" :class="$style.searchResultItem">
+ <XMessage :message="message" :user="message.fromUser" :isSearchResult="true"/>
+ </div>
+ </div>
+ </MkFoldableSection>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import XMessage from './XMessage.vue';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import * as os from '@/os.js';
+import MkInput from '@/components/MkInput.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
+
+const props = defineProps<{
+ userId?: string;
+ roomId?: string;
+}>();
+
+const searchQuery = ref('');
+const searched = ref(false);
+const searchResults = ref<Misskey.entities.ChatMessage[]>([]);
+
+async function search() {
+ const res = await misskeyApi('chat/messages/search', {
+ query: searchQuery.value,
+ roomId: props.roomId,
+ userId: props.userId,
+ });
+
+ searchResults.value = res;
+ searched.value = true;
+}
+</script>
+
+<style lang="scss" module>
+.searchResultItem {
+ padding: 12px;
+ border: solid 1px var(--MI_THEME-divider);
+ border-radius: 12px;
+}
+</style>
diff --git a/packages/frontend/src/pages/chat/room.vue b/packages/frontend/src/pages/chat/room.vue
new file mode 100644
index 0000000000..15e9f43db2
--- /dev/null
+++ b/packages/frontend/src/pages/chat/room.vue
@@ -0,0 +1,426 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<PageWithHeader v-model:tab="tab" :reversed="tab === 'chat'" :tabs="headerTabs" :actions="headerActions">
+ <MkSpacer v-if="tab === 'chat'" :contentMax="700">
+ <div v-if="initializing">
+ <MkLoading/>
+ </div>
+
+ <div v-else-if="messages.length === 0">
+ <div class="_gaps" style="text-align: center;">
+ <div>{{ i18n.ts._chat.noMessagesYet }}</div>
+ <template v-if="user">
+ <div v-if="user.chatScope === 'followers'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromFollowers }}</div>
+ <div v-else-if="user.chatScope === 'following'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromFollowing }}</div>
+ <div v-else-if="user.chatScope === 'mutual'">{{ i18n.ts._chat.thisUserAllowsChatOnlyFromMutualFollowing }}</div>
+ <div v-else>{{ i18n.ts._chat.thisUserNotAllowedChatAnyone }}</div>
+ </template>
+ <template v-else-if="room">
+ <div>{{ i18n.ts._chat.inviteUserToChat }}</div>
+ </template>
+ </div>
+ </div>
+
+ <div v-else class="_gaps">
+ <div v-if="canFetchMore">
+ <MkButton :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMore">{{ i18n.ts.loadMore }}</MkButton>
+ </div>
+
+ <TransitionGroup
+ :enterActiveClass="prefer.s.animation ? $style.transition_x_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_x_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_x_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_x_leaveTo : ''"
+ :moveClass="prefer.s.animation ? $style.transition_x_move : ''"
+ tag="div" class="_gaps"
+ >
+ <XMessage v-for="message in messages.toReversed()" :key="message.id" :message="message"/>
+ </TransitionGroup>
+ </div>
+ </MkSpacer>
+
+ <MkSpacer v-else-if="tab === 'search'" :contentMax="700">
+ <XSearch :userId="userId" :roomId="roomId"/>
+ </MkSpacer>
+
+ <MkSpacer v-else-if="tab === 'members'" :contentMax="700">
+ <XMembers v-if="room != null" :room="room" @inviteUser="inviteUser"/>
+ </MkSpacer>
+
+ <MkSpacer v-else-if="tab === 'info'" :contentMax="700">
+ <XInfo v-if="room != null" :room="room"/>
+ </MkSpacer>
+
+ <template #footer>
+ <div v-if="tab === 'chat'" :class="$style.footer">
+ <div class="_gaps">
+ <Transition name="fade">
+ <div v-show="showIndicator" :class="$style.new">
+ <button class="_buttonPrimary" :class="$style.newButton" @click="onIndicatorClick">
+ <i class="fas ti-fw fa-arrow-circle-down" :class="$style.newIcon"></i>{{ i18n.ts.newMessageExists }}
+ </button>
+ </div>
+ </Transition>
+ <XForm v-if="!initializing" :user="user" :room="room" :class="$style.form"/>
+ </div>
+ </div>
+ </template>
+</PageWithHeader>
+</template>
+
+<script lang="ts" setup>
+import { ref, useTemplateRef, computed, watch, onMounted, nextTick, onBeforeUnmount, onDeactivated, onActivated } from 'vue';
+import * as Misskey from 'misskey-js';
+import { isTailVisible } from '@@/js/scroll.js';
+import XMessage from './XMessage.vue';
+import XForm from './room.form.vue';
+import XSearch from './room.search.vue';
+import XMembers from './room.members.vue';
+import XInfo from './room.info.vue';
+import type { MenuItem } from '@/types/menu.js';
+import * as os from '@/os.js';
+import { useStream } from '@/stream.js';
+import * as sound from '@/utility/sound.js';
+import { i18n } from '@/i18n.js';
+import { ensureSignin } from '@/i.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
+import { prefer } from '@/preferences.js';
+import MkButton from '@/components/MkButton.vue';
+import { useRouter } from '@/router.js';
+
+const $i = ensureSignin();
+const router = useRouter();
+
+const props = defineProps<{
+ userId?: string;
+ roomId?: string;
+}>();
+
+const initializing = ref(true);
+const moreFetching = ref(false);
+const messages = ref<Misskey.entities.ChatMessage[]>([]);
+const canFetchMore = ref(false);
+const user = ref<Misskey.entities.UserDetailed | null>(null);
+const room = ref<Misskey.entities.ChatRoom | null>(null);
+const connection = ref<Misskey.ChannelConnection<Misskey.Channels['chatUser'] | Misskey.Channels['chatRoom']> | null>(null);
+const showIndicator = ref(false);
+
+function normalizeMessage(message: Misskey.entities.ChatMessageLite | Misskey.entities.ChatMessage) {
+ const reactions = [...message.reactions];
+ for (const record of reactions) {
+ if (room.value == null && record.user == null) { // 1on1の時はuserは省略される
+ record.user = message.fromUserId === $i.id ? user.value : $i;
+ }
+ }
+
+ return {
+ ...message,
+ fromUser: message.fromUser ?? (message.fromUserId === $i.id ? $i : user),
+ reactions,
+ };
+}
+
+async function initialize() {
+ const LIMIT = 20;
+
+ initializing.value = true;
+
+ if (props.userId) {
+ const [u, m] = await Promise.all([
+ misskeyApi('users/show', { userId: props.userId }),
+ misskeyApi('chat/messages/user-timeline', { userId: props.userId, limit: LIMIT }),
+ ]);
+
+ user.value = u;
+ messages.value = m.map(x => normalizeMessage(x));
+
+ if (messages.value.length === LIMIT) {
+ canFetchMore.value = true;
+ }
+
+ connection.value = useStream().useChannel('chatUser', {
+ otherId: user.value.id,
+ });
+ connection.value.on('message', onMessage);
+ connection.value.on('deleted', onDeleted);
+ connection.value.on('react', onReact);
+ } else {
+ const [r, m] = await Promise.all([
+ misskeyApi('chat/rooms/show', { roomId: props.roomId }),
+ misskeyApi('chat/messages/room-timeline', { roomId: props.roomId, limit: LIMIT }),
+ ]);
+
+ room.value = r;
+ messages.value = m.map(x => normalizeMessage(x));
+
+ if (messages.value.length === LIMIT) {
+ canFetchMore.value = true;
+ }
+
+ connection.value = useStream().useChannel('chatRoom', {
+ roomId: room.value.id,
+ });
+ connection.value.on('message', onMessage);
+ connection.value.on('deleted', onDeleted);
+ connection.value.on('react', onReact);
+ }
+
+ window.document.addEventListener('visibilitychange', onVisibilitychange);
+
+ initializing.value = false;
+}
+
+let isActivated = true;
+
+onActivated(() => {
+ isActivated = true;
+});
+
+onDeactivated(() => {
+ isActivated = false;
+});
+
+async function fetchMore() {
+ const LIMIT = 30;
+
+ moreFetching.value = true;
+
+ const newMessages = props.userId ? await misskeyApi('chat/messages/user-timeline', {
+ userId: user.value.id,
+ limit: LIMIT,
+ untilId: messages.value[messages.value.length - 1].id,
+ }) : await misskeyApi('chat/messages/room-timeline', {
+ roomId: room.value.id,
+ limit: LIMIT,
+ untilId: messages.value[messages.value.length - 1].id,
+ });
+
+ messages.value.push(...newMessages.map(x => normalizeMessage(x)));
+
+ canFetchMore.value = newMessages.length === LIMIT;
+ moreFetching.value = false;
+}
+
+function onMessage(message: Misskey.entities.ChatMessage) {
+ sound.playMisskeySfx('chatMessage');
+
+ messages.value.unshift(normalizeMessage(message));
+
+ // TODO: DOM的にバックグラウンドになっていないかどうかも考慮する
+ if (message.fromUserId !== $i.id && !window.document.hidden && isActivated) {
+ connection.value?.send('read', {
+ id: message.id,
+ });
+ }
+
+ if (message.fromUserId !== $i.id) {
+ //notifyNewMessage();
+ }
+}
+
+function onDeleted(id) {
+ const index = messages.value.findIndex(m => m.id === id);
+ if (index !== -1) {
+ messages.value.splice(index, 1);
+ }
+}
+
+function onReact(ctx) {
+ const message = messages.value.find(m => m.id === ctx.messageId);
+ if (message) {
+ if (room.value == null) { // 1on1の時はuserは省略される
+ message.reactions.push({
+ reaction: ctx.reaction,
+ user: message.fromUserId === $i.id ? user : $i,
+ });
+ } else {
+ message.reactions.push({
+ reaction: ctx.reaction,
+ user: ctx.user,
+ });
+ }
+ }
+}
+
+function onIndicatorClick() {
+ showIndicator.value = false;
+}
+
+function notifyNewMessage() {
+ showIndicator.value = true;
+}
+
+function onVisibilitychange() {
+ if (window.document.hidden) return;
+ // TODO
+}
+
+onMounted(() => {
+ initialize();
+});
+
+onBeforeUnmount(() => {
+ connection.value?.dispose();
+ window.document.removeEventListener('visibilitychange', onVisibilitychange);
+});
+
+async function inviteUser() {
+ const invitee = await os.selectUser({ includeSelf: false, localOnly: true });
+ os.apiWithDialog('chat/rooms/invitations/create', {
+ roomId: room.value?.id,
+ userId: invitee.id,
+ });
+}
+
+async function leaveRoom() {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ text: i18n.ts.areYouSure,
+ });
+ if (canceled) return;
+
+ misskeyApi('chat/rooms/leave', {
+ roomId: room.value?.id,
+ });
+ router.push('/chat');
+}
+
+function showMenu(ev: MouseEvent) {
+ const menuItems: MenuItem[] = [];
+
+ if (room.value) {
+ if (room.value.ownerId === $i.id) {
+ menuItems.push({
+ text: i18n.ts._chat.inviteUser,
+ icon: 'ti ti-user-plus',
+ action: () => {
+ inviteUser();
+ },
+ });
+ } else {
+ menuItems.push({
+ text: i18n.ts._chat.leave,
+ icon: 'ti ti-x',
+ action: () => {
+ leaveRoom();
+ },
+ });
+ }
+ }
+
+ os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
+}
+
+const tab = ref('chat');
+
+const headerTabs = computed(() => room.value ? [{
+ key: 'chat',
+ title: i18n.ts.chat,
+ icon: 'ti ti-messages',
+}, {
+ key: 'members',
+ title: i18n.ts._chat.members,
+ icon: 'ti ti-users',
+}, {
+ key: 'search',
+ title: i18n.ts.search,
+ icon: 'ti ti-search',
+}, {
+ key: 'info',
+ title: i18n.ts.info,
+ icon: 'ti ti-info-circle',
+}] : [{
+ key: 'chat',
+ title: i18n.ts.chat,
+ icon: 'ti ti-messages',
+}, {
+ key: 'search',
+ title: i18n.ts.search,
+ icon: 'ti ti-search',
+}]);
+
+const headerActions = computed(() => [{
+ icon: 'ti ti-dots',
+ handler: showMenu,
+}]);
+
+definePage(computed(() => !initializing.value ? user.value ? {
+ userName: user,
+ avatar: user,
+} : {
+ title: room.value?.name,
+ icon: 'ti ti-users',
+} : null));
+</script>
+
+<style lang="scss" module>
+.transition_x_move,
+.transition_x_enterActive,
+.transition_x_leaveActive {
+ transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important;
+}
+.transition_x_enterFrom,
+.transition_x_leaveTo {
+ opacity: 0;
+ transform: translateY(80px);
+}
+.transition_x_leaveActive {
+ position: absolute;
+}
+
+.root {
+}
+
+.more {
+ margin: 0 auto;
+}
+
+.footer {
+ width: 100%;
+ padding-top: 8px;
+}
+
+.new {
+ width: 100%;
+ padding-bottom: 8px;
+ text-align: center;
+}
+
+.newButton {
+ display: inline-block;
+ margin: 0;
+ padding: 0 12px;
+ line-height: 32px;
+ font-size: 12px;
+ border-radius: 16px;
+}
+
+.newIcon {
+ display: inline-block;
+ margin-right: 8px;
+}
+
+.footer {
+
+}
+
+.form {
+ margin: 0 auto;
+ width: 100%;
+ max-width: 700px;
+}
+
+.fade-enter-active, .fade-leave-active {
+ transition: opacity 0.1s;
+}
+
+.fade-enter-from, .fade-leave-to {
+ transition: opacity 0.5s;
+ opacity: 0;
+}
+</style>
diff --git a/packages/frontend/src/pages/clicker.vue b/packages/frontend/src/pages/clicker.vue
index 9e9b5e8688..479204f39b 100644
--- a/packages/frontend/src/pages/clicker.vue
+++ b/packages/frontend/src/pages/clicker.vue
@@ -4,19 +4,18 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
+<PageWithHeader>
<MkSpacer :contentMax="800">
<MkClickerGame/>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import MkClickerGame from '@/components/MkClickerGame.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
-definePageMetadata(() => ({
+definePage(() => ({
title: '🍪👈',
icon: 'ti ti-cookie',
}));
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 240f395e04..041364d4fc 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions"/></template>
+<PageWithHeader :actions="headerActions">
<MkSpacer :contentMax="800">
<div v-if="clip" class="_gaps">
<div class="_panel">
@@ -27,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkNotes :pagination="pagination" :detail="true"/>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -36,16 +35,16 @@ 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 { $i } from '@/i.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 { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.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 { isSupportShare } from '@/utility/navigator.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { genEmbedCode } from '@/utility/get-embed-code.js';
import { assertServerContext, serverContext } from '@/server-context.js';
// contextは非ログイン状態の情報しかないためログイン時は利用できない
@@ -148,7 +147,6 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
text: i18n.ts.copyUrl,
action: () => {
copyToClipboard(`${url}/clips/${clip.value!.id}`);
- os.success();
},
}, {
icon: 'ti ti-code',
@@ -193,7 +191,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
},
}] : null);
-definePageMetadata(() => ({
+definePage(() => ({
title: clip.value ? clip.value.name : i18n.ts.clip,
icon: 'ti ti-paperclip',
}));
diff --git a/packages/frontend/src/pages/contact.vue b/packages/frontend/src/pages/contact.vue
index 1f2bee5a77..39d70cafc7 100644
--- a/packages/frontend/src/pages/contact.vue
+++ b/packages/frontend/src/pages/contact.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
+<PageWithHeader>
<MkSpacer :contentMax="600" :marginMin="20">
<div class="_gaps_m">
<MkKeyValue :copy="instance.maintainerName">
@@ -31,17 +30,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkKeyValue>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkLink from '@/components/MkLink.vue';
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.inquiry,
icon: 'ti ti-help-circle',
}));
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index 107a0d760c..7205cca42f 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -4,91 +4,88 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div>
- <MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
- <MkSpacer :contentMax="900">
- <div class="ogwlenmc">
- <div v-if="tab === 'local'" class="local">
- <MkInput v-model="query" :debounce="true" type="search" autocapitalize="off">
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
+ <MkSpacer :contentMax="900">
+ <div class="ogwlenmc">
+ <div v-if="tab === 'local'" class="local">
+ <MkInput v-model="query" :debounce="true" type="search" autocapitalize="off">
+ <template #prefix><i class="ti ti-search"></i></template>
+ <template #label>{{ i18n.ts.search }}</template>
+ </MkInput>
+ <MkSwitch v-model="selectMode" style="margin: 8px 0;">
+ <template #label>Select mode</template>
+ </MkSwitch>
+ <div v-if="selectMode" class="_buttons">
+ <MkButton inline @click="selectAll">Select all</MkButton>
+ <MkButton inline @click="setCategoryBulk">Set category</MkButton>
+ <MkButton inline @click="setTagBulk">Set tag</MkButton>
+ <MkButton inline @click="addTagBulk">Add tag</MkButton>
+ <MkButton inline @click="removeTagBulk">Remove tag</MkButton>
+ <MkButton inline @click="setLicenseBulk">Set License</MkButton>
+ <MkButton inline danger @click="delBulk">Delete</MkButton>
+ </div>
+ <MkPagination ref="emojisPaginationComponent" :pagination="pagination" :displayLimit="50">
+ <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
+ <template #default="{items}">
+ <div class="ldhfsamy">
+ <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)">
+ <img :src="emoji.url" class="img" :alt="emoji.name"/>
+ <div class="body">
+ <div class="name _monospace">{{ emoji.name }}</div>
+ <div class="info">{{ emoji.category }}</div>
+ </div>
+ </button>
+ </div>
+ </template>
+ </MkPagination>
+ </div>
+
+ <div v-else-if="tab === 'remote'" class="remote">
+ <FormSplit>
+ <MkInput v-model="queryRemote" :debounce="true" type="search" autocapitalize="off">
<template #prefix><i class="ti ti-search"></i></template>
<template #label>{{ i18n.ts.search }}</template>
</MkInput>
- <MkSwitch v-model="selectMode" style="margin: 8px 0;">
- <template #label>Select mode</template>
- </MkSwitch>
- <div v-if="selectMode" class="_buttons">
- <MkButton inline @click="selectAll">Select all</MkButton>
- <MkButton inline @click="setCategoryBulk">Set category</MkButton>
- <MkButton inline @click="setTagBulk">Set tag</MkButton>
- <MkButton inline @click="addTagBulk">Add tag</MkButton>
- <MkButton inline @click="removeTagBulk">Remove tag</MkButton>
- <MkButton inline @click="setLicenseBulk">Set License</MkButton>
- <MkButton inline danger @click="delBulk">Delete</MkButton>
- </div>
- <MkPagination ref="emojisPaginationComponent" :pagination="pagination" :displayLimit="50">
- <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
- <template #default="{items}">
- <div class="ldhfsamy">
- <button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)">
- <img :src="emoji.url" class="img" :alt="emoji.name"/>
- <div class="body">
- <div class="name _monospace">{{ emoji.name }}</div>
- <div class="info">{{ emoji.category }}</div>
- </div>
- </button>
- </div>
- </template>
- </MkPagination>
- </div>
-
- <div v-else-if="tab === 'remote'" class="remote">
- <FormSplit>
- <MkInput v-model="queryRemote" :debounce="true" type="search" autocapitalize="off">
- <template #prefix><i class="ti ti-search"></i></template>
- <template #label>{{ i18n.ts.search }}</template>
- </MkInput>
- <MkInput v-model="host" :debounce="true">
- <template #label>{{ i18n.ts.host }}</template>
- </MkInput>
- </FormSplit>
- <MkPagination :pagination="remotePagination" :displayLimit="50">
- <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
- <template #default="{items}">
- <div class="ldhfsamy">
- <div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)">
- <img :src="getProxiedImageUrl(emoji.url, 'emoji')" class="img" :alt="emoji.name"/>
- <div class="body">
- <div class="name _monospace">{{ emoji.name }}</div>
- <div class="info">{{ emoji.host }}</div>
- </div>
+ <MkInput v-model="host" :debounce="true">
+ <template #label>{{ i18n.ts.host }}</template>
+ </MkInput>
+ </FormSplit>
+ <MkPagination :pagination="remotePagination" :displayLimit="50">
+ <template #empty><span>{{ i18n.ts.noCustomEmojis }}</span></template>
+ <template #default="{items}">
+ <div class="ldhfsamy">
+ <div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)">
+ <img :src="getProxiedImageUrl(emoji.url, 'emoji')" class="img" :alt="emoji.name"/>
+ <div class="body">
+ <div class="name _monospace">{{ emoji.name }}</div>
+ <div class="info">{{ emoji.host }}</div>
</div>
</div>
- </template>
- </MkPagination>
- </div>
+ </div>
+ </template>
+ </MkPagination>
</div>
- </MkSpacer>
- </MkStickyContainer>
-</div>
+ </div>
+ </MkSpacer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { computed, defineAsyncComponent, ref, shallowRef } from 'vue';
+import { computed, defineAsyncComponent, ref, useTemplateRef } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSplit from '@/components/form/split.vue';
-import { selectFile } from '@/scripts/select-file.js';
+import { selectFile } from '@/utility/select-file.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { getProxiedImageUrl } from '@/utility/media-proxy.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
-const emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const emojisPaginationComponent = useTemplateRef('emojisPaginationComponent');
const tab = ref('local');
const query = ref<string | null>(null);
@@ -337,7 +334,7 @@ const headerTabs = computed(() => [{
title: i18n.ts.remote,
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.customEmojis,
icon: 'ph-smiley ph-bold ph-lg',
}));
diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue
index 8706dc7047..c079b9030d 100644
--- a/packages/frontend/src/pages/drive.file.info.vue
+++ b/packages/frontend/src/pages/drive.file.info.vue
@@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
<div v-else class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</div>
@@ -85,8 +85,8 @@ import bytes from '@/filters/bytes.js';
import { infoImageUrl } from '@/instance.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useRouter } from '@/router/supplier.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/drive.file.notes.vue b/packages/frontend/src/pages/drive.file.notes.vue
index ca63d43747..d7519896cc 100644
--- a/packages/frontend/src/pages/drive.file.notes.vue
+++ b/packages/frontend/src/pages/drive.file.notes.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { i18n } from '@/i18n.js';
-import { Paging } from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkNotes from '@/components/MkNotes.vue';
diff --git a/packages/frontend/src/pages/drive.file.vue b/packages/frontend/src/pages/drive.file.vue
index 5711ec8b3a..3063d5a4d6 100644
--- a/packages/frontend/src/pages/drive.file.vue
+++ b/packages/frontend/src/pages/drive.file.vue
@@ -10,11 +10,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <MkSpacer v-if="tab === 'info'" key="info" :contentMax="800">
+ <MkSpacer v-if="tab === 'info'" :contentMax="800">
<XFileInfo :fileId="fileId"/>
</MkSpacer>
- <MkSpacer v-else-if="tab === 'notes'" key="notes" :contentMax="800">
+ <MkSpacer v-else-if="tab === 'notes'" :contentMax="800">
<XNotes :fileId="fileId"/>
</MkSpacer>
</MkHorizontalSwipe>
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, defineAsyncComponent } from 'vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
const props = defineProps<{
@@ -48,7 +48,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-pencil',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts._fileViewer.title,
icon: 'ti ti-file',
}));
diff --git a/packages/frontend/src/pages/drive.vue b/packages/frontend/src/pages/drive.vue
index 25e140f67f..c5813a4523 100644
--- a/packages/frontend/src/pages/drive.vue
+++ b/packages/frontend/src/pages/drive.vue
@@ -14,7 +14,7 @@ import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
import XDrive from '@/components/MkDrive.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const folder = ref<Misskey.entities.DriveFolder | null>(null);
@@ -22,7 +22,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: folder.value ? folder.value.name : i18n.ts.drive,
icon: 'ti ti-cloud',
hideHeader: true,
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 8d369101af..eee174a6af 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
- <img v-if="defaultStore.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
+ <img v-if="store.s.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/>
<canvas ref="canvasEl" :class="$style.canvas"/>
<Transition
@@ -191,26 +191,28 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch, useTemplateRef } from 'vue';
import * as Matter from 'matter-js';
import * as Misskey from 'misskey-js';
-import { DropAndFusionGame, Mono } from 'misskey-bubble-game';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { DropAndFusionGame } from 'misskey-bubble-game';
+import { useInterval } from '@@/js/use-interval.js';
+import { apiUrl } from '@@/js/config.js';
+import type { Mono } from 'misskey-bubble-game';
+import { definePage } from '@/page.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import * as os from '@/os.js';
import MkNumber from '@/components/MkNumber.vue';
import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
import MkButton from '@/components/MkButton.vue';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { defaultStore } from '@/store.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { store } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { useInterval } from '@@/js/use-interval.js';
-import { apiUrl } from '@@/js/config.js';
-import { $i } from '@/account.js';
-import * as sound from '@/scripts/sound.js';
+import { $i } from '@/i.js';
+import * as sound from '@/utility/sound.js';
import MkRange from '@/components/MkRange.vue';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { prefer } from '@/preferences.js';
type FrontendMonoDefinition = {
id: string;
@@ -565,8 +567,8 @@ let game = new DropAndFusionGame({
});
attachGameEvents();
-const containerEl = shallowRef<HTMLElement>();
-const canvasEl = shallowRef<HTMLCanvasElement>();
+const containerEl = useTemplateRef('containerEl');
+const canvasEl = useTemplateRef('canvasEl');
const dropperX = ref(0);
const currentPick = shallowRef<{ id: string; mono: Mono } | null>(null);
const stock = shallowRef<{ id: string; mono: Mono }[]>([]);
@@ -585,8 +587,8 @@ const showConfig = ref(false);
const replaying = ref(false);
const replayPlaybackRate = ref(1);
const currentFrame = ref(0);
-const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
-const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
+const bgmVolume = ref(prefer.s['game.dropAndFusion'].bgmVolume);
+const sfxVolume = ref(prefer.s['game.dropAndFusion'].sfxVolume);
watch(replayPlaybackRate, (newValue) => {
game.replayPlaybackRate = newValue;
@@ -622,7 +624,7 @@ function loadMonoTextures() {
if (renderer.textures[mono.img]) return;
let src = mono.img;
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+
if (monoTextureUrls[mono.img]) {
src = monoTextureUrls[mono.img];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -630,7 +632,7 @@ function loadMonoTextures() {
src = URL.createObjectURL(monoTextures[mono.img]);
monoTextureUrls[mono.img] = src;
} else {
- const res = await fetch(mono.img);
+ const res = await window.fetch(mono.img);
const blob = await res.blob();
monoTextures[mono.img] = blob;
src = URL.createObjectURL(blob);
@@ -648,7 +650,6 @@ function loadMonoTextures() {
function getTextureImageUrl(mono: Mono) {
const def = monoDefinitions.value.find(x => x.id === mono.id)!;
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (monoTextureUrls[def.img]) {
return monoTextureUrls[def.img];
@@ -849,17 +850,16 @@ function exportLog() {
l: DropAndFusionGame.serializeLogs(logs),
});
copyToClipboard(data);
- os.success();
}
function updateSettings<
- K extends keyof typeof defaultStore.state.dropAndFusion,
- V extends typeof defaultStore.state.dropAndFusion[K],
+ K extends keyof typeof prefer.s['game.dropAndFusion'],
+ V extends typeof prefer.s['game.dropAndFusion'][K],
>(key: K, value: V) {
const changes: { [P in K]?: V } = {};
changes[key] = value;
- defaultStore.set('dropAndFusion', {
- ...defaultStore.state.dropAndFusion,
+ prefer.commit('game.dropAndFusion', {
+ ...prefer.s['game.dropAndFusion'],
...changes,
});
}
@@ -876,7 +876,7 @@ function loadImage(url: string) {
function getGameImageDriveFile() {
return new Promise<Misskey.entities.DriveFile | null>(res => {
- const dcanvas = document.createElement('canvas');
+ const dcanvas = window.document.createElement('canvas');
dcanvas.width = game.GAME_WIDTH;
dcanvas.height = game.GAME_HEIGHT;
const ctx = dcanvas.getContext('2d');
@@ -909,8 +909,8 @@ function getGameImageDriveFile() {
formData.append('name', `bubble-game-${Date.now()}.png`);
formData.append('isSensitive', 'false');
formData.append('i', $i.token);
- if (defaultStore.state.uploadFolder) {
- formData.append('folderId', defaultStore.state.uploadFolder);
+ if (prefer.s.uploadFolder) {
+ formData.append('folderId', prefer.s.uploadFolder);
}
window.fetch(apiUrl + '/drive/files/create', {
@@ -1229,7 +1229,7 @@ onDeactivated(() => {
bgmNodes?.soundSource.stop();
});
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.bubbleGame,
icon: 'ti ti-apple',
}));
diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue
index 54352c9b0d..7f571a7c36 100644
--- a/packages/frontend/src/pages/drop-and-fusion.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.vue
@@ -88,12 +88,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import XGame from './drop-and-fusion.game.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
const gameMode = ref<'normal' | 'square' | 'yen' | 'sweets' | 'space'>('normal');
const gameStarted = ref(false);
@@ -121,7 +121,7 @@ function onGameEnd() {
gameStarted.value = false;
}
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.bubbleGame,
icon: 'ti ti-device-gamepad',
}));
diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue
index c8e6dfb05a..f9d3197ece 100644
--- a/packages/frontend/src/pages/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/emoji-edit-dialog.vue
@@ -87,11 +87,11 @@ 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 { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { customEmojiCategories } from '@/custom-emojis.js';
import MkSwitch from '@/components/MkSwitch.vue';
-import { selectFile } from '@/scripts/select-file.js';
+import { selectFile } from '@/utility/select-file.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
const props = defineProps<{
diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue
index 594a8eda0e..773d65d270 100644
--- a/packages/frontend/src/pages/emojis.emoji.vue
+++ b/packages/frontend/src/pages/emojis.emoji.vue
@@ -18,14 +18,14 @@ 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 { misskeyApiGet } from '@/utility/misskey-api.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
const props = defineProps<{
- emoji: Misskey.entities.EmojiSimple;
+ emoji: Misskey.entities.EmojiSimple;
}>();
function menu(ev) {
@@ -38,7 +38,6 @@ function menu(ev) {
icon: 'ti ti-copy',
action: () => {
copyToClipboard(`:${props.emoji.name}:`);
- os.success();
},
}, {
text: i18n.ts.info,
diff --git a/packages/frontend/src/pages/explore.roles.vue b/packages/frontend/src/pages/explore.roles.vue
index 389cd23ad2..ffefeb9618 100644
--- a/packages/frontend/src/pages/explore.roles.vue
+++ b/packages/frontend/src/pages/explore.roles.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkRolePreview from '@/components/MkRolePreview.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
const roles = ref<Misskey.entities.Role[] | null>(null);
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index 0d2c6217d4..4db26e799c 100644
--- a/packages/frontend/src/pages/explore.users.vue
+++ b/packages/frontend/src/pages/explore.users.vue
@@ -63,12 +63,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { watch, ref, shallowRef, computed } from 'vue';
+import { watch, ref, useTemplateRef, computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkUserList from '@/components/MkUserList.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkTab from '@/components/MkTab.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
@@ -77,7 +77,7 @@ const props = defineProps<{
}>();
const origin = ref('local');
-const tagsEl = shallowRef<InstanceType<typeof MkFoldableSection>>();
+const tagsEl = useTemplateRef('tagsEl');
const tagsLocal = ref<Misskey.entities.Hashtag[]>([]);
const tagsRemote = ref<Misskey.entities.Hashtag[]>([]);
diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue
index b1a8183d9b..85b9fe4932 100644
--- a/packages/frontend/src/pages/explore.vue
+++ b/packages/frontend/src/pages/explore.vue
@@ -4,30 +4,29 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'featured'" key="featured">
+ <div v-if="tab === 'featured'">
<XFeatured/>
</div>
- <div v-else-if="tab === 'users'" key="users">
+ <div v-else-if="tab === 'users'">
<XUsers/>
</div>
- <div v-else-if="tab === 'roles'" key="roles">
+ <div v-else-if="tab === 'roles'">
<XRoles/>
</div>
</MkHorizontalSwipe>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { computed, watch, ref, shallowRef } from 'vue';
+import { computed, watch, ref, useTemplateRef } from 'vue';
import XFeatured from './explore.featured.vue';
import XUsers from './explore.users.vue';
import XRoles from './explore.roles.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
@@ -38,7 +37,7 @@ const props = withDefaults(defineProps<{
});
const tab = ref(props.initialTab);
-const tagsEl = shallowRef<InstanceType<typeof MkFoldableSection>>();
+const tagsEl = useTemplateRef('tagsEl');
watch(() => props.tag, () => {
if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null);
@@ -60,7 +59,7 @@ const headerTabs = computed(() => [{
title: i18n.ts.roles,
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.explore,
icon: 'ti ti-hash',
}));
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index ab26573b19..456f7800f7 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -4,13 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
+<PageWithHeader>
<MkSpacer :contentMax="800">
<MkPagination :pagination="pagination">
<template #empty>
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.noNotes }}</div>
</div>
</template>
@@ -22,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</MkPagination>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -30,7 +29,7 @@ import MkPagination from '@/components/MkPagination.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import { defineAsyncComponent } from 'vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { infoImageUrl } from '@/instance.js';
import { defaultStore } from '@/store.js';
@@ -45,7 +44,7 @@ const pagination = {
limit: 10,
};
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.favorites,
icon: 'ti ti-star',
}));
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index d84ec4873b..c2f66c0e4d 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<div class="_gaps">
<MkInput v-model="title">
@@ -37,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSpacer>
</div>
</template>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -46,14 +45,14 @@ import * as Misskey from 'misskey-js';
import { AISCRIPT_VERSION } from '@syuilo/aiscript';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkTextarea from '@/components/MkTextarea.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const PRESET_DEFAULT = `/// @ ${AISCRIPT_VERSION}
@@ -461,7 +460,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: flash.value ? `${i18n.ts._play.edit}: ${flash.value.title}` : i18n.ts._play.new,
}));
</script>
diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index 2b85489706..98ab587b55 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'featured'" key="featured">
+ <div v-if="tab === 'featured'">
<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
<div class="_gaps_s">
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
@@ -16,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkPagination>
</div>
- <div v-else-if="tab === 'my'" key="my">
+ <div v-else-if="tab === 'my'">
<div class="_gaps">
<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
@@ -27,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
- <div v-else-if="tab === 'liked'" key="liked">
+ <div v-else-if="tab === 'liked'">
<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
<div class="_gaps_s">
<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
@@ -36,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -46,8 +45,8 @@ import MkPagination from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { useRouter } from '@/router/supplier.js';
+import { definePage } from '@/page.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -91,7 +90,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-heart',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: 'Play',
icon: 'ti ti-player-play',
}));
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 7a7d52691c..a644c83620 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -4,12 +4,11 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
- <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
<div v-if="flash" :key="flash.id">
- <Transition :name="defaultStore.state.animation ? 'zoom' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? 'zoom' : ''" mode="out-in">
<div v-if="started" :class="$style.started">
<div class="main _panel">
<MkAsUi v-if="root" :component="root" :components="components"/>
@@ -58,30 +57,32 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-else/>
</Transition>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { computed, onDeactivated, onUnmounted, Ref, ref, watch, shallowRef, defineAsyncComponent } from 'vue';
+import { computed, onDeactivated, onUnmounted, ref, watch, shallowRef, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
import { Interpreter, Parser, values } from '@syuilo/aiscript';
+import { url } from '@@/js/config.js';
+import type { Ref } from 'vue';
+import type { AsUiComponent, AsUiRoot } from '@/aiscript/ui.js';
+import type { MenuItem } from '@/types/menu.js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@@/js/config.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkAsUi from '@/components/MkAsUi.vue';
-import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui.js';
-import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { registerAsUiLib } from '@/aiscript/ui.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
import MkFolder from '@/components/MkFolder.vue';
import MkCode from '@/components/MkCode.vue';
-import { defaultStore } from '@/store.js';
-import { $i } from '@/account.js';
-import { isSupportShare } from '@/scripts/navigator.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import type { MenuItem } from '@/types/menu.js';
-import { pleaseLogin } from '@/scripts/please-login.js';
+import { prefer } from '@/preferences.js';
+import { $i } from '@/i.js';
+import { isSupportShare } from '@/utility/navigator.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { pleaseLogin } from '@/utility/please-login.js';
const props = defineProps<{
id: string;
@@ -127,7 +128,6 @@ function copyLink() {
if (!flash.value) return;
copyToClipboard(`${url}/play/${flash.value.id}`);
- os.success();
}
function shareWithNavigator() {
@@ -196,6 +196,8 @@ async function run() {
if (aiscript.value) aiscript.value.abort();
if (!flash.value) return;
+ components.value = [];
+
aiscript.value = new Interpreter({
...createAiScriptEnv({
storageKey: 'flash:' + flash.value.id,
@@ -300,7 +302,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: flash.value ? flash.value.title : 'Play',
...flash.value ? {
avatar: flash.value.user,
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index 2f9cacbde9..744fa215cd 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -4,59 +4,57 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div :key="tab" class="_gaps">
- <MkPagination ref="paginationComponent" :pagination="pagination">
- <template #empty>
- <div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
- <div>{{ i18n.ts.noFollowRequests }}</div>
- </div>
- </template>
- <template #default="{items}">
- <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">
- <div class="name">
- <MkA v-user-preview="displayUser(req).id" class="name" :to="userPage(displayUser(req))"><MkUserName :user="displayUser(req)"/></MkA>
- <p class="acct">@{{ acct(displayUser(req)) }}</p>
- </div>
- <div v-if="tab === 'list'" class="commands">
- <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>
+ <MkPagination ref="paginationComponent" :pagination="pagination">
+ <template #empty>
+ <div class="_fullinfo">
+ <img :src="infoImageUrl" draggable="false"/>
+ <div>{{ i18n.ts.noFollowRequests }}</div>
+ </div>
+ </template>
+ <template #default="{items}">
+ <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">
+ <div class="name">
+ <MkA v-user-preview="displayUser(req).id" class="name" :to="userPage(displayUser(req))"><MkUserName :user="displayUser(req)"/></MkA>
+ <p class="acct">@{{ acct(displayUser(req)) }}</p>
+ </div>
+ <div v-if="tab === 'list'" class="commands">
+ <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>
- </template>
- </MkPagination>
- </div>
+ </div>
+ </template>
+ </MkPagination>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { shallowRef, computed, ref } from 'vue';
-import MkPagination, { type Paging } from '@/components/MkPagination.vue';
+import { useTemplateRef, computed, ref } from 'vue';
+import type { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import { userPage, acct } from '@/filters/user.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { infoImageUrl } from '@/instance.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const paginationComponent = useTemplateRef('paginationComponent');
const pagination = computed<Paging>(() => tab.value === 'list' ? {
endpoint: 'following/requests/list',
@@ -104,7 +102,7 @@ const headerTabs = computed(() => [
const tab = ref($i?.isLocked || !$i.hasPendingSentFollowRequest ? 'list' : 'sent');
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.followRequests,
icon: 'ti ti-user-plus',
}));
@@ -145,12 +143,10 @@ definePageMetadata(() => ({
}
> .name {
- font-size: 16px;
line-height: 24px;
}
> .acct {
- font-size: 15px;
line-height: 16px;
opacity: 0.7;
}
@@ -163,7 +159,6 @@ definePageMetadata(() => ({
overflow: hidden;
text-overflow: ellipsis;
opacity: 0.7;
- font-size: 14px;
padding-right: 40px;
padding-left: 8px;
box-sizing: border-box;
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index 70f8b2c31d..7831e084a2 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800" :marginMin="16" :marginMax="32">
<FormSuspense :p="init" class="_gaps">
<MkInput v-model="title">
@@ -34,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</FormSuspense>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -45,12 +44,12 @@ import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSuspense from '@/components/form/suspense.vue';
-import { selectFiles } from '@/scripts/select-file.js';
+import { selectFiles } from '@/utility/select-file.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -122,7 +121,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: props.postId ? i18n.ts.edit : i18n.ts.postToGallery,
icon: 'ti ti-pencil',
}));
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index 2162e269bf..806b6df671 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="1400">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'explore'" key="explore">
+ <div v-if="tab === 'explore'">
<MkFoldableSection class="_margin">
<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true">
@@ -26,14 +25,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkPagination>
</MkFoldableSection>
</div>
- <div v-else-if="tab === 'liked'" key="liked">
+ <div v-else-if="tab === 'liked'">
<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
<div :class="$style.items">
<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
</div>
</MkPagination>
</div>
- <div v-else-if="tab === 'my'" key="my">
+ <div v-else-if="tab === 'my'">
<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
<MkPagination v-slot="{items}" :pagination="myPostsPagination">
<div :class="$style.items">
@@ -43,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -52,9 +51,9 @@ import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -119,7 +118,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-edit',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.gallery,
icon: 'ph-images-square ph-bold ph-lg',
}));
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index 539a6a9a7b..77c4cf1f08 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="1000" :marginMin="16" :marginMax="32">
<div class="_root">
- <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
<div v-if="post" class="rkxwuolj">
<div class="files">
<div v-for="file in post.files" :key="file.id" class="file">
@@ -59,28 +58,28 @@ SPDX-License-Identifier: AGPL-3.0-only
</Transition>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, watch, ref, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
+import { url } from '@@/js/config.js';
+import type { MenuItem } from '@/types/menu.js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
import MkFollowButton from '@/components/MkFollowButton.vue';
-import { url } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { defaultStore } from '@/store.js';
-import { $i } from '@/account.js';
-import { isSupportShare } from '@/scripts/navigator.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { useRouter } from '@/router/supplier.js';
-import type { MenuItem } from '@/types/menu.js';
+import { definePage } from '@/page.js';
+import { prefer } from '@/preferences.js';
+import { $i } from '@/i.js';
+import { isSupportShare } from '@/utility/navigator.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -111,7 +110,6 @@ function fetchPost() {
function copyLink() {
copyToClipboard(`${url}/gallery/${post.value.id}`);
- os.success();
}
function share() {
@@ -208,7 +206,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: post.value ? post.value.title : i18n.ts.gallery,
...post.value ? {
avatar: post.value.user,
diff --git a/packages/frontend/src/pages/games.vue b/packages/frontend/src/pages/games.vue
index 998b8be0f3..7436c13332 100644
--- a/packages/frontend/src/pages/games.vue
+++ b/packages/frontend/src/pages/games.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
+<PageWithHeader>
<MkSpacer :contentMax="800">
<div class="_gaps">
<div class="_panel" :class="$style.link">
@@ -20,14 +19,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
-definePageMetadata(() => ({
+definePage(() => ({
title: 'Misskey Games',
icon: 'ti ti-device-gamepad',
}));
diff --git a/packages/frontend/src/pages/install-extensions.vue b/packages/frontend/src/pages/install-extensions.vue
index 6d68ed83b4..bf57b0c231 100644
--- a/packages/frontend/src/pages/install-extensions.vue
+++ b/packages/frontend/src/pages/install-extensions.vue
@@ -4,14 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
- <MkSpacer :contentMax="500">
+<PageWithAnimBg>
+ <MkSpacer :contentMax="550" :marginMax="50">
<MkLoading v-if="uiPhase === 'fetching'"/>
- <MkExtensionInstaller v-else-if="uiPhase === 'confirm' && data" :extension="data" @confirm="install()">
+ <MkExtensionInstaller v-else-if="uiPhase === 'confirm' && data" :extension="data" @confirm="install()" @cancel="close_()">
<template #additionalInfo>
<FormSection>
- <template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template>
<div class="_gaps_s">
<MkKeyValue>
<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template>
@@ -35,29 +33,30 @@ SPDX-License-Identifier: AGPL-3.0-only
<h2 :class="$style.extInstallerTitle">{{ errorKV?.title }}</h2>
<div :class="$style.extInstallerNormDesc">{{ errorKV?.description }}</div>
<div class="_buttonsCenter">
- <MkButton @click="goBack()">{{ i18n.ts.goBack }}</MkButton>
- <MkButton @click="goToMisskey()">{{ i18n.ts.goToMisskey }}</MkButton>
+ <MkButton @click="close_()">{{ i18n.ts.close }}</MkButton>
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithAnimBg>
</template>
<script lang="ts" setup>
-import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue';
+import { ref, computed, nextTick } from 'vue';
+import type { Extension } from '@/components/MkExtensionInstaller.vue';
+import type { AiScriptPluginMeta } from '@/plugin.js';
import MkLoading from '@/components/global/MkLoading.vue';
-import MkExtensionInstaller, { type Extension } from '@/components/MkExtensionInstaller.vue';
+import MkExtensionInstaller from '@/components/MkExtensionInstaller.vue';
import MkButton from '@/components/MkButton.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';
-import { parseThemeCode, installTheme } from '@/scripts/install-theme.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { parsePluginMeta, installPlugin } from '@/plugin.js';
+import { parseThemeCode, installTheme } from '@/theme.js';
+import { unisonReload } from '@/utility/unison-reload.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const uiPhase = ref<'fetching' | 'confirm' | 'error'>('fetching');
const errorKV = ref<{
@@ -73,12 +72,12 @@ const hash = ref<string | null>(null);
const data = ref<Extension | null>(null);
-function goBack(): void {
- history.back();
-}
-
-function goToMisskey(): void {
- location.href = '/';
+function close_(): void {
+ if (window.history.length === 1) {
+ window.close();
+ } else {
+ window.history.back();
+ }
}
async function fetch() {
@@ -205,9 +204,9 @@ async function install() {
try {
await installPlugin(data.value.raw, data.value.meta as AiScriptPluginMeta);
os.success();
- nextTick(() => {
- unisonReload('/');
- });
+ window.setTimeout(() => {
+ close_();
+ }, 3000);
} catch (err) {
errorKV.value = {
title: i18n.ts._externalResourceInstaller._errors._pluginInstallFailed.title,
@@ -221,28 +220,18 @@ async function install() {
if (!data.value.meta) return;
await installTheme(data.value.raw);
os.success();
- nextTick(() => {
- location.href = '/settings/theme';
- });
+ window.setTimeout(() => {
+ close_();
+ }, 3000);
}
}
-onActivated(() => {
- const urlParams = new URLSearchParams(window.location.search);
- url.value = urlParams.get('url');
- hash.value = urlParams.get('hash');
- fetch();
-});
-
-onDeactivated(() => {
- uiPhase.value = 'fetching';
-});
-
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => []);
+const urlParams = new URLSearchParams(window.location.search);
+url.value = urlParams.get('url');
+hash.value = urlParams.get('hash');
+fetch();
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts._externalResourceInstaller.title,
icon: 'ti ti-download',
}));
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 32a9d30264..963cc98c7c 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer v-if="instance" :contentMax="600" :marginMin="16" :marginMax="32">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'overview'" key="overview" class="_gaps_m">
+ <div v-if="tab === 'overview'" class="_gaps_m">
<div class="fnfelxur">
<img :src="faviconUrl" alt="" class="icon"/>
<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
@@ -100,7 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
</FormSection>
</div>
- <div v-else-if="tab === 'chart'" key="chart" class="_gaps_m">
+ <div v-else-if="tab === 'chart'" class="_gaps_m">
<div class="cmhjzshl">
<div class="selects">
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
@@ -125,14 +124,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</div>
- <div v-else-if="tab === 'users'" key="users" class="_gaps_m">
+ <div v-else-if="tab === 'users'" class="_gaps_m">
<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
<MkUserCardMini :user="user"/>
</MkA>
</MkPagination>
</div>
- <div v-else-if="tab === 'following'" key="following" class="_gaps_m">
+ <div v-else-if="tab === 'following'" class="_gaps_m">
<MkPagination v-slot="{items}" :pagination="followingPagination">
<div class="follow-relations-list">
<div v-for="followRelationship in items" :key="followRelationship.id" class="follow-relation">
@@ -147,7 +146,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkPagination>
</div>
- <div v-else-if="tab === 'followers'" key="followers" class="_gaps_m">
+ <div v-else-if="tab === 'followers'" class="_gaps_m">
<MkPagination v-slot="{items}" :pagination="followersPagination">
<div class="follow-relations-list">
<div v-for="followRelationship in items" :key="followRelationship.id" class="follow-relation">
@@ -162,19 +161,21 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkPagination>
</div>
- <div v-else-if="tab === 'raw'" key="raw" class="_gaps_m">
+ <div v-else-if="tab === 'raw'" class="_gaps_m">
<MkObjectView tall :value="instance">
</MkObjectView>
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
import * as Misskey from 'misskey-js';
-import MkChart, { type ChartSrc } from '@/components/MkChart.vue';
+import type { ChartSrc } from '@/components/MkChart.vue';
+import type { Paging } from '@/components/MkPagination.vue';
+import MkChart from '@/components/MkChart.vue';
import MkObjectView from '@/components/MkObjectView.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue';
@@ -184,15 +185,15 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import number from '@/filters/number.js';
-import { iAmModerator, iAmAdmin } from '@/account.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { iAmModerator, iAmAdmin } from '@/i.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkPagination, { type Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
+import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
import { dateString } from '@/filters/date.js';
import MkTextarea from '@/components/MkTextarea.vue';
import MkInfo from '@/components/MkInfo.vue';
@@ -458,7 +459,7 @@ function getFollowingTabs() {
];
}
-definePageMetadata(() => ({
+definePage(() => ({
title: props.host,
icon: 'ti ti-server',
}));
diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue
index 3416ee6cfc..77ad1cdd96 100644
--- a/packages/frontend/src/pages/invite.vue
+++ b/packages/frontend/src/pages/invite.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
+<PageWithHeader>
<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"/>
+ <img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
<div :class="$style.text">
<i class="ti ti-alert-triangle"></i>
{{ i18n.ts.nothing }}
@@ -30,23 +29,24 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkPagination>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { computed, ref, shallowRef } from 'vue';
-import type * as Misskey from 'misskey-js';
+import { computed, ref, useTemplateRef } from 'vue';
+import * as Misskey from 'misskey-js';
+import type { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkButton from '@/components/MkButton.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
import MkInviteCode from '@/components/MkInviteCode.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { serverErrorImageUrl, instance } from '@/instance.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
const currentInviteLimit = ref<null | number>(null);
const inviteLimit = (($i != null && $i.policies.inviteLimit) || (($i == null && instance.policies.inviteLimit))) as number;
const inviteLimitCycle = (($i != null && $i.policies.inviteLimitCycle) || ($i == null && instance.policies.inviteLimitCycle)) as number;
@@ -91,7 +91,7 @@ async function update() {
update();
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.invite,
icon: 'ti ti-user-plus',
}));
diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue
index e1ba424afc..3a1f98db74 100644
--- a/packages/frontend/src/pages/list.vue
+++ b/packages/frontend/src/pages/list.vue
@@ -4,18 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer v-if="error != null" :contentMax="1200">
<div :class="$style.root">
- <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
+ <img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
<p :class="$style.text">
<i class="ti ti-alert-triangle"></i>
{{ i18n.ts.nothing }}
</p>
</div>
</MkSpacer>
- <MkSpacer v-else-if="list" :contentMax="700" :class="$style.main">
+ <MkSpacer v-else-if="list" :contentMax="700">
<div v-if="list" class="members _margin">
<div :class="$style.member_text">{{ i18n.ts.members }}</div>
<div class="_gaps_s">
@@ -30,19 +29,19 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-if="!list.isLiked" v-tooltip="i18n.ts.like" inline :class="$style.button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="1 > 0" class="count">{{ list.likedCount }}</span></MkButton>
<MkButton inline @click="create()"><i class="ti ti-download" :class="$style.import"></i>{{ i18n.ts.import }}</MkButton>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { watch, computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkButton from '@/components/MkButton.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { serverErrorImageUrl } from '@/instance.js';
const props = defineProps<{
@@ -101,16 +100,12 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: list.value ? list.value.name : i18n.ts.lists,
icon: 'ti ti-list',
}));
</script>
<style lang="scss" module>
-.main {
- min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
-}
-
.userItem {
display: flex;
}
diff --git a/packages/frontend/src/pages/lookup.vue b/packages/frontend/src/pages/lookup.vue
index 6f10c69640..623c2a6779 100644
--- a/packages/frontend/src/pages/lookup.vue
+++ b/packages/frontend/src/pages/lookup.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<div v-if="state === 'done'" class="_buttonsCenter">
<MkButton @click="close">{{ i18n.ts.close }}</MkButton>
@@ -15,23 +14,23 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading/>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { mainRouter } from '@/router/main.js';
+import { definePage } from '@/page.js';
+import { mainRouter } from '@/router.js';
import MkButton from '@/components/MkButton.vue';
const state = ref<'fetching' | 'done'>('fetching');
function fetch() {
- const params = new URL(location.href).searchParams;
+ const params = new URL(window.location.href).searchParams;
// acctのほうはdeprecated
let uri = params.get('uri') ?? params.get('acct');
@@ -76,12 +75,12 @@ function close(): void {
// 閉じなければ100ms後タイムラインに
window.setTimeout(() => {
- location.href = '/';
+ window.location.href = '/';
}, 100);
}
function goToMisskey(): void {
- location.href = '/';
+ window.location.href = '/';
}
fetch();
@@ -90,7 +89,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata({
+definePage({
title: i18n.ts.lookup,
icon: 'ti ti-world-search',
});
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index ab060587c5..d4296d428b 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div>
- <MkAnimBg style="position: fixed; top: 0;"/>
+<PageWithAnimBg>
<div :class="$style.formContainer">
<div :class="$style.form">
<MkAuthConfirm
@@ -25,19 +24,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkAuthConfirm>
</div>
</div>
-</div>
+</PageWithAnimBg>
</template>
<script lang="ts" setup>
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';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
const props = defineProps<{
session: string;
@@ -64,7 +60,7 @@ async function onAccept(token: string) {
const cbUrl = new URL(props.callback);
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url');
cbUrl.searchParams.set('session', props.session);
- location.href = cbUrl.toString();
+ window.location.href = cbUrl.toString();
} else {
authRoot.value?.showUI('success');
}
@@ -77,7 +73,7 @@ function onDeny() {
authRoot.value?.showUI('denied');
}
-definePageMetadata(() => ({
+definePage(() => ({
title: 'MiAuth',
icon: 'ti ti-apps',
}));
diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue
index 2b8518747f..8f331f1333 100644
--- a/packages/frontend/src/pages/my-antennas/create.vue
+++ b/packages/frontend/src/pages/my-antennas/create.vue
@@ -4,19 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkAntennaEditor @created="onAntennaCreated"/>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { antennasCache } from '@/cache.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import MkAntennaEditor from '@/components/MkAntennaEditor.vue';
const router = useRouter();
@@ -29,7 +27,7 @@ function onAntennaCreated() {
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.createAntenna,
icon: 'ti ti-antenna',
}));
diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue
index 9f927cd1a0..f449e83c1f 100644
--- a/packages/frontend/src/pages/my-antennas/edit.vue
+++ b/packages/frontend/src/pages/my-antennas/edit.vue
@@ -4,22 +4,20 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkAntennaEditor v-if="antenna" :antenna="antenna" @updated="onAntennaUpdated"/>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkAntennaEditor from '@/components/MkAntennaEditor.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { antennasCache } from '@/cache.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -41,7 +39,7 @@ misskeyApi('antennas/show', { antennaId: props.antennaId }).then((antennaRespons
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.editAntenna,
icon: 'ti ti-antenna',
}));
diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue
index 540cb34904..297436ad61 100644
--- a/packages/frontend/src/pages/my-antennas/index.vue
+++ b/packages/frontend/src/pages/my-antennas/index.vue
@@ -4,13 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<div>
<div v-if="antennas.length === 0" class="empty">
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</div>
@@ -24,14 +23,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { onActivated, computed } from 'vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { antennasCache } from '@/cache.js';
import { infoImageUrl } from '@/instance.js';
@@ -55,7 +54,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.manageAntennas,
icon: 'ti ti-antenna',
}));
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index acf37a9a2f..1525bbef9b 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -4,35 +4,34 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'my'" key="my" class="_gaps">
+ <div v-if="tab === 'my'" class="_gaps">
<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
<MkPagination v-slot="{ items }" ref="pagingComponent" :pagination="pagination" class="_gaps">
<MkClipPreview v-for="item in items" :key="item.id" :clip="item" :noUserInfo="true"/>
</MkPagination>
</div>
- <div v-else-if="tab === 'favorites'" key="favorites" class="_gaps">
+ <div v-else-if="tab === 'favorites'" class="_gaps">
<MkClipPreview v-for="item in favorites" :key="item.id" :clip="item"/>
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { watch, ref, shallowRef, computed } from 'vue';
+import { watch, ref, useTemplateRef, computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkPagination from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import MkClipPreview from '@/components/MkClipPreview.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { clipsCache } from '@/cache.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
@@ -46,7 +45,7 @@ const tab = ref('my');
const favorites = ref<Misskey.entities.Clip[] | null>(null);
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
watch(tab, async () => {
favorites.value = await misskeyApi('clips/my-favorites');
@@ -100,7 +99,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-heart',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.clip,
icon: 'ti ti-paperclip',
}));
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index d08f6fec5a..6e23769083 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -4,13 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<div class="_gaps">
<div v-if="items.length === 0" class="empty">
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</div>
@@ -25,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -34,12 +33,12 @@ import MkButton from '@/components/MkButton.vue';
import MkAvatars from '@/components/MkAvatars.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { userListsCache } from '@/cache.js';
import { infoImageUrl } from '@/instance.js';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const items = computed(() => userListsCache.value.value ?? []);
@@ -71,7 +70,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.manageLists,
icon: 'ti ti-list',
}));
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index 69e404bd85..c187435af9 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -4,9 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
- <MkSpacer :contentMax="700" :class="$style.main">
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
+ <MkSpacer :contentMax="700">
<div v-if="list" class="_gaps">
<MkFolder>
<template #label>{{ i18n.ts.settings }}</template>
@@ -49,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -57,8 +56,8 @@ import { computed, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { userPage } from '@/filters/user.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
@@ -66,16 +65,16 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkInput from '@/components/MkInput.vue';
import { userListsCache } from '@/cache.js';
-import { signinRequired } from '@/account.js';
-import { defaultStore } from '@/store.js';
+import { ensureSignin } from '@/i.js';
import MkPagination from '@/components/MkPagination.vue';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
+import { prefer } from '@/preferences.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const {
enableInfiniteScroll,
-} = defaultStore.reactiveState;
+} = prefer.r;
const props = defineProps<{
listId: string;
@@ -191,17 +190,13 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: list.value ? list.value.name : i18n.ts.lists,
icon: 'ti ti-list',
}));
</script>
<style lang="scss" module>
-.main {
- min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
-}
-
.userItem {
display: flex;
}
diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue
index 6a2d01b6fa..684a3bb5bd 100644
--- a/packages/frontend/src/pages/not-found.vue
+++ b/packages/frontend/src/pages/not-found.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
<div class="_fullinfo">
- <img :src="notFoundImageUrl" class="_ghost"/>
+ <img :src="notFoundImageUrl" draggable="false"/>
<div>{{ i18n.ts.notFoundDescription }}</div>
</div>
</div>
@@ -15,8 +15,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed } from 'vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { pleaseLogin } from '@/scripts/please-login.js';
+import { definePage } from '@/page.js';
+import { pleaseLogin } from '@/utility/please-login.js';
import { notFoundImageUrl } from '@/instance.js';
const props = defineProps<{
@@ -31,7 +31,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.notFound,
icon: 'ti ti-alert-triangle',
}));
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index 5214ca4849..dece69bfc8 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<div>
- <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
+ <Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
<div v-if="note">
<div v-if="showNext" class="_margin">
<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
@@ -45,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</Transition>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -56,16 +55,17 @@ import type { Paging } from '@/components/MkPagination.vue';
import MkNotes from '@/components/MkNotes.vue';
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
import MkButton from '@/components/MkButton.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js';
import MkClipPreview from '@/components/MkClipPreview.vue';
import SkErrorList from '@/components/SkErrorList.vue';
-import { defaultStore } from '@/store.js';
-import { pleaseLogin } from '@/scripts/please-login.js';
+import { prefer } from '@/preferences.js';
+import { pleaseLogin } from '@/utility/please-login.js';
+import { getAppearNote } from '@/utility/get-appear-note.js';
import { serverContext, assertServerContext } from '@/server-context.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
// contextは非ログイン状態の情報しかないためログイン時は利用できない
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
@@ -140,10 +140,11 @@ function fetchNote() {
noteId: props.noteId,
}).then(res => {
note.value = res;
+ const appearNote = getAppearNote(res);
// 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く
- if (note.value.clippedCount > 0 || new Date(note.value.createdAt).getTime() < new Date('2023-10-01').getTime()) {
+ if ((appearNote.clippedCount ?? 0) > 0 || new Date(appearNote.createdAt).getTime() < new Date('2023-10-01').getTime()) {
misskeyApi('notes/clips', {
- noteId: note.value.id,
+ noteId: appearNote.id,
}).then((_clips) => {
clips.value = _clips;
});
@@ -177,14 +178,14 @@ const headerActions = computed(() => note.value ? [
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.note,
...note.value ? {
subtitle: dateString(note.value.createdAt),
avatar: note.value.user,
path: `/notes/${note.value.id}`,
share: {
- title: i18n.tsx.noteOf({ user: note.value.user.name }),
+ title: i18n.tsx.noteOf({ user: note.value.user.name ?? note.value.user.username }),
text: note.value.text,
},
} : {},
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index 46ee501c76..a7ff519a34 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -4,33 +4,32 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'all'" key="all">
+ <div v-if="tab === 'all'">
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
</div>
- <div v-else-if="tab === 'mentions'" key="mention">
+ <div v-else-if="tab === 'mentions'">
<MkNotes :pagination="mentionsPagination"/>
</div>
- <div v-else-if="tab === 'directNotes'" key="directNotes">
+ <div v-else-if="tab === 'directNotes'">
<MkNotes :pagination="directNotesPagination"/>
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
+import { notificationTypes } from '@@/js/const.js';
import XNotifications from '@/components/MkNotifications.vue';
import MkNotes from '@/components/MkNotes.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { notificationTypes } from '@@/js/const.js';
+import { definePage } from '@/page.js';
const tab = ref('all');
const includeTypes = ref<string[] | null>(null);
@@ -94,7 +93,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-mail',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.notifications,
icon: 'ti ti-bell',
}));
diff --git a/packages/frontend/src/pages/oauth.vue b/packages/frontend/src/pages/oauth.vue
index 8719a769e5..49fdd25ff3 100644
--- a/packages/frontend/src/pages/oauth.vue
+++ b/packages/frontend/src/pages/oauth.vue
@@ -4,13 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div>
- <MkAnimBg style="position: fixed; top: 0;"/>
+<PageWithAnimBg>
<div :class="$style.formContainer">
<div :class="$style.form">
<MkAuthConfirm
ref="authRoot"
:name="name"
+ :icon="logo"
:permissions="permissions"
:waitOnDeny="true"
@accept="onAccept"
@@ -18,50 +18,50 @@ SPDX-License-Identifier: AGPL-3.0-only
/>
</div>
</div>
-</div>
+</PageWithAnimBg>
</template>
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import MkAnimBg from '@/components/MkAnimBg.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkAuthConfirm from '@/components/MkAuthConfirm.vue';
-const transactionIdMeta = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:transaction-id"]');
+const transactionIdMeta = window.document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:transaction-id"]');
if (transactionIdMeta) {
transactionIdMeta.remove();
}
-const name = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-name"]')?.content;
-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)) ?? [];
+const name = window.document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-name"]')?.content;
+const logo = window.document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-logo"]')?.content;
+const permissions = window.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');
+ const form = window.document.createElement('form');
form.action = '/oauth/decision';
form.method = 'post';
form.acceptCharset = 'utf-8';
- const loginToken = document.createElement('input');
+ const loginToken = window.document.createElement('input');
loginToken.type = 'hidden';
loginToken.name = 'login_token';
loginToken.value = token;
form.appendChild(loginToken);
- const transactionId = document.createElement('input');
+ const transactionId = window.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');
+ const cancel = window.document.createElement('input');
cancel.type = 'hidden';
cancel.name = 'cancel';
cancel.value = 'cancel';
form.appendChild(cancel);
}
- document.body.appendChild(form);
+ window.document.body.appendChild(form);
form.submit();
}
@@ -73,7 +73,7 @@ function onDeny(token: string) {
doPost(token, 'deny');
}
-definePageMetadata(() => ({
+definePage(() => ({
title: 'OAuth',
icon: 'ti ti-apps',
}));
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 c3ad6657b0..1b98425719 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
@@ -26,7 +26,7 @@ import * as Misskey from 'misskey-js';
import XContainer from '../page-editor.container.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
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 36e03b4790..f275ec9517 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
@@ -30,7 +30,7 @@ import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkNote from '@/components/MkNote.vue';
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
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 3fed07f7e8..4d1a3716e7 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
@@ -21,14 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-
+
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';
import { i18n } from '@/i18n.js';
-import { deepClone } from '@/scripts/clone.js';
+import { deepClone } from '@/utility/clone.js';
import MkButton from '@/components/MkButton.vue';
import { getPageBlockList } from '@/pages/page-editor/common.js';
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 5795b46c00..4a980ce472 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
@@ -15,12 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-
-import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue';
+import { watch, ref, useTemplateRef, 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';
+import { Autocomplete } from '@/utility/autocomplete.js';
const props = defineProps<{
modelValue: Misskey.entities.PageBlock & { type: 'text' }
@@ -33,7 +32,7 @@ const emit = defineEmits<{
let autocomplete: Autocomplete;
const text = ref(props.modelValue.text ?? '');
-const inputEl = shallowRef<HTMLTextAreaElement | null>(null);
+const inputEl = useTemplateRef('inputEl');
watch(text, () => {
emit('update:modelValue', {
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 1739c2fc00..6b10c641d7 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.container.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.container.vue
@@ -61,11 +61,11 @@ function remove() {
position: relative;
overflow: hidden;
background: var(--MI_THEME-panel);
- border: solid 2px var(--MI_THEME-X12);
+ border: solid 2px light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
border-radius: var(--MI-radius-sm);
&:hover {
- border: solid 2px var(--MI_THEME-X13);
+ border: solid 2px light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15));
}
&.warn {
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index 8597654375..67134f0976 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<div class="jqqmcavi">
<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ i18n.ts._pages.viewPage }}</MkButton>
@@ -57,26 +56,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, provide, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { v4 as uuid } from 'uuid';
+import { url } from '@@/js/config.js';
import XBlocks from './page-editor.blocks.vue';
import MkButton from '@/components/MkButton.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue';
-import { url } from '@@/js/config.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { selectFile } from '@/scripts/select-file.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { selectFile } from '@/utility/select-file.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i } from '@/account.js';
-import { mainRouter } from '@/router/main.js';
+import { definePage } from '@/page.js';
+import { $i } from '@/i.js';
+import { mainRouter } from '@/router.js';
import { getPageBlockList } from '@/pages/page-editor/common.js';
const props = defineProps<{
@@ -264,10 +263,10 @@ const headerTabs = computed(() => [{
icon: 'ti ti-note',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: props.initPageId ? i18n.ts._pages.editPage
- : props.initPageName && props.initUser ? i18n.ts._pages.readPage
- : i18n.ts._pages.newPage,
+ : props.initPageName && props.initUser ? i18n.ts._pages.readPage
+ : i18n.ts._pages.newPage,
icon: 'ti ti-pencil',
}));
</script>
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index d27a4f121d..637821e74f 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -4,14 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.fadeEnterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.fadeLeaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.fadeEnterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.fadeLeaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.fadeEnterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.fadeLeaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.fadeEnterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.fadeLeaveTo : ''"
mode="out-in"
>
<div v-if="page" :key="page.id" class="_gaps">
@@ -94,17 +93,18 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-else/>
</Transition>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, watch, ref, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
+import { url } from '@@/js/config.js';
+import type { MenuItem } from '@/types/menu.js';
import XPage from '@/components/page/page.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@@/js/config.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkMediaImage from '@/components/MkMediaImage.vue';
import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
import MkFollowButton from '@/components/MkFollowButton.vue';
@@ -112,16 +112,16 @@ import MkContainer from '@/components/MkContainer.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagePreview from '@/components/MkPagePreview.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { pageViewInterruptors, defaultStore } from '@/store.js';
-import { deepClone } from '@/scripts/clone.js';
-import { $i } from '@/account.js';
-import { isSupportShare } from '@/scripts/navigator.js';
+import { definePage } from '@/page.js';
+import { deepClone } from '@/utility/clone.js';
+import { $i } from '@/i.js';
+import { isSupportShare } from '@/utility/navigator.js';
import { instance } from '@/instance.js';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { useRouter } from '@/router/supplier.js';
-import type { MenuItem } from '@/types/menu.js';
+import { getStaticImageUrl } from '@/utility/media-proxy.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { useRouter } from '@/router.js';
+import { prefer } from '@/preferences.js';
+import { getPluginHandlers } from '@/plugin.js';
const router = useRouter();
@@ -150,6 +150,7 @@ function fetchPage() {
page.value = _page;
// plugin
+ const pageViewInterruptors = getPluginHandlers('page_view_interruptor');
if (pageViewInterruptors.length > 0) {
let result = deepClone(_page);
for (const interruptor of pageViewInterruptors) {
@@ -188,7 +189,6 @@ function copyLink() {
if (!page.value) return;
copyToClipboard(`${url}/@${page.value.user.username}/pages/${page.value.name}`);
- os.success();
}
function shareWithNote() {
@@ -318,7 +318,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: page.value ? page.value.title || page.value.name : i18n.ts.pages,
...page.value ? {
avatar: page.value.user,
diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue
index 4ef9d3b091..c99d7f1a0f 100644
--- a/packages/frontend/src/pages/pages.vue
+++ b/packages/frontend/src/pages/pages.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="700">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'featured'" key="featured">
+ <div v-if="tab === 'featured'">
<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
<div class="_gaps">
<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
@@ -16,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkPagination>
</div>
- <div v-else-if="tab === 'my'" key="my" class="_gaps">
+ <div v-else-if="tab === 'my'" class="_gaps">
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="myPagesPagination">
<div class="_gaps">
@@ -25,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkPagination>
</div>
- <div v-else-if="tab === 'liked'" key="liked">
+ <div v-else-if="tab === 'liked'">
<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
<div class="_gaps">
<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
@@ -34,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -44,8 +43,8 @@ import MkPagination from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { useRouter } from '@/router/supplier.js';
+import { definePage } from '@/page.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -88,7 +87,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-heart',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.pages,
icon: 'ti ti-note',
}));
diff --git a/packages/frontend/src/pages/preview.vue b/packages/frontend/src/pages/preview.vue
index 8e07b190aa..78167500f4 100644
--- a/packages/frontend/src/pages/preview.vue
+++ b/packages/frontend/src/pages/preview.vue
@@ -13,13 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed } from 'vue';
import MkSample from '@/components/MkPreview.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(computed(() => ({
+definePage(computed(() => ({
title: i18n.ts.preview,
icon: 'ti ti-eye',
})));
diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue
index a13a8f1f4b..9140555f86 100644
--- a/packages/frontend/src/pages/registry.keys.vue
+++ b/packages/frontend/src/pages/registry.keys.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="600" :marginMin="16">
<div class="_gaps_m">
<FormSplit>
@@ -29,16 +28,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</FormSection>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { watch, computed, ref } from 'vue';
import JSON5 from 'json5';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/MkButton.vue';
@@ -96,7 +95,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.registry,
icon: 'ti ti-adjustments',
}));
diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue
index c40d13f664..7c0a7f20bb 100644
--- a/packages/frontend/src/pages/registry.value.vue
+++ b/packages/frontend/src/pages/registry.value.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="600" :marginMin="16">
<div class="_gaps_m">
<FormInfo warn>{{ i18n.ts.editTheseSettingsMayBreakAccount }}</FormInfo>
@@ -41,16 +40,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { watch, computed, ref } from 'vue';
import JSON5 from 'json5';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
@@ -123,7 +122,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.registry,
icon: 'ti ti-adjustments',
}));
diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue
index c641874b17..c60833920b 100644
--- a/packages/frontend/src/pages/registry.vue
+++ b/packages/frontend/src/pages/registry.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="600" :marginMin="16">
<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
@@ -18,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</FormSection>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -26,9 +25,9 @@ import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import JSON5 from 'json5';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/MkButton.vue';
@@ -73,7 +72,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.registry,
icon: 'ti ti-adjustments',
}));
diff --git a/packages/frontend/src/pages/reset-password.vue b/packages/frontend/src/pages/reset-password.vue
index 6d24029535..0a7726a7f8 100644
--- a/packages/frontend/src/pages/reset-password.vue
+++ b/packages/frontend/src/pages/reset-password.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer v-if="token" :contentMax="700" :marginMin="16" :marginMax="32">
<div class="_gaps_m">
<MkInput v-model="password" type="password">
@@ -16,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton primary @click="save">{{ i18n.ts.save }}</MkButton>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -25,8 +24,8 @@ import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { mainRouter } from '@/router/main.js';
+import { definePage } from '@/page.js';
+import { mainRouter } from '@/router.js';
const props = defineProps<{
token?: string;
@@ -55,7 +54,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.resetPassword,
icon: 'ti ti-lock',
}));
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
index 429f502133..403a760521 100644
--- a/packages/frontend/src/pages/reversi/game.board.vue
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -148,18 +148,18 @@ import * as Reversi from 'misskey-reversi';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import { deepClone } from '@/scripts/clone.js';
+import { deepClone } from '@/utility/clone.js';
import { useInterval } from '@@/js/use-interval.js';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
import { url } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { userPage } from '@/filters/user.js';
-import * as sound from '@/scripts/sound.js';
+import * as sound from '@/utility/sound.js';
import * as os from '@/os.js';
-import { confetti } from '@/scripts/confetti.js';
+import { confetti } from '@/utility/confetti.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const props = defineProps<{
game: Misskey.entities.ReversiGameDetailed;
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
index 437a1a2294..d2720a79fc 100644
--- a/packages/frontend/src/pages/reversi/game.setting.vue
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -114,17 +114,17 @@ import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue';
import * as Misskey from 'misskey-js';
import * as Reversi from 'misskey-reversi';
import { i18n } from '@/i18n.js';
-import { signinRequired } from '@/account.js';
-import { deepClone } from '@/scripts/clone.js';
+import { ensureSignin } from '@/i.js';
+import { deepClone } from '@/utility/clone.js';
import MkButton from '@/components/MkButton.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js';
import type { MenuItem } from '@/types/menu.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const router = useRouter();
diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue
index 10ea3717ab..a447572cc0 100644
--- a/packages/frontend/src/pages/reversi/game.vue
+++ b/packages/frontend/src/pages/reversi/game.vue
@@ -14,17 +14,17 @@ import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue';
import * as Misskey from 'misskey-js';
import GameSetting from './game.setting.vue';
import GameBoard from './game.board.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { useStream } from '@/stream.js';
-import { signinRequired } from '@/account.js';
-import { useRouter } from '@/router/supplier.js';
+import { ensureSignin } from '@/i.js';
+import { useRouter } from '@/router.js';
import * as os from '@/os.js';
import { url } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { useInterval } from '@@/js/use-interval.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const router = useRouter();
@@ -114,7 +114,7 @@ onUnmounted(() => {
}
});
-definePageMetadata(() => ({
+definePage(() => ({
title: 'Reversi',
icon: 'ti ti-device-gamepad',
}));
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
index d608a2411c..e3f01d9938 100644
--- a/packages/frontend/src/pages/reversi/index.vue
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -107,19 +107,19 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onDeactivated, onMounted, onUnmounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { useStream } from '@/stream.js';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import MkPagination from '@/components/MkPagination.vue';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import * as os from '@/os.js';
import { useInterval } from '@@/js/use-interval.js';
-import { pleaseLogin } from '@/scripts/please-login.js';
-import * as sound from '@/scripts/sound.js';
+import { pleaseLogin } from '@/utility/please-login.js';
+import * as sound from '@/utility/sound.js';
const myGamesPagination = {
endpoint: 'reversi/games' as const,
@@ -261,7 +261,7 @@ onUnmounted(() => {
cancelMatching();
});
-definePageMetadata(() => ({
+definePage(() => ({
title: 'Reversi',
icon: 'ti ti-device-gamepad',
}));
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index d985349bc5..5a1ed70e2f 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -4,11 +4,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :displayBackButton="true" :tabs="headerTabs"/></template>
+<PageWithHeader v-model:tab="tab" :displayBackButton="true" :tabs="headerTabs">
<MkSpacer v-if="error != null" :contentMax="1200">
<div :class="$style.root">
- <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
+ <img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
<p :class="$style.text">
<i class="ti ti-alert-triangle"></i>
{{ error }}
@@ -20,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="role">{{ role.description }}</div>
<MkUserList v-if="visible" :pagination="users" :extractor="(item) => item.user"/>
<div v-else-if="!visible" class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</div>
@@ -28,22 +27,22 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer v-else-if="tab === 'timeline'" :contentMax="700">
<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
<div v-else-if="!visible" class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { instanceName } from '@@/js/config.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkUserList from '@/components/MkUserList.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
import MkTimeline from '@/components/MkTimeline.vue';
-import { instanceName } from '@@/js/config.js';
import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
const props = withDefaults(defineProps<{
@@ -93,7 +92,7 @@ const headerTabs = computed(() => [{
title: i18n.ts.timeline,
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: role.value ? role.value.name : (error.value ?? i18n.ts.role),
icon: 'ti ti-badge',
}));
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index 983cba1746..5bf9d52221 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -4,9 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
-
+<PageWithHeader>
<MkSpacer :contentMax="800">
<div :class="$style.root">
<div class="_gaps_s">
@@ -53,25 +51,28 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { onDeactivated, onUnmounted, Ref, ref, watch, computed } from 'vue';
+import { onDeactivated, onUnmounted, ref, watch, computed } from 'vue';
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
+import type { Ref } from 'vue';
+import type { AsUiComponent } from '@/aiscript/ui.js';
+import type { AsUiRoot } from '@/aiscript/ui.js';
import MkContainer from '@/components/MkContainer.vue';
import MkButton from '@/components/MkButton.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
-import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui.js';
+import { definePage } from '@/page.js';
+import { registerAsUiLib } from '@/aiscript/ui.js';
import MkAsUi from '@/components/MkAsUi.vue';
import { miLocalStorage } from '@/local-storage.js';
-import { claimAchievement } from '@/scripts/achievements.js';
+import { claimAchievement } from '@/utility/achievements.js';
const parser = new Parser();
let aiscript: Interpreter;
@@ -99,7 +100,7 @@ function stringifyUiProps(uiProps) {
return JSON.stringify(
{ ...uiProps, type: undefined, id: undefined },
(k, v) => typeof v === 'function' ? '<function>' : v,
- 2
+ 2,
);
}
@@ -198,7 +199,7 @@ const showns = computed(() => {
return result;
});
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.scratchpad,
icon: 'ti ti-terminal-2',
}));
diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue
index d5f96efb8e..cf991efa07 100644
--- a/packages/frontend/src/pages/search.note.vue
+++ b/packages/frontend/src/pages/search.note.vue
@@ -6,24 +6,36 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<div class="_gaps">
- <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter.prevent="search">
+ <MkInput
+ v-model="searchQuery"
+ large
+ autofocus
+ type="search"
+ @enter.prevent="search"
+ >
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
- <MkFoldableSection :expanded="true">
+ <MkFoldableSection expanded>
<template #header>{{ i18n.ts.options }}</template>
<div class="_gaps_m">
- <template v-if="instance.federation !== 'none'">
- <MkRadios v-model="hostSelect">
- <template #label>{{ i18n.ts.host }}</template>
- <option value="all" default>{{ i18n.ts.all }}</option>
- <option value="local">{{ i18n.ts.local }}</option>
- <option v-if="noteSearchableScope === 'global'" value="specified">{{ i18n.ts.specifyHost }}</option>
- </MkRadios>
- <MkInput v-if="noteSearchableScope === 'global'" v-model="hostInput" :disabled="hostSelect !== 'specified'" :large="true" type="search">
+ <MkRadios v-model="searchScope">
+ <option v-if="instance.federation !== 'none' && noteSearchableScope === 'global'" value="all">{{ i18n.ts._search.searchScopeAll }}</option>
+ <option value="local">{{ instance.federation === 'none' ? i18n.ts._search.searchScopeAll : i18n.ts._search.searchScopeLocal }}</option>
+ <option v-if="instance.federation !== 'none' && noteSearchableScope === 'global'" value="server">{{ i18n.ts._search.searchScopeServer }}</option>
+ <option value="user">{{ i18n.ts._search.searchScopeUser }}</option>
+ </MkRadios>
+
+ <div v-if="instance.federation !== 'none' && searchScope === 'server'" :class="$style.subOptionRoot">
+ <MkInput
+ v-model="hostInput"
+ :placeholder="i18n.ts._search.serverHostPlaceholder"
+ @enter.prevent="search"
+ >
+ <template #label>{{ i18n.ts._search.pleaseEnterServerHost }}</template>
<template #prefix><i class="ti ti-server"></i></template>
</MkInput>
- </template>
+ </div>
<MkSwitch v-model="order">{{ i18n.ts._noteSearch.newestToOldest }}</MkSwitch>
@@ -37,52 +49,99 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="flash">{{ i18n.ts._noteSearch._fileType.flash }}</option>
</MkSelect>
- <MkFolder :defaultOpen="true">
- <template #label>{{ i18n.ts.specifyUser }}</template>
- <template v-if="user" #suffix>@{{ user.username }}{{ user.host ? `@${user.host}` : "" }}</template>
-
+ <div v-if="searchScope === 'user'" :class="$style.subOptionRoot">
+ <div :class="$style.userSelectLabel">{{ i18n.ts._search.pleaseSelectUser }}</div>
<div class="_gaps">
- <div :class="$style.userItem">
- <MkUserCardMini v-if="user" :class="$style.userCard" :user="user" :withChart="false"/>
- <MkButton v-if="user == null && $i != null" transparent :class="$style.addMeButton" @click="selectSelf"><div :class="$style.addUserButtonInner"><span><i class="ti ti-plus"></i><i class="ti ti-user"></i></span><span>{{ i18n.ts.selectSelf }}</span></div></MkButton>
- <MkButton v-if="user == null" transparent :class="$style.addUserButton" @click="selectUser"><div :class="$style.addUserButtonInner"><i class="ti ti-plus"></i><span>{{ i18n.ts.selectUser }}</span></div></MkButton>
- <button class="_button" :class="$style.remove" :disabled="user == null" @click="removeUser"><i class="ti ti-x"></i></button>
+ <div v-if="user == null" :class="$style.userSelectButtons">
+ <div v-if="$i != null">
+ <MkButton
+ transparent
+ :class="$style.userSelectButton"
+ @click="selectSelf"
+ >
+ <div :class="$style.userSelectButtonInner">
+ <span><i class="ti ti-plus"></i><i class="ti ti-user"></i></span>
+ <span>{{ i18n.ts.selectSelf }}</span>
+ </div>
+ </MkButton>
+ </div>
+ <div :style="$i == null ? 'grid-column: span 2;' : undefined">
+ <MkButton
+ transparent
+ :class="$style.userSelectButton"
+ @click="selectUser"
+ >
+ <div :class="$style.userSelectButtonInner">
+ <span><i class="ti ti-plus"></i></span>
+ <span>{{ i18n.ts.selectUser }}</span>
+ </div>
+ </MkButton>
+ </div>
+ </div>
+ <div v-else :class="$style.userSelectedButtons">
+ <div style="overflow: hidden;">
+ <MkUserCardMini
+ :user="user"
+ :withChart="false"
+ :class="$style.userSelectedCard"
+ />
+ </div>
+ <div>
+ <button
+ class="_button"
+ :class="$style.userSelectedRemoveButton"
+ @click="removeUser"
+ >
+ <i class="ti ti-x"></i>
+ </button>
+ </div>
</div>
</div>
- </MkFolder>
+ </div>
</div>
</MkFoldableSection>
<div>
- <MkButton large primary gradate rounded style="margin: 0 auto;" @click="search">{{ i18n.ts.search }}</MkButton>
+ <MkButton
+ large
+ primary
+ gradate
+ rounded
+ :disabled="searchParams == null"
+ style="margin: 0 auto;"
+ @click="search"
+ >
+ {{ i18n.ts.search }}
+ </MkButton>
</div>
</div>
<MkFoldableSection v-if="notePagination">
<template #header>{{ i18n.ts.searchResult }}</template>
- <MkNotes :key="key" :pagination="notePagination"/>
+ <MkNotes :key="`searchNotes:${key}`" :pagination="notePagination"/>
</MkFoldableSection>
</div>
</template>
<script lang="ts" setup>
-import { computed, ref, toRef, watch } from 'vue';
-import type { UserDetailed } from 'misskey-js/entities.js';
+import { computed, ref, shallowRef, toRef } from 'vue';
+import type * as Misskey from 'misskey-js';
import type { Paging } from '@/components/MkPagination.vue';
-import MkNotes from '@/components/MkNotes.vue';
-import MkInput from '@/components/MkInput.vue';
-import MkButton from '@/components/MkButton.vue';
-import MkSwitch from '@/components/MkSwitch.vue';
-import MkSelect from '@/components/MkSelect.vue';
+import { $i } from '@/i.js';
+import { host as localHost } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
+import { instance } from '@/instance.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { apLookup } from '@/utility/lookup.js';
+import { useRouter } from '@/router.js';
+import MkButton from '@/components/MkButton.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import MkFolder from '@/components/MkFolder.vue';
-import { useRouter } from '@/router/supplier.js';
-import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkNotes from '@/components/MkNotes.vue';
import MkRadios from '@/components/MkRadios.vue';
-import { $i } from '@/account.js';
-import { instance } from '@/instance.js';
+import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkSelect from '@/components/MkSelect.vue';
const props = withDefaults(defineProps<{
query?: string;
@@ -97,86 +156,132 @@ const props = withDefaults(defineProps<{
});
const router = useRouter();
+
const key = ref(0);
+const notePagination = ref<Paging<'notes/search'>>();
+
const searchQuery = ref(toRef(props, 'query').value);
-const notePagination = ref<Paging>();
-const user = ref<UserDetailed | null>(null);
const hostInput = ref(toRef(props, 'host').value);
const order = ref(false);
const filetype = ref<'image' | 'video' | 'audio' | 'module' | 'flash' | null>(null);
+const user = shallowRef<Misskey.entities.UserDetailed | null>(null);
+
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const noteSearchableScope = instance.noteSearchableScope ?? 'local';
-const hostSelect = ref<'all' | 'local' | 'specified'>('all');
+//#region set user
+let fetchedUser: Misskey.entities.UserDetailed | null = null;
+
+if (props.userId) {
+ fetchedUser = await misskeyApi('users/show', {
+ userId: props.userId,
+ }).catch(() => null);
+}
+
+if (props.username && fetchedUser == null) {
+ fetchedUser = await misskeyApi('users/show', {
+ username: props.username,
+ ...(props.host ? { host: props.host } : {}),
+ }).catch(() => null);
+}
+
+if (fetchedUser != null) {
+ if (!(noteSearchableScope === 'local' && fetchedUser.host != null)) {
+ user.value = fetchedUser;
+ }
+}
+//#endregion
+
+const searchScope = ref<'all' | 'local' | 'server' | 'user'>((() => {
+ if (user.value != null) return 'user';
+ if (noteSearchableScope === 'local') return 'local';
+ if (hostInput.value) return 'server';
+ return 'all';
+})());
-const setHostSelectWithInput = (after:string|undefined|null, before:string|undefined|null) => {
- if (before === after) return;
- if (after === '') hostSelect.value = 'all';
- else hostSelect.value = 'specified';
+type SearchParams = {
+ readonly query: string;
+ readonly host?: string;
+ readonly userId?: string;
};
-setHostSelectWithInput(hostInput.value, undefined);
+const fixHostIfLocal = (target: string | null | undefined) => {
+ if (!target || target === localHost) return '.';
+ return target;
+};
-watch(hostInput, setHostSelectWithInput);
+const searchParams = computed<SearchParams | null>(() => {
+ const trimmedQuery = searchQuery.value.trim();
+ if (!trimmedQuery) return null;
-const searchHost = computed(() => {
- if (hostSelect.value === 'local' || instance.federation === 'none') return '.';
- if (hostSelect.value === 'specified') return hostInput.value;
- return null;
-});
+ if (searchScope.value === 'user') {
+ if (user.value == null) return null;
+ return {
+ query: trimmedQuery,
+ host: fixHostIfLocal(user.value.host),
+ userId: user.value.id,
+ };
+ }
-if (props.userId != null) {
- misskeyApi('users/show', { userId: props.userId }).then(_user => {
- user.value = _user;
- });
-} else if (props.username != null) {
- misskeyApi('users/show', {
- username: props.username,
- ...(props.host != null && props.host !== '') ? { host: props.host } : {},
- }).then(_user => {
- user.value = _user;
- });
-}
+ if (instance.federation !== 'none' && searchScope.value === 'server') {
+ let trimmedHost = hostInput.value?.trim();
+ if (!trimmedHost) return null;
+ if (trimmedHost.startsWith('https://') || trimmedHost.startsWith('http://')) {
+ try {
+ trimmedHost = new URL(trimmedHost).host;
+ } catch (err) { /* empty */ }
+ }
+ return {
+ query: trimmedQuery,
+ host: fixHostIfLocal(trimmedHost),
+ };
+ }
+
+ if (instance.federation === 'none' || searchScope.value === 'local') {
+ return {
+ query: trimmedQuery,
+ host: '.',
+ };
+ }
+
+ return {
+ query: trimmedQuery,
+ };
+});
function selectUser() {
- os.selectUser({ includeSelf: true, localOnly: instance.noteSearchableScope === 'local' }).then(_user => {
+ os.selectUser({
+ includeSelf: true,
+ localOnly: instance.noteSearchableScope === 'local',
+ }).then(_user => {
user.value = _user;
- hostInput.value = _user.host ?? '';
});
}
function selectSelf() {
- user.value = $i as UserDetailed | null;
- hostInput.value = null;
+ user.value = $i;
}
function removeUser() {
user.value = null;
- hostInput.value = '';
}
async function search() {
- const query = searchQuery.value.toString().trim();
-
- if (query == null || query === '') return;
+ if (searchParams.value == null) return;
//#region AP lookup
- if (query.startsWith('http://') || query.startsWith('https://') && !query.includes(' ')) {
+ if ((searchParams.value.query.startsWith('http://') || searchParams.value.query.startsWith('https://')) && !searchParams.value.query.includes(' ')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
- const promise = misskeyApi('ap/show', {
- uri: query,
- });
-
- os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
-
- const res = await promise;
+ const res = await apLookup(searchParams.value.query);
if (res.type === 'User') {
router.push(`/@${res.object.username}@${res.object.host}`);
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (res.type === 'Note') {
router.push(`/notes/${res.object.id}`);
}
@@ -186,25 +291,25 @@ async function search() {
}
//#endregion
- if (query.length > 1 && !query.includes(' ')) {
- if (query.startsWith('@')) {
+ if (searchParams.value.query.length > 1 && !searchParams.value.query.includes(' ')) {
+ if (searchParams.value.query.startsWith('@')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
- router.push(`/${query}`);
+ router.push(`/${searchParams.value.query}`);
return;
}
}
- if (query.startsWith('#')) {
+ if (searchParams.value.query.startsWith('#')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.openTagPageConfirm,
});
if (!confirm.canceled) {
- router.push(`/tags/${encodeURIComponent(query.substring(1))}`);
+ router.push(`/tags/${encodeURIComponent(searchParams.value.query.substring(1))}`);
return;
}
}
@@ -214,9 +319,7 @@ async function search() {
endpoint: 'notes/search',
limit: 10,
params: {
- query: searchQuery.value,
- userId: user.value ? user.value.id : null,
- ...(searchHost.value ? { host: searchHost.value } : {}),
+ ...searchParams.value,
order: order.value ? 'desc' : 'asc',
filetype: filetype.value,
},
@@ -226,41 +329,48 @@ async function search() {
}
</script>
<style lang="scss" module>
-.userItem {
- display: flex;
- justify-content: center;
+.subOptionRoot {
+ background: var(--MI_THEME-panel);
+ border-radius: var(--MI-radius);
+ padding: var(--MI-margin);
}
-.addMeButton {
- border: 2px dashed var(--MI_THEME-fgTransparent);
- padding: 12px;
- margin-right: 16px;
+
+.userSelectLabel {
+ font-size: 0.85em;
+ padding: 0 0 8px;
+ user-select: none;
+}
+
+.userSelectButtons {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 16px;
}
-.addUserButton {
- border: 2px dashed var(--MI_THEME-fgTransparent);
+
+.userSelectButton {
+ width: 100%;
+ height: 100%;
padding: 12px;
- flex-grow: 1;
+ border: 2px dashed var(--MI_THEME-fgTransparent);
}
-.addUserButtonInner {
+
+.userSelectButtonInner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
min-height: 38px;
}
-.userCard {
- flex-grow: 1;
+
+.userSelectedButtons {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
}
-.remove {
+
+.userSelectedRemoveButton {
width: 32px;
height: 32px;
- align-self: center;
-
- & > i:before {
- color: #ff2a2a;
- }
-
- &:disabled {
- opacity: 0;
- }
+ color: #ff2a2a;
}
</style>
diff --git a/packages/frontend/src/pages/search.stories.impl.ts b/packages/frontend/src/pages/search.stories.impl.ts
index 0110a7ab8e..27271615c2 100644
--- a/packages/frontend/src/pages/search.stories.impl.ts
+++ b/packages/frontend/src/pages/search.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import search_ from './search.vue';
import { userDetailed } from '@/../.storybook/fakes.js';
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index 8d0899a30c..a10b93f18e 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFoldableSection v-if="userPagination">
<template #header>{{ i18n.ts.searchResult }}</template>
- <MkUserList :key="key" :pagination="userPagination"/>
+ <MkUserList :key="`searchUsers:${key}`" :pagination="userPagination"/>
</MkFoldableSection>
</div>
</template>
@@ -36,8 +36,8 @@ import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import * as os from '@/os.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useRouter } from '@/router/supplier.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { useRouter } from '@/router.js';
const props = withDefaults(defineProps<{
query?: string,
@@ -49,14 +49,16 @@ const props = withDefaults(defineProps<{
const router = useRouter();
-const key = ref('');
+const key = ref(0);
+const userPagination = ref<Paging<'users/search'>>();
+
const searchQuery = ref(toRef(props, 'query').value);
const searchOrigin = ref(toRef(props, 'origin').value);
-const userPagination = ref<Paging>();
async function search() {
const query = searchQuery.value.toString().trim();
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (query == null || query === '') return;
//#region AP lookup
@@ -76,6 +78,7 @@ async function search() {
if (res.type === 'User') {
router.push(`/@${res.object.username}@${res.object.host}`);
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (res.type === 'Note') {
router.push(`/notes/${res.object.id}`);
}
@@ -116,6 +119,6 @@ async function search() {
},
};
- key.value = query;
+ key.value++;
}
</script>
diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue
index 38d7548fa8..e0cb2dcbab 100644
--- a/packages/frontend/src/pages/search.vue
+++ b/packages/frontend/src/pages/search.vue
@@ -4,11 +4,9 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-
+<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <MkSpacer v-if="tab === 'note'" key="note" :contentMax="800">
+ <MkSpacer v-if="tab === 'note'" :contentMax="800">
<div v-if="notesSearchAvailable || ignoreNotesSearchAvailable">
<XNote v-bind="props"/>
</div>
@@ -17,18 +15,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkSpacer>
- <MkSpacer v-else-if="tab === 'user'" key="user" :contentMax="800">
+ <MkSpacer v-else-if="tab === 'user'" :contentMax="800">
<XUser v-bind="props"/>
</MkSpacer>
</MkHorizontalSwipe>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref, toRef } from 'vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { notesSearchAvailable } from '@/scripts/check-permissions.js';
+import { definePage } from '@/page.js';
+import { notesSearchAvailable } from '@/utility/check-permissions.js';
import MkInfo from '@/components/MkInfo.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
@@ -68,7 +66,7 @@ const headerTabs = computed(() => [{
icon: 'ti ti-users',
}]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.search,
icon: 'ti ti-search',
}));
diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
index 18c82ffdf6..03f973a33e 100644
--- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue
+++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
@@ -106,7 +106,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef, ref } from 'vue';
+import { hostname, port } from '@@/js/config';
+import { useTemplateRef, ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
@@ -116,10 +117,10 @@ import * as os from '@/os.js';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkLink from '@/components/MkLink.vue';
-import { confetti } from '@/scripts/confetti.js';
-import { signinRequired } from '@/account.js';
+import { confetti } from '@/utility/confetti.js';
+import { ensureSignin } from '@/i.js';
-const $i = signinRequired();
+const $i = ensureSignin();
defineProps<{
twoFactorData: {
@@ -132,7 +133,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
const page = ref(0);
const token = ref<string | number | null>(null);
const backupCodes = ref<string[]>();
@@ -159,9 +160,9 @@ async function tokenDone() {
function downloadBackupCodes() {
if (backupCodes.value !== undefined) {
const txtBlob = new Blob([backupCodes.value.join('\n')], { type: 'text/plain' });
- const dummya = document.createElement('a');
+ const dummya = window.document.createElement('a');
dummya.href = URL.createObjectURL(txtBlob);
- dummya.download = `${$i.username}-2fa-backup-codes.txt`;
+ dummya.download = `${$i.username}@${hostname}` + (port !== '' ? `_${port}` : '') + '-2fa-backup-codes.txt';
dummya.click();
}
}
diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue
index 776f59dda3..f47ffc984e 100644
--- a/packages/frontend/src/pages/settings/2fa.vue
+++ b/packages/frontend/src/pages/settings/2fa.vue
@@ -4,74 +4,82 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<FormSection :first="first">
- <template #label>{{ i18n.ts['2fa'] }}</template>
+<SearchMarker markerId="2fa" :keywords="['2fa']">
+ <FormSection :first="first">
+ <template #label><SearchLabel>{{ i18n.ts['2fa'] }}</SearchLabel></template>
- <div v-if="$i" class="_gaps_s">
- <MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'partial'" warn>
- {{ i18n.ts._2fa.backupCodeUsedWarning }}
- </MkInfo>
- <MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'none'" warn>
- {{ i18n.ts._2fa.backupCodesExhaustedWarning }}
- </MkInfo>
+ <div v-if="$i" class="_gaps_s">
+ <MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'partial'" warn>
+ {{ i18n.ts._2fa.backupCodeUsedWarning }}
+ </MkInfo>
+ <MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'none'" warn>
+ {{ i18n.ts._2fa.backupCodesExhaustedWarning }}
+ </MkInfo>
- <MkFolder :defaultOpen="true">
- <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(--MI_THEME-success)"></i></template>
+ <SearchMarker :keywords="['totp', 'app']">
+ <MkFolder :defaultOpen="true">
+ <template #icon><i class="ti ti-shield-lock"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.totp }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.totpDescription }}</SearchKeyword></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"/>
- <template v-if="$i.securityKeysList.length > 0">
- <MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
- <MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
- </template>
- <MkButton v-else danger @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
- </div>
+ <div v-if="$i.twoFactorEnabled" class="_gaps_s">
+ <div v-text="i18n.ts._2fa.alreadyRegistered"/>
+ <template v-if="$i.securityKeysList.length > 0">
+ <MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
+ <MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
+ </template>
+ <MkButton v-else danger @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
+ </div>
- <div v-else-if="!$i.twoFactorEnabled" class="_gaps_s">
- <MkButton primary gradate @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
- <MkLink url="https://misskey-hub.net/docs/for-users/stepped-guides/how-to-enable-2fa/" target="_blank"><i class="ti ti-help-circle"></i> {{ i18n.ts.learnMore }}</MkLink>
- </div>
- </MkFolder>
+ <div v-else-if="!$i.twoFactorEnabled" class="_gaps_s">
+ <MkButton primary gradate @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
+ <MkLink url="https://misskey-hub.net/docs/for-users/stepped-guides/how-to-enable-2fa/" target="_blank"><i class="ti ti-help-circle"></i> {{ i18n.ts.learnMore }}</MkLink>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-key"></i></template>
- <template #label>{{ i18n.ts.securityKeyAndPasskey }}</template>
- <div class="_gaps_s">
- <MkInfo>
- {{ i18n.ts._2fa.securityKeyInfo }}
- </MkInfo>
+ <SearchMarker :keywords="['security', 'key', 'passkey']">
+ <MkFolder>
+ <template #icon><i class="ti ti-key"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.securityKeyAndPasskey }}</SearchLabel></template>
+ <div class="_gaps_s">
+ <MkInfo>
+ {{ i18n.ts._2fa.securityKeyInfo }}
+ </MkInfo>
- <MkInfo v-if="!webAuthnSupported()" warn>
- {{ i18n.ts._2fa.securityKeyNotSupported }}
- </MkInfo>
+ <MkInfo v-if="!webAuthnSupported()" warn>
+ {{ i18n.ts._2fa.securityKeyNotSupported }}
+ </MkInfo>
- <MkInfo v-else-if="webAuthnSupported() && !$i.twoFactorEnabled" warn>
- {{ i18n.ts._2fa.registerTOTPBeforeKey }}
- </MkInfo>
+ <MkInfo v-else-if="webAuthnSupported() && !$i.twoFactorEnabled" warn>
+ {{ i18n.ts._2fa.registerTOTPBeforeKey }}
+ </MkInfo>
- <template v-else>
- <MkButton primary @click="addSecurityKey">{{ i18n.ts._2fa.registerSecurityKey }}</MkButton>
- <MkFolder v-for="key in $i.securityKeysList" :key="key.id">
- <template #label>{{ key.name }}</template>
- <template #suffix><I18n :src="i18n.ts.lastUsedAt"><template #t><MkTime :time="key.lastUsed"/></template></I18n></template>
- <div class="_buttons">
- <MkButton @click="renameKey(key)"><i class="ti ti-forms"></i> {{ i18n.ts.rename }}</MkButton>
- <MkButton danger @click="unregisterKey(key)"><i class="ti ti-trash"></i> {{ i18n.ts.unregister }}</MkButton>
- </div>
- </MkFolder>
- </template>
- </div>
- </MkFolder>
+ <template v-else>
+ <MkButton primary @click="addSecurityKey">{{ i18n.ts._2fa.registerSecurityKey }}</MkButton>
+ <MkFolder v-for="key in $i.securityKeysList" :key="key.id">
+ <template #label>{{ key.name }}</template>
+ <template #suffix><I18n :src="i18n.ts.lastUsedAt"><template #t><MkTime :time="key.lastUsed"/></template></I18n></template>
+ <div class="_buttons">
+ <MkButton @click="renameKey(key)"><i class="ti ti-forms"></i> {{ i18n.ts.rename }}</MkButton>
+ <MkButton danger @click="unregisterKey(key)"><i class="ti ti-trash"></i> {{ i18n.ts.unregister }}</MkButton>
+ </div>
+ </MkFolder>
+ </template>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
- <template #label>{{ i18n.ts.passwordLessLogin }}</template>
- <template #caption>{{ i18n.ts.passwordLessLoginDescription }}</template>
- </MkSwitch>
- </div>
-</FormSection>
+ <SearchMarker :keywords="['password', 'less', 'key', 'passkey', 'login', 'signin']">
+ <MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
+ <template #label><SearchLabel>{{ i18n.ts.passwordLessLogin }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.passwordLessLoginDescription }}</SearchKeyword></template>
+ </MkSwitch>
+ </SearchMarker>
+ </div>
+ </FormSection>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -84,10 +92,11 @@ 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, updateAccountPartial } from '@/account.js';
+import { ensureSignin } from '@/i.js';
import { i18n } from '@/i18n.js';
+import { updateCurrentAccountPartial } from '@/accounts.js';
-const $i = signinRequired();
+const $i = ensureSignin();
// メモ: 各エンドポイントはmeUpdatedを発行するため、refreshAccountは不要
@@ -123,7 +132,7 @@ async function unregisterTOTP(): Promise<void> {
password: auth.result.password,
token: auth.result.token,
}).then(res => {
- updateAccountPartial({
+ updateCurrentAccountPartial({
twoFactorEnabled: false,
});
}).catch(error => {
diff --git a/packages/frontend/src/pages/settings/accessibility.vue b/packages/frontend/src/pages/settings/accessibility.vue
new file mode 100644
index 0000000000..e8268719f5
--- /dev/null
+++ b/packages/frontend/src/pages/settings/accessibility.vue
@@ -0,0 +1,173 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<SearchMarker path="/settings/accessibility" :label="i18n.ts.accessibility" :keywords="['accessibility']" icon="ti ti-accessible">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/mens_room_3d.png" color="#0011ff">
+ <SearchKeyword>{{ i18n.ts._settings.accessibilityBanner }}</SearchKeyword>
+ </MkFeatureBanner>
+
+ <div class="_gaps_s">
+ <SearchMarker :keywords="['animation', 'motion', 'reduce']">
+ <MkPreferenceContainer k="animation">
+ <MkSwitch v-model="reduceAnimation">
+ <template #label><SearchLabel>{{ i18n.ts.reduceUiAnimation }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif']">
+ <MkPreferenceContainer k="disableShowingAnimatedImages">
+ <MkSwitch v-model="disableShowingAnimatedImages">
+ <template #label><SearchLabel>{{ i18n.ts.disableShowingAnimatedImages }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['mfm', 'enable', 'show', 'animated']">
+ <MkPreferenceContainer k="animatedMfm">
+ <MkSwitch v-model="animatedMfm">
+ <template #label><SearchLabel>{{ i18n.ts.enableAnimatedMfm }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['swipe', 'horizontal', 'tab']">
+ <MkPreferenceContainer k="enableHorizontalSwipe">
+ <MkSwitch v-model="enableHorizontalSwipe">
+ <template #label><SearchLabel>{{ i18n.ts.enableHorizontalSwipe }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['keep', 'screen', 'display', 'on']">
+ <MkPreferenceContainer k="keepScreenOn">
+ <MkSwitch v-model="keepScreenOn">
+ <template #label><SearchLabel>{{ i18n.ts.keepScreenOn }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['native', 'system', 'video', 'audio', 'player', 'media']">
+ <MkPreferenceContainer k="useNativeUiForVideoAudioPlayer">
+ <MkSwitch v-model="useNativeUiForVideoAudioPlayer">
+ <template #label><SearchLabel>{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['text', 'selectable']">
+ <MkPreferenceContainer k="makeEveryTextElementsSelectable">
+ <MkSwitch v-model="makeEveryTextElementsSelectable">
+ <template #label><SearchLabel>{{ i18n.ts._settings.makeEveryTextElementsSelectable }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts._settings.makeEveryTextElementsSelectable_description }}</template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+
+ <SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']">
+ <MkPreferenceContainer k="menuStyle">
+ <MkSelect v-model="menuStyle">
+ <template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template>
+ <option value="auto">{{ i18n.ts.auto }}</option>
+ <option value="popup">{{ i18n.ts.popup }}</option>
+ <option value="drawer">{{ i18n.ts.drawer }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['contextmenu', 'system', 'native']">
+ <MkPreferenceContainer k="contextMenu">
+ <MkSelect v-model="contextMenu">
+ <template #label><SearchLabel>{{ i18n.ts._contextMenu.title }}</SearchLabel></template>
+ <option value="app">{{ i18n.ts._contextMenu.app }}</option>
+ <option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option>
+ <option value="native">{{ i18n.ts._contextMenu.native }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['font', 'size']">
+ <MkRadios v-model="fontSize">
+ <template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template>
+ <option :value="null"><span style="font-size: 14px;">Aa</span></option>
+ <option value="1"><span style="font-size: 15px;">Aa</span></option>
+ <option value="2"><span style="font-size: 16px;">Aa</span></option>
+ <option value="3"><span style="font-size: 17px;">Aa</span></option>
+ </MkRadios>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['font', 'system', 'native']">
+ <MkSwitch v-model="useSystemFont">
+ <template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
+ </div>
+</SearchMarker>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from 'vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkSelect from '@/components/MkSelect.vue';
+import { prefer } from '@/preferences.js';
+import { reloadAsk } from '@/utility/reload-ask.js';
+import { i18n } from '@/i18n.js';
+import { definePage } from '@/page.js';
+import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
+import { miLocalStorage } from '@/local-storage.js';
+import MkRadios from '@/components/MkRadios.vue';
+
+const reduceAnimation = prefer.model('animation', v => !v, v => !v);
+const animatedMfm = prefer.model('animatedMfm');
+const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages');
+const keepScreenOn = prefer.model('keepScreenOn');
+const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
+const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
+const contextMenu = prefer.model('contextMenu');
+const menuStyle = prefer.model('menuStyle');
+const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable');
+
+const fontSize = ref(miLocalStorage.getItem('fontSize'));
+const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
+
+watch(fontSize, () => {
+ if (fontSize.value == null) {
+ miLocalStorage.removeItem('fontSize');
+ } else {
+ miLocalStorage.setItem('fontSize', fontSize.value);
+ }
+});
+
+watch(useSystemFont, () => {
+ if (useSystemFont.value) {
+ miLocalStorage.setItem('useSystemFont', 't');
+ } else {
+ miLocalStorage.removeItem('useSystemFont');
+ }
+});
+
+watch([
+ keepScreenOn,
+ contextMenu,
+ fontSize,
+ useSystemFont,
+ makeEveryTextElementsSelectable,
+], async () => {
+ await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
+});
+
+const headerActions = computed(() => []);
+
+const headerTabs = computed(() => []);
+
+definePage(() => ({
+ title: i18n.ts.accessibility,
+ icon: 'ti ti-accessible',
+}));
+</script>
diff --git a/packages/frontend/src/pages/settings/account-data.vue b/packages/frontend/src/pages/settings/account-data.vue
new file mode 100644
index 0000000000..14bea577a3
--- /dev/null
+++ b/packages/frontend/src/pages/settings/account-data.vue
@@ -0,0 +1,277 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<SearchMarker path="/settings/account-data" :label="i18n.ts._settings.accountData" :keywords="['import', 'export', 'data', 'archive']" icon="ti ti-package">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/package_3d.png" color="#ff9100">
+ <SearchKeyword>{{ i18n.ts._settings.accountDataBanner }}</SearchKeyword>
+ </MkFeatureBanner>
+
+ <div class="_gaps_s">
+ <SearchMarker :keywords="['notes']">
+ <MkFolder>
+ <template #icon><i class="ti ti-pencil"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._exportOrImport.allNotes }}</SearchLabel></template>
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.export }}</template>
+ <template #icon><i class="ti ti-download"></i></template>
+ <MkButton primary :class="$style.button" inline @click="exportNotes()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+ </MkFolder>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['favorite', 'notes']">
+ <MkFolder>
+ <template #icon><i class="ti ti-star"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._exportOrImport.favoritedNotes }}</SearchLabel></template>
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.export }}</template>
+ <template #icon><i class="ti ti-download"></i></template>
+ <MkButton primary :class="$style.button" inline @click="exportFavorites()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+ </MkFolder>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['clip', 'notes']">
+ <MkFolder>
+ <template #icon><i class="ti ti-star"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._exportOrImport.clips }}</SearchLabel></template>
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.export }}</template>
+ <template #icon><i class="ti ti-download"></i></template>
+ <MkButton primary :class="$style.button" inline @click="exportClips()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+ </MkFolder>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['following', 'users']">
+ <MkFolder>
+ <template #icon><i class="ti ti-users"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._exportOrImport.followingList }}</SearchLabel></template>
+ <div class="_gaps_s">
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.export }}</template>
+ <template #icon><i class="ti ti-download"></i></template>
+ <div class="_gaps_s">
+ <MkSwitch v-model="excludeMutingUsers">
+ {{ i18n.ts._exportOrImport.excludeMutingUsers }}
+ </MkSwitch>
+ <MkSwitch v-model="excludeInactiveUsers">
+ {{ i18n.ts._exportOrImport.excludeInactiveUsers }}
+ </MkSwitch>
+ <MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+ </div>
+ </MkFolder>
+ <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing" :defaultOpen="true">
+ <template #label>{{ i18n.ts.import }}</template>
+ <template #icon><i class="ti ti-upload"></i></template>
+ <MkSwitch v-model="withReplies">
+ {{ i18n.ts._exportOrImport.withReplies }}
+ </MkSwitch>
+ <MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
+ </MkFolder>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['user', 'lists']">
+ <MkFolder>
+ <template #icon><i class="ti ti-users"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._exportOrImport.userLists }}</SearchLabel></template>
+ <div class="_gaps_s">
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.export }}</template>
+ <template #icon><i class="ti ti-download"></i></template>
+ <MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+ </MkFolder>
+ <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists" :defaultOpen="true">
+ <template #label>{{ i18n.ts.import }}</template>
+ <template #icon><i class="ti ti-upload"></i></template>
+ <MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
+ </MkFolder>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['mute', 'users']">
+ <MkFolder>
+ <template #icon><i class="ti ti-user-off"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._exportOrImport.muteList }}</SearchLabel></template>
+ <div class="_gaps_s">
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.export }}</template>
+ <template #icon><i class="ti ti-download"></i></template>
+ <MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+ </MkFolder>
+ <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting" :defaultOpen="true">
+ <template #label>{{ i18n.ts.import }}</template>
+ <template #icon><i class="ti ti-upload"></i></template>
+ <MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
+ </MkFolder>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['block', 'users']">
+ <MkFolder>
+ <template #icon><i class="ti ti-user-off"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._exportOrImport.blockingList }}</SearchLabel></template>
+ <div class="_gaps_s">
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.export }}</template>
+ <template #icon><i class="ti ti-download"></i></template>
+ <MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+ </MkFolder>
+ <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking" :defaultOpen="true">
+ <template #label>{{ i18n.ts.import }}</template>
+ <template #icon><i class="ti ti-upload"></i></template>
+ <MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
+ </MkFolder>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['antennas']">
+ <MkFolder>
+ <template #icon><i class="ti ti-antenna"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.antennas }}</SearchLabel></template>
+ <div class="_gaps_s">
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.export }}</template>
+ <template #icon><i class="ti ti-download"></i></template>
+ <MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+ </MkFolder>
+ <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas" :defaultOpen="true">
+ <template #label>{{ i18n.ts.import }}</template>
+ <template #icon><i class="ti ti-upload"></i></template>
+ <MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
+ </MkFolder>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+ </div>
+ </div>
+</SearchMarker>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed } from 'vue';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import * as os from '@/os.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { selectFile } from '@/utility/select-file.js';
+import { i18n } from '@/i18n.js';
+import { definePage } from '@/page.js';
+import { $i } from '@/i.js';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
+import { prefer } from '@/preferences.js';
+
+const excludeMutingUsers = ref(false);
+const excludeInactiveUsers = ref(false);
+const withReplies = ref(prefer.s.defaultFollowWithReplies);
+
+const onExportSuccess = () => {
+ os.alert({
+ type: 'info',
+ text: i18n.ts.exportRequested,
+ });
+};
+
+const onImportSuccess = () => {
+ os.alert({
+ type: 'info',
+ text: i18n.ts.importRequested,
+ });
+};
+
+const onError = (ev) => {
+ os.alert({
+ type: 'error',
+ text: ev.message,
+ });
+};
+
+const exportNotes = () => {
+ misskeyApi('i/export-notes', {}).then(onExportSuccess).catch(onError);
+};
+
+const exportFavorites = () => {
+ misskeyApi('i/export-favorites', {}).then(onExportSuccess).catch(onError);
+};
+
+const exportClips = () => {
+ misskeyApi('i/export-clips', {}).then(onExportSuccess).catch(onError);
+};
+
+const exportFollowing = () => {
+ misskeyApi('i/export-following', {
+ excludeMuting: excludeMutingUsers.value,
+ excludeInactive: excludeInactiveUsers.value,
+ })
+ .then(onExportSuccess).catch(onError);
+};
+
+const exportBlocking = () => {
+ misskeyApi('i/export-blocking', {}).then(onExportSuccess).catch(onError);
+};
+
+const exportUserLists = () => {
+ misskeyApi('i/export-user-lists', {}).then(onExportSuccess).catch(onError);
+};
+
+const exportMuting = () => {
+ misskeyApi('i/export-mute', {}).then(onExportSuccess).catch(onError);
+};
+
+const exportAntennas = () => {
+ misskeyApi('i/export-antennas', {}).then(onExportSuccess).catch(onError);
+};
+
+const importFollowing = async (ev) => {
+ const file = await selectFile(ev.currentTarget ?? ev.target);
+ misskeyApi('i/import-following', {
+ fileId: file.id,
+ withReplies: withReplies.value,
+ }).then(onImportSuccess).catch(onError);
+};
+
+const importUserLists = async (ev) => {
+ const file = await selectFile(ev.currentTarget ?? ev.target);
+ misskeyApi('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
+
+const importMuting = async (ev) => {
+ const file = await selectFile(ev.currentTarget ?? ev.target);
+ misskeyApi('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
+
+const importBlocking = async (ev) => {
+ const file = await selectFile(ev.currentTarget ?? ev.target);
+ misskeyApi('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
+
+const importAntennas = async (ev) => {
+ const file = await selectFile(ev.currentTarget ?? ev.target);
+ misskeyApi('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
+
+const headerActions = computed(() => []);
+
+const headerTabs = computed(() => []);
+
+definePage(() => ({
+ title: i18n.ts._settings.accountData,
+ icon: 'ti ti-package',
+}));
+</script>
+
+<style module>
+.button {
+ margin-right: 16px;
+}
+</style>
diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index c2588736b3..2fd0a021da 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -4,80 +4,50 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="">
- <FormSuspense :p="init">
- <div class="_gaps">
- <div class="_buttons">
- <MkButton primary @click="addAccount"><i class="ti ti-plus"></i> {{ i18n.ts.addAccount }}</MkButton>
- <MkButton @click="init"><i class="ti ti-refresh"></i> {{ i18n.ts.reloadAccountsList }}</MkButton>
- </div>
-
- <template v-for="[id, user] in accounts">
- <MkUserCardMini v-if="user != null" :key="user.id" :user="user" :class="$style.user" @click.prevent="menu(user, $event)"/>
- <button v-else v-panel class="_button" :class="$style.unknownUser" @click="menu(id, $event)">
- <div :class="$style.unknownUserAvatarMock"><i class="ti ti-user-question"></i></div>
- <div>
- <div :class="$style.unknownUserTitle">{{ i18n.ts.unknown }}</div>
- <div :class="$style.unknownUserSub">ID: <span class="_monospace">{{ id }}</span></div>
- </div>
- </button>
- </template>
+<SearchMarker path="/settings/accounts" :label="i18n.ts.accounts" :keywords="['accounts']" icon="ti ti-users">
+ <div class="_gaps">
+ <div class="_buttons">
+ <MkButton primary @click="addAccount"><i class="ti ti-plus"></i> {{ i18n.ts.addAccount }}</MkButton>
+ <!--<MkButton @click="refreshAllAccounts"><i class="ti ti-refresh"></i></MkButton>-->
</div>
- </FormSuspense>
-</div>
+
+ <MkUserCardMini v-for="x in accounts" :key="x[0] + x[1].id" :user="x[1]" :class="$style.user" @click.prevent="menu(x[0], x[1], $event)"/>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
-import type * as Misskey from 'misskey-js';
-import FormSuspense from '@/components/form/suspense.vue';
+import * as Misskey from 'misskey-js';
+import type { MenuItem } from '@/types/menu.js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { getAccounts, removeAccount as _removeAccount, login, $i, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { $i } from '@/i.js';
+import { switchAccount, removeAccount, login, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/accounts.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import { MenuItem } from '@/types/menu';
-
-const storedAccounts = ref<{ id: string, token: string }[] | null>(null);
-const accounts = ref(new Map<string, Misskey.entities.UserDetailed | null>());
+import { prefer } from '@/preferences.js';
-const init = async () => {
- getAccounts().then(accounts => {
- storedAccounts.value = accounts.filter(x => x.id !== $i!.id);
+const accounts = prefer.r.accounts;
- return misskeyApi('users/show', {
- userIds: storedAccounts.value.map(x => x.id),
- });
- }).then(response => {
- if (storedAccounts.value == null) return;
- accounts.value = new Map(storedAccounts.value.map(x => [x.id, response.find((y: Misskey.entities.UserDetailed) => y.id === x.id) ?? null]));
- });
-};
+function refreshAllAccounts() {
+ // TODO
+}
-function menu(account: Misskey.entities.UserDetailed | string, ev: MouseEvent) {
+function menu(host: string, account: Misskey.entities.UserDetailed, ev: MouseEvent) {
let menu: MenuItem[];
- if (typeof account === 'string') {
- menu = [{
- text: i18n.ts.logout,
- icon: 'ti ti-trash',
- danger: true,
- action: () => removeAccount(account),
- }];
- } else {
- menu = [{
- text: i18n.ts.switch,
- icon: 'ti ti-switch-horizontal',
- action: () => switchAccount(account.id),
- }, {
- text: i18n.ts.logout,
- icon: 'ti ti-trash',
- danger: true,
- action: () => removeAccount(account.id),
- }];
- }
+ menu = [{
+ text: i18n.ts.switch,
+ icon: 'ti ti-switch-horizontal',
+ action: () => switchAccount(host, account.id),
+ }, {
+ text: i18n.ts.remove,
+ icon: 'ti ti-trash',
+ action: () => removeAccount(host, account.id),
+ }];
os.popupMenu(menu, ev.currentTarget ?? ev.target);
}
@@ -92,16 +62,10 @@ function addAccount(ev: MouseEvent) {
}], ev.currentTarget ?? ev.target);
}
-async function removeAccount(id: string) {
- await _removeAccount(id);
- accounts.value.delete(id);
-}
-
function addExistingAccount() {
getAccountWithSigninDialog().then((res) => {
if (res != null) {
os.success();
- init();
}
});
}
@@ -109,26 +73,16 @@ function addExistingAccount() {
function createAccount() {
getAccountWithSignupDialog().then((res) => {
if (res != null) {
- switchAccountWithToken(res.token);
+ login(res.token);
}
});
}
-async function switchAccount(id: string) {
- const fetchedAccounts = await getAccounts();
- const token = fetchedAccounts.find(x => x.id === id)!.token;
- switchAccountWithToken(token);
-}
-
-function switchAccountWithToken(token: string) {
- login(token);
-}
-
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.accounts,
icon: 'ti ti-users',
}));
diff --git a/packages/frontend/src/pages/settings/api.vue b/packages/frontend/src/pages/settings/api.vue
deleted file mode 100644
index b35d406a98..0000000000
--- a/packages/frontend/src/pages/settings/api.vue
+++ /dev/null
@@ -1,53 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div class="_gaps_m">
- <MkButton primary @click="generateToken">{{ i18n.ts.generateAccessToken }}</MkButton>
- <FormLink to="/settings/apps">{{ i18n.ts.manageAccessTokens }}</FormLink>
- <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { defineAsyncComponent, ref, computed } from 'vue';
-import FormLink from '@/components/form/link.vue';
-import MkButton from '@/components/MkButton.vue';
-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';
-
-const isDesktop = ref(window.innerWidth >= 1100);
-
-function generateToken() {
- const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {}, {
- done: async result => {
- const { name, permissions } = result;
- const { token } = await misskeyApi('miauth/gen-token', {
- session: null,
- name: name,
- permission: permissions,
- });
-
- os.alert({
- type: 'success',
- title: i18n.ts.token,
- text: token,
- });
- },
- closed: () => dispose(),
- });
-}
-
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => []);
-
-definePageMetadata(() => ({
- title: 'API',
- icon: 'ti ti-api',
-}));
-</script>
diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue
index 6515503505..c72179b9a1 100644
--- a/packages/frontend/src/pages/settings/apps.vue
+++ b/packages/frontend/src/pages/settings/apps.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormPagination ref="list" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</template>
@@ -57,9 +57,9 @@ SPDX-License-Identifier: AGPL-3.0-only
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 { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
@@ -86,7 +86,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.installedApps,
icon: 'ti ti-plug',
}));
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
index c71595380e..d3bec4a19e 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
@@ -16,9 +16,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed } from 'vue';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const props = defineProps<{
active?: boolean;
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
index 77f9d4af20..1fd977cbd4 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
@@ -48,15 +48,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef, ref, computed } from 'vue';
+import { useTemplateRef, ref, computed } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { i18n } from '@/i18n.js';
import MkRange from '@/components/MkRange.vue';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const props = defineProps<{
usingIndex: number | null;
@@ -87,7 +87,7 @@ const emit = defineEmits<{
(ev: 'detach'): void;
}>();
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
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);
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue
index efcd2a5f3f..e0f964a23b 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.vue
@@ -4,45 +4,47 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div>
- <div v-if="!loading" class="_gaps">
- <MkInfo>{{ i18n.tsx._profile.avatarDecorationMax({ max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.tsx.remainingN({ n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
+<SearchMarker path="/settings/avatar-decoration" :label="i18n.ts.avatarDecorations" :keywords="['avatar', 'icon', 'decoration']" icon="ti ti-sparkles">
+ <div>
+ <div v-if="!loading" class="_gaps">
+ <MkInfo>{{ i18n.tsx._profile.avatarDecorationMax({ max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.tsx.remainingN({ n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
- <MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
+ <MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
- <div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
- <div>{{ i18n.ts.inUse }}</div>
+ <div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
+ <div>{{ i18n.ts.inUse }}</div>
+
+ <div :class="$style.decorations">
+ <XDecoration
+ v-for="(avatarDecoration, i) in $i.avatarDecorations"
+ :decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
+ :angle="avatarDecoration.angle"
+ :flipH="avatarDecoration.flipH"
+ :offsetX="avatarDecoration.offsetX"
+ :offsetY="avatarDecoration.offsetY"
+ :showBelow="avatarDecoration.showBelow"
+ :active="true"
+ @click="openDecoration(avatarDecoration, i)"
+ />
+ </div>
+
+ <MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
+ </div>
<div :class="$style.decorations">
<XDecoration
- v-for="(avatarDecoration, i) in $i.avatarDecorations"
- :decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
- :angle="avatarDecoration.angle"
- :flipH="avatarDecoration.flipH"
- :offsetX="avatarDecoration.offsetX"
- :offsetY="avatarDecoration.offsetY"
- :showBelow="avatarDecoration.showBelow"
- :active="true"
- @click="openDecoration(avatarDecoration, i)"
+ v-for="avatarDecoration in avatarDecorations"
+ :key="avatarDecoration.id"
+ :decoration="avatarDecoration"
+ @click="openDecoration(avatarDecoration)"
/>
</div>
-
- <MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
</div>
-
- <div :class="$style.decorations">
- <XDecoration
- v-for="avatarDecoration in avatarDecorations"
- :key="avatarDecoration.id"
- :decoration="avatarDecoration"
- @click="openDecoration(avatarDecoration)"
- />
+ <div v-else>
+ <MkLoading/>
</div>
</div>
- <div v-else>
- <MkLoading/>
- </div>
-</div>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -51,13 +53,13 @@ import * as Misskey from 'misskey-js';
import XDecoration from './avatar-decoration.decoration.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
import MkInfo from '@/components/MkInfo.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const loading = ref(true);
const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]);
@@ -132,7 +134,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.avatarDecorations,
icon: 'ti ti-sparkles',
}));
diff --git a/packages/frontend/src/pages/settings/connect.vue b/packages/frontend/src/pages/settings/connect.vue
new file mode 100644
index 0000000000..280ee546dc
--- /dev/null
+++ b/packages/frontend/src/pages/settings/connect.vue
@@ -0,0 +1,112 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<SearchMarker path="/settings/connect" :label="i18n.ts._settings.serviceConnection" :keywords="['app', 'service', 'connect', 'webhook', 'api', 'token']" icon="ti ti-link">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/link_3d.png" color="#ff0088">
+ <SearchKeyword>{{ i18n.ts._settings.serviceConnectionBanner }}</SearchKeyword>
+ </MkFeatureBanner>
+
+ <SearchMarker :keywords="['api', 'app', 'token', 'accessToken']">
+ <FormSection>
+ <template #label><i class="ti ti-api"></i> <SearchLabel>{{ i18n.ts._settings.api }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <MkButton primary @click="generateToken">{{ i18n.ts.generateAccessToken }}</MkButton>
+ <FormLink to="/settings/apps">{{ i18n.ts.manageAccessTokens }}</FormLink>
+ <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink>
+ </div>
+ </FormSection>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['webhook']">
+ <FormSection>
+ <template #label><i class="ti ti-webhook"></i> <SearchLabel>{{ i18n.ts._settings.webhook }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <FormLink :to="`/settings/webhook/new`">
+ {{ i18n.ts._webhookSettings.createWebhook }}
+ </FormLink>
+
+ <MkFolder :defaultOpen="true">
+ <template #label>{{ i18n.ts.manage }}</template>
+
+ <MkPagination :pagination="pagination">
+ <template #default="{items}">
+ <div class="_gaps">
+ <FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`">
+ <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(--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>
+ <MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime>
+ </template>
+ </FormLink>
+ </div>
+ </template>
+ </MkPagination>
+ </MkFolder>
+ </div>
+ </FormSection>
+ </SearchMarker>
+ </div>
+</SearchMarker>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, defineAsyncComponent } from 'vue';
+import MkPagination from '@/components/MkPagination.vue';
+import FormSection from '@/components/form/section.vue';
+import FormLink from '@/components/form/link.vue';
+import { definePage } from '@/page.js';
+import { i18n } from '@/i18n.js';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
+import * as os from '@/os.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+
+const isDesktop = ref(window.innerWidth >= 1100);
+
+const pagination = {
+ endpoint: 'i/webhooks/list' as const,
+ limit: 100,
+ noPaging: true,
+};
+
+function generateToken() {
+ const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {}, {
+ done: async result => {
+ const { name, permissions } = result;
+ const { token } = await misskeyApi('miauth/gen-token', {
+ session: null,
+ name: name,
+ permission: permissions,
+ });
+
+ os.alert({
+ type: 'success',
+ title: i18n.ts.token,
+ text: token,
+ });
+ },
+ closed: () => dispose(),
+ });
+}
+
+const headerActions = computed(() => []);
+
+const headerTabs = computed(() => []);
+
+definePage(() => ({
+ title: i18n.ts._settings.serviceConnection,
+ icon: 'ti ti-link',
+}));
+</script>
diff --git a/packages/frontend/src/pages/settings/custom-css.vue b/packages/frontend/src/pages/settings/custom-css.vue
index cf05e75acc..9b0e04860e 100644
--- a/packages/frontend/src/pages/settings/custom-css.vue
+++ b/packages/frontend/src/pages/settings/custom-css.vue
@@ -18,9 +18,9 @@ import { ref, watch, computed } from 'vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
import FormInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { unisonReload } from '@/utility/unison-reload.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { miLocalStorage } from '@/local-storage.js';
const localCustomCss = ref(miLocalStorage.getItem('customCss') ?? '');
@@ -45,7 +45,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.customCss,
icon: 'ti ti-code',
}));
diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue
index e574ec7dc0..9b2b40374e 100644
--- a/packages/frontend/src/pages/settings/deck.vue
+++ b/packages/frontend/src/pages/settings/deck.vue
@@ -4,39 +4,84 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <MkSwitch v-model="useSimpleUiForNonRootPages">{{ i18n.ts._deck.useSimpleUiForNonRootPages }}</MkSwitch>
+<SearchMarker path="/settings/deck" :label="i18n.ts.deck" :keywords="['deck', 'ui']" icon="ti ti-columns">
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['sync', 'profiles', 'devices']">
+ <MkSwitch :modelValue="profilesSyncEnabled" @update:modelValue="changeProfilesSyncEnabled">
+ <template #label><SearchLabel>{{ i18n.ts._deck.enableSyncBetweenDevicesForProfiles }}</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="navWindow">{{ i18n.ts.defaultNavigationBehaviour }}: {{ i18n.ts.openInWindow }}</MkSwitch>
+ <SearchMarker :keywords="['ui', 'root', 'page']">
+ <MkPreferenceContainer k="deck.useSimpleUiForNonRootPages">
+ <MkSwitch v-model="useSimpleUiForNonRootPages">
+ <template #label><SearchLabel>{{ i18n.ts._deck.useSimpleUiForNonRootPages }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
- <MkSwitch v-model="alwaysShowMainColumn">{{ i18n.ts._deck.alwaysShowMainColumn }}</MkSwitch>
+ <SearchMarker :keywords="['default', 'navigation', 'behaviour', 'window']">
+ <MkPreferenceContainer k="deck.navWindow">
+ <MkSwitch v-model="navWindow">
+ <template #label><SearchLabel>{{ i18n.ts.defaultNavigationBehaviour }}</SearchLabel>: {{ i18n.ts.openInWindow }}</template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
- <MkRadios v-model="columnAlign">
- <template #label>{{ i18n.ts._deck.columnAlign }}</template>
- <option value="left">{{ i18n.ts.left }}</option>
- <option value="center">{{ i18n.ts.center }}</option>
- </MkRadios>
-</div>
+ <SearchMarker :keywords="['always', 'show', 'main', 'column']">
+ <MkPreferenceContainer k="deck.alwaysShowMainColumn">
+ <MkSwitch v-model="alwaysShowMainColumn">
+ <template #label><SearchLabel>{{ i18n.ts._deck.alwaysShowMainColumn }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['column', 'align']">
+ <MkPreferenceContainer k="deck.columnAlign">
+ <MkRadios v-model="columnAlign">
+ <template #label><SearchLabel>{{ i18n.ts._deck.columnAlign }}</SearchLabel></template>
+ <option value="left">{{ i18n.ts.left }}</option>
+ <option value="center">{{ i18n.ts.center }}</option>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
-import { deckStore } from '@/ui/deck/deck-store.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
+import { prefer } from '@/preferences.js';
+import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
+
+const navWindow = prefer.model('deck.navWindow');
+const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages');
+const alwaysShowMainColumn = prefer.model('deck.alwaysShowMainColumn');
+const columnAlign = prefer.model('deck.columnAlign');
+
+const profilesSyncEnabled = ref(prefer.isSyncEnabled('deck.profiles'));
-const navWindow = computed(deckStore.makeGetterSetter('navWindow'));
-const useSimpleUiForNonRootPages = computed(deckStore.makeGetterSetter('useSimpleUiForNonRootPages'));
-const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn'));
-const columnAlign = computed(deckStore.makeGetterSetter('columnAlign'));
+function changeProfilesSyncEnabled(value: boolean) {
+ if (value) {
+ prefer.enableSync('deck.profiles').then((res) => {
+ if (res == null) return;
+ if (res.enabled) profilesSyncEnabled.value = true;
+ });
+ } else {
+ prefer.disableSync('deck.profiles');
+ profilesSyncEnabled.value = false;
+ }
+}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.deck,
icon: 'ti ti-columns',
}));
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
index 1ad3613e4b..a65a1e6e71 100644
--- a/packages/frontend/src/pages/settings/drive-cleaner.vue
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -48,19 +48,21 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { computed, ref, shallowRef, watch, type StyleValue } from 'vue';
+import { computed, ref, shallowRef, watch } from 'vue';
+import type { StyleValue } from 'vue';
import tinycolor from 'tinycolor2';
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 { misskeyApi } from '@/utility/misskey-api.js';
import MkPagination from '@/components/MkPagination.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import { i18n } from '@/i18n.js';
import bytes from '@/filters/bytes.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkSelect from '@/components/MkSelect.vue';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
@@ -162,7 +164,7 @@ function onContextMenu(ev: MouseEvent, file): void {
os.contextMenu(getDriveFileMenu(file), ev);
}
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.drivecleaner,
icon: 'ti ti-trash',
}));
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index eef2e5b505..57161aa666 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -4,56 +4,82 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <FormSection v-if="!fetching" first>
- <template #label>{{ i18n.ts.usageAmount }}</template>
+<SearchMarker path="/settings/drive" :label="i18n.ts.drive" :keywords="['drive']" icon="ti ti-cloud">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/cloud_3d.png" color="#0059ff">
+ <SearchKeyword>{{ i18n.ts._settings.driveBanner }}</SearchKeyword>
+ </MkFeatureBanner>
- <div class="_gaps_m">
- <div>
- <div :class="$style.meter"><div :class="$style.meterValue" :style="meterStyle"></div></div>
- </div>
- <FormSplit>
- <MkKeyValue>
- <template #key>{{ i18n.ts.capacity }}</template>
- <template #value>{{ bytes(capacity, 1) }}</template>
- </MkKeyValue>
- <MkKeyValue>
- <template #key>{{ i18n.ts.inUse }}</template>
- <template #value>{{ bytes(usage, 1) }}</template>
- </MkKeyValue>
- </FormSplit>
- </div>
- </FormSection>
+ <SearchMarker :keywords="['capacity', 'usage']">
+ <FormSection first>
+ <template #label><SearchLabel>{{ i18n.ts.usageAmount }}</SearchLabel></template>
+
+ <div v-if="!fetching" class="_gaps_m">
+ <div>
+ <div :class="$style.meter"><div :class="$style.meterValue" :style="meterStyle"></div></div>
+ </div>
+ <FormSplit>
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.capacity }}</template>
+ <template #value>{{ bytes(capacity, 1) }}</template>
+ </MkKeyValue>
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.inUse }}</template>
+ <template #value>{{ bytes(usage, 1) }}</template>
+ </MkKeyValue>
+ </FormSplit>
+ </div>
+ </FormSection>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['statistics', 'usage']">
+ <FormSection>
+ <template #label><SearchLabel>{{ i18n.ts.statistics }}</SearchLabel></template>
+ <MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspectRatio="6"/>
+ </FormSection>
+ </SearchMarker>
+
+ <FormSection>
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['default', 'upload', 'folder']">
+ <FormLink to="" @click="chooseUploadFolder()">
+ <SearchLabel>{{ i18n.ts.uploadFolder }}</SearchLabel>
+ <template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
+ <template #suffixIcon><i class="ti ti-folder"></i></template>
+ </FormLink>
+ </SearchMarker>
- <FormSection>
- <template #label>{{ i18n.ts.statistics }}</template>
- <MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspectRatio="6"/>
- </FormSection>
+ <FormLink to="/settings/drive/cleaner">
+ {{ i18n.ts.drivecleaner }}
+ </FormLink>
- <FormSection>
- <div class="_gaps_m">
- <FormLink to="" @click="chooseUploadFolder()">
- {{ i18n.ts.uploadFolder }}
- <template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
- <template #suffixIcon><i class="ti ti-folder"></i></template>
- </FormLink>
- <FormLink to="/settings/drive/cleaner">
- {{ i18n.ts.drivecleaner }}
- </FormLink>
- <MkSwitch v-model="keepOriginalUploading">
- <template #label>{{ i18n.ts.keepOriginalUploading }}</template>
- <template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
- </MkSwitch>
- <MkSwitch v-model="keepOriginalFilename">
- <template #label>{{ i18n.ts.keepOriginalFilename }}</template>
- <template #caption>{{ i18n.ts.keepOriginalFilenameDescription }}</template>
- </MkSwitch>
- <MkSwitch v-model="defaultSensitive" @update:modelValue="saveProfile()">
- <template #label>{{ i18n.ts.alwaysMarkSensitive }}</template>
- </MkSwitch>
- </div>
- </FormSection>
-</div>
+ <SearchMarker :keywords="['keep', 'original', 'raw', 'upload']">
+ <MkPreferenceContainer k="keepOriginalUploading">
+ <MkSwitch v-model="keepOriginalUploading">
+ <template #label><SearchLabel>{{ i18n.ts.keepOriginalUploading }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.keepOriginalUploadingDescription }}</SearchKeyword></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['keep', 'original', 'filename']">
+ <MkPreferenceContainer k="keepOriginalFilename">
+ <MkSwitch v-model="keepOriginalFilename">
+ <template #label><SearchLabel>{{ i18n.ts.keepOriginalFilename }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchKeyword></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file']">
+ <MkSwitch v-model="defaultSensitive" @update:modelValue="saveProfile()">
+ <template #label><SearchLabel>{{ i18n.ts.alwaysMarkSensitive }}</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
+ </div>
+ </FormSection>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -66,15 +92,17 @@ import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import bytes from '@/filters/bytes.js';
-import { defaultStore } from '@/store.js';
import MkChart from '@/components/MkChart.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { signinRequired } from '@/account.js';
+import { definePage } from '@/page.js';
+import { ensureSignin } from '@/i.js';
+import { prefer } from '@/preferences.js';
+import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
-const $i = signinRequired();
+const $i = ensureSignin();
const fetching = ref(true);
const usage = ref<number | null>(null);
@@ -94,8 +122,8 @@ const meterStyle = computed(() => {
};
});
-const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading'));
-const keepOriginalFilename = computed(defaultStore.makeGetterSetter('keepOriginalFilename'));
+const keepOriginalUploading = prefer.model('keepOriginalUploading');
+const keepOriginalFilename = prefer.model('keepOriginalFilename');
misskeyApi('drive').then(info => {
capacity.value = info.capacity;
@@ -103,9 +131,9 @@ misskeyApi('drive').then(info => {
fetching.value = false;
});
-if (defaultStore.state.uploadFolder) {
+if (prefer.s.uploadFolder) {
misskeyApi('drive/folders/show', {
- folderId: defaultStore.state.uploadFolder,
+ folderId: prefer.s.uploadFolder,
}).then(response => {
uploadFolder.value = response;
});
@@ -113,11 +141,11 @@ if (defaultStore.state.uploadFolder) {
function chooseUploadFolder() {
os.selectDriveFolder(false).then(async folder => {
- defaultStore.set('uploadFolder', folder[0] ? folder[0].id : null);
+ prefer.commit('uploadFolder', folder[0] ? folder[0].id : null);
os.success();
- if (defaultStore.state.uploadFolder) {
+ if (prefer.s.uploadFolder) {
uploadFolder.value = await misskeyApi('drive/folders/show', {
- folderId: defaultStore.state.uploadFolder,
+ folderId: prefer.s.uploadFolder,
});
} else {
uploadFolder.value = null;
@@ -142,7 +170,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.drive,
icon: 'ti ti-cloud',
}));
diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue
index d90c86a4ec..e9acd59778 100644
--- a/packages/frontend/src/pages/settings/email.vue
+++ b/packages/frontend/src/pages/settings/email.vue
@@ -4,25 +4,37 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div v-if="instance.enableEmail" class="_gaps_m">
- <FormSection first>
- <template #label>{{ i18n.ts.emailAddress }}</template>
- <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(--MI_THEME-success);"></i> {{ i18n.ts.emailVerified }}</template>
- </MkInput>
- </FormSection>
+<SearchMarker path="/settings/email" :label="i18n.ts.email" :keywords="['email']" icon="ti ti-mail">
+ <div class="_gaps_m">
+ <MkInfo v-if="!instance.enableEmail">{{ i18n.ts.emailNotSupported }}</MkInfo>
- <FormSection>
- <MkSwitch :modelValue="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail">
- {{ i18n.ts.receiveAnnouncementFromInstance }}
- </MkSwitch>
- </FormSection>
-</div>
-<div v-if="!instance.enableEmail" class="_gaps_m">
- <MkInfo>{{ i18n.ts.emailNotSupported }}</MkInfo>
-</div>
+ <MkDisableSection :disabled="!instance.enableEmail">
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['email', 'address']">
+ <FormSection first>
+ <template #label><SearchLabel>{{ i18n.ts.emailAddress }}</SearchLabel></template>
+ <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(--MI_THEME-success);"></i> {{ i18n.ts.emailVerified }}</template>
+ </MkInput>
+ </FormSection>
+ </SearchMarker>
+
+ <FormSection>
+ <SearchMarker :keywords="['announcement', 'email']">
+ <MkSwitch :modelValue="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail">
+ <template #label><SearchLabel>{{ i18n.ts.receiveAnnouncementFromInstance }}</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
+ </FormSection>
+ </div>
+ </MkDisableSection>
+ </div>
+ <div v-if="!instance.enableEmail" class="_gaps_m">
+ <MkInfo>{{ i18n.ts.emailNotSupported }}</MkInfo>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -31,14 +43,15 @@ import FormSection from '@/components/form/section.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
+import MkDisableSection from '@/components/MkDisableSection.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { signinRequired } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { ensureSignin } from '@/i.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { instance } from '@/instance.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const emailAddress = ref($i.email);
@@ -69,7 +82,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.email,
icon: 'ti ti-mail',
}));
diff --git a/packages/frontend/src/pages/settings/emoji-palette.palette.vue b/packages/frontend/src/pages/settings/emoji-palette.palette.vue
new file mode 100644
index 0000000000..33d1d7c9fa
--- /dev/null
+++ b/packages/frontend/src/pages/settings/emoji-palette.palette.vue
@@ -0,0 +1,166 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkFolder :defaultOpen="true">
+ <template #icon><i class="ti ti-palette"></i></template>
+ <template #label>{{ palette.name === '' ? '(' + i18n.ts.noName + ')' : palette.name }}</template>
+ <template #footer>
+ <div class="_buttons">
+ <MkButton @click="rename"><i class="ti ti-pencil"></i> {{ i18n.ts.rename }}</MkButton>
+ <MkButton @click="copy"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
+ <MkButton danger @click="paste"><i class="ti ti-clipboard"></i> {{ i18n.ts.paste }}</MkButton>
+ <MkButton danger iconOnly style="margin-left: auto;" @click="del"><i class="ti ti-trash"></i></MkButton>
+ </div>
+ </template>
+
+ <div>
+ <div v-panel style="border-radius: 6px;">
+ <Sortable
+ v-model="emojis"
+ :class="$style.emojis"
+ :itemKey="item => item"
+ :animation="150"
+ :delay="100"
+ :delayOnTouchOnly="true"
+ :group="{ name: 'SortableEmojiPalettes' }"
+ >
+ <template #item="{element}">
+ <button class="_button" :class="$style.emojisItem" @click="remove(element, $event)">
+ <MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/>
+ <MkEmoji v-else :emoji="element" :normal="true"/>
+ </button>
+ </template>
+ <template #footer>
+ <button class="_button" :class="$style.emojisAdd" @click="pick">
+ <i class="ti ti-plus"></i>
+ </button>
+ </template>
+ </Sortable>
+ </div>
+ <div :class="$style.editorCaption">{{ i18n.ts.reactionSettingDescription2 }}</div>
+ </div>
+</MkFolder>
+</template>
+
+<script lang="ts" setup>
+import { ref, watch } from 'vue';
+import Sortable from 'vuedraggable';
+import MkButton from '@/components/MkButton.vue';
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
+import { deepClone } from '@/utility/clone.js';
+import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
+import MkEmoji from '@/components/global/MkEmoji.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+
+const props = defineProps<{
+ palette: {
+ id: string;
+ name: string;
+ emojis: string[];
+ };
+}>();
+
+const emit = defineEmits<{
+ (ev: 'updateEmojis', emojis: string[]): void,
+ (ev: 'updateName', name: string): void,
+ (ev: 'del'): void,
+}>();
+
+const emojis = ref<string[]>(deepClone(props.palette.emojis));
+
+watch(emojis, () => {
+ emit('updateEmojis', emojis.value);
+}, { deep: true });
+
+function remove(reaction: string, ev: MouseEvent) {
+ os.popupMenu([{
+ text: i18n.ts.remove,
+ action: () => {
+ emojis.value = emojis.value.filter(x => x !== reaction);
+ },
+ }], getHTMLElement(ev));
+}
+
+function pick(ev: MouseEvent) {
+ os.pickEmoji(getHTMLElement(ev), {
+ showPinned: false,
+ }).then(it => {
+ const emoji = it;
+ if (!emojis.value.includes(emoji)) {
+ emojis.value.push(emoji);
+ }
+ });
+}
+
+function getHTMLElement(ev: MouseEvent): HTMLElement {
+ const target = ev.currentTarget ?? ev.target;
+ return target as HTMLElement;
+}
+
+function rename() {
+ os.inputText({
+ title: i18n.ts.rename,
+ default: props.palette.name,
+ }).then(({ canceled, result: name }) => {
+ if (canceled) return;
+ if (name != null) {
+ emit('updateName', name);
+ }
+ });
+}
+
+function copy() {
+ copyToClipboard(emojis.value.join(' '));
+}
+
+function paste() {
+ // TODO: validate
+ navigator.clipboard.readText().then(text => {
+ emojis.value = text.split(' ');
+ });
+}
+
+function del(ev: MouseEvent) {
+ os.popupMenu([{
+ text: i18n.ts.delete,
+ action: () => {
+ emit('del');
+ },
+ }], ev.currentTarget ?? ev.target);
+}
+</script>
+
+<style lang="scss" module>
+.tab {
+ margin: calc(var(--MI-margin) / 2) 0;
+ padding: calc(var(--MI-margin) / 2) 0;
+ background: var(--MI_THEME-bg);
+}
+
+.emojis {
+ padding: 12px;
+ font-size: 1.1em;
+}
+
+.emojisItem {
+ display: inline-block;
+ padding: 8px;
+ cursor: move;
+}
+
+.emojisAdd {
+ display: inline-block;
+ padding: 8px;
+}
+
+.editorCaption {
+ font-size: 0.85em;
+ padding: 8px 0 0 0;
+ color: var(--MI_THEME-fgTransparentWeak);
+}
+</style>
diff --git a/packages/frontend/src/pages/settings/emoji-palette.vue b/packages/frontend/src/pages/settings/emoji-palette.vue
new file mode 100644
index 0000000000..398228e226
--- /dev/null
+++ b/packages/frontend/src/pages/settings/emoji-palette.vue
@@ -0,0 +1,251 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<SearchMarker path="/settings/emoji-palette" :label="i18n.ts.emojiPalette" :keywords="['emoji', 'palette']" icon="ti ti-mood-happy">
+ <div class="_gaps_m">
+ <FormSection first>
+ <template #label>{{ i18n.ts._emojiPalette.palettes }}</template>
+
+ <div class="_gaps_s">
+ <XPalette
+ v-for="palette in prefer.r.emojiPalettes.value"
+ :key="palette.id"
+ :palette="palette"
+ @updateEmojis="emojis => updatePaletteEmojis(palette.id, emojis)"
+ @updateName="name => updatePaletteName(palette.id, name)"
+ @del="delPalette(palette.id)"
+ />
+ <MkButton primary rounded style="margin: auto;" @click="addPalette"><i class="ti ti-plus"></i></MkButton>
+ </div>
+ </FormSection>
+
+ <FormSection>
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['sync', 'palettes', 'devices']">
+ <MkSwitch :modelValue="palettesSyncEnabled" @update:modelValue="changePalettesSyncEnabled">
+ <template #label><SearchLabel>{{ i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes }}</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
+ </div>
+ </FormSection>
+
+ <FormSection>
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['main', 'palette']">
+ <MkPreferenceContainer k="emojiPaletteForMain">
+ <MkSelect v-model="emojiPaletteForMain">
+ <template #label><SearchLabel>{{ i18n.ts._emojiPalette.paletteForMain }}</SearchLabel></template>
+ <option key="-" :value="null">({{ i18n.ts.auto }})</option>
+ <option v-for="palette in prefer.r.emojiPalettes.value" :key="palette.id" :value="palette.id">{{ palette.name === '' ? '(' + i18n.ts.noName + ')' : palette.name }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['reaction', 'palette']">
+ <MkPreferenceContainer k="emojiPaletteForReaction">
+ <MkSelect v-model="emojiPaletteForReaction">
+ <template #label><SearchLabel>{{ i18n.ts._emojiPalette.paletteForReaction }}</SearchLabel></template>
+ <option key="-" :value="null">({{ i18n.ts.auto }})</option>
+ <option v-for="palette in prefer.r.emojiPalettes.value" :key="palette.id" :value="palette.id">{{ palette.name === '' ? '(' + i18n.ts.noName + ')' : palette.name }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+ </FormSection>
+
+ <SearchMarker :keywords="['emoji', 'picker', 'display']">
+ <FormSection>
+ <template #label><SearchLabel>{{ i18n.ts.emojiPickerDisplay }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['emoji', 'picker', 'scale', 'size']">
+ <MkPreferenceContainer k="emojiPickerScale">
+ <MkRadios v-model="emojiPickerScale">
+ <template #label><SearchLabel>{{ i18n.ts.size }}</SearchLabel></template>
+ <option :value="1">{{ i18n.ts.small }}</option>
+ <option :value="2">{{ i18n.ts.medium }}</option>
+ <option :value="3">{{ i18n.ts.large }}</option>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['emoji', 'picker', 'width', 'column', 'size']">
+ <MkPreferenceContainer k="emojiPickerWidth">
+ <MkRadios v-model="emojiPickerWidth">
+ <template #label><SearchLabel>{{ i18n.ts.numberOfColumn }}</SearchLabel></template>
+ <option :value="1">5</option>
+ <option :value="2">6</option>
+ <option :value="3">7</option>
+ <option :value="4">8</option>
+ <option :value="5">9</option>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['emoji', 'picker', 'height', 'size']">
+ <MkPreferenceContainer k="emojiPickerHeight">
+ <MkRadios v-model="emojiPickerHeight">
+ <template #label><SearchLabel>{{ i18n.ts.height }}</SearchLabel></template>
+ <option :value="1">{{ i18n.ts.small }}</option>
+ <option :value="2">{{ i18n.ts.medium }}</option>
+ <option :value="3">{{ i18n.ts.large }}</option>
+ <option :value="4">{{ i18n.ts.large }}+</option>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['emoji', 'picker', 'style']">
+ <MkPreferenceContainer k="emojiPickerStyle">
+ <MkSelect v-model="emojiPickerStyle">
+ <template #label><SearchLabel>{{ i18n.ts.style }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.needReloadToApply }}</template>
+ <option value="auto">{{ i18n.ts.auto }}</option>
+ <option value="popup">{{ i18n.ts.popup }}</option>
+ <option value="drawer">{{ i18n.ts.drawer }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <MkButton @click="previewPicker"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
+ </div>
+ </FormSection>
+ </SearchMarker>
+ </div>
+</SearchMarker>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from 'vue';
+import { v4 as uuid } from 'uuid';
+import XPalette from './emoji-palette.palette.vue';
+import MkRadios from '@/components/MkRadios.vue';
+import MkButton from '@/components/MkButton.vue';
+import FormSection from '@/components/form/section.vue';
+import MkSelect from '@/components/MkSelect.vue';
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
+import { definePage } from '@/page.js';
+import MkFolder from '@/components/MkFolder.vue';
+import { prefer } from '@/preferences.js';
+import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import { emojiPicker } from '@/utility/emoji-picker.js';
+
+const emojiPaletteForReaction = prefer.model('emojiPaletteForReaction');
+const emojiPaletteForMain = prefer.model('emojiPaletteForMain');
+const emojiPickerScale = prefer.model('emojiPickerScale');
+const emojiPickerWidth = prefer.model('emojiPickerWidth');
+const emojiPickerHeight = prefer.model('emojiPickerHeight');
+const emojiPickerStyle = prefer.model('emojiPickerStyle');
+
+const palettesSyncEnabled = ref(prefer.isSyncEnabled('emojiPalettes'));
+
+function changePalettesSyncEnabled(value: boolean) {
+ if (value) {
+ prefer.enableSync('emojiPalettes').then((res) => {
+ if (res == null) return;
+ if (res.enabled) palettesSyncEnabled.value = true;
+ });
+ } else {
+ prefer.disableSync('emojiPalettes');
+ palettesSyncEnabled.value = false;
+ }
+}
+
+function addPalette() {
+ prefer.commit('emojiPalettes', [
+ ...prefer.s.emojiPalettes,
+ {
+ id: uuid(),
+ name: '',
+ emojis: [],
+ },
+ ]);
+}
+
+function updatePaletteEmojis(id: string, emojis: string[]) {
+ prefer.commit('emojiPalettes', prefer.s.emojiPalettes.map(palette => {
+ if (palette.id === id) {
+ return {
+ ...palette,
+ emojis,
+ };
+ } else {
+ return palette;
+ }
+ }));
+}
+
+function updatePaletteName(id: string, name: string) {
+ prefer.commit('emojiPalettes', prefer.s.emojiPalettes.map(palette => {
+ if (palette.id === id) {
+ return {
+ ...palette,
+ name,
+ };
+ } else {
+ return palette;
+ }
+ }));
+}
+
+function delPalette(id: string) {
+ if (prefer.s.emojiPalettes.length === 1) {
+ addPalette();
+ }
+ prefer.commit('emojiPalettes', prefer.s.emojiPalettes.filter(palette => palette.id !== id));
+ if (prefer.s.emojiPaletteForMain === id) {
+ prefer.commit('emojiPaletteForMain', null);
+ }
+ if (prefer.s.emojiPaletteForReaction === id) {
+ prefer.commit('emojiPaletteForReaction', null);
+ }
+}
+
+function getHTMLElement(ev: MouseEvent): HTMLElement {
+ const target = ev.currentTarget ?? ev.target;
+ return target as HTMLElement;
+}
+
+function previewPicker(ev: MouseEvent) {
+ emojiPicker.show(getHTMLElement(ev));
+}
+
+definePage(() => ({
+ title: i18n.ts.emojiPalette,
+ icon: 'ti ti-mood-happy',
+}));
+</script>
+
+<style lang="scss" module>
+.tab {
+ margin: calc(var(--MI-margin) / 2) 0;
+ padding: calc(var(--MI-margin) / 2) 0;
+ background: var(--MI_THEME-bg);
+}
+
+.emojis {
+ padding: 12px;
+ font-size: 1.1em;
+}
+
+.emojisItem {
+ display: inline-block;
+ padding: 8px;
+ cursor: move;
+}
+
+.emojisAdd {
+ display: inline-block;
+ padding: 8px;
+}
+
+.editorCaption {
+ font-size: 0.85em;
+ padding: 8px 0 0 0;
+ color: var(--MI_THEME-fgTransparentWeak);
+}
+</style>
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
deleted file mode 100644
index 7ddc1eab71..0000000000
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ /dev/null
@@ -1,316 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div class="_gaps_m">
- <MkFolder :defaultOpen="true">
- <template #icon><i class="ti ti-pin"></i></template>
- <template #label>{{ i18n.ts.pinned }} ({{ i18n.ts.reaction }})</template>
- <template #caption>{{ i18n.ts.pinnedEmojisForReactionSettingDescription }}</template>
-
- <div class="_gaps">
- <div>
- <div v-panel style="border-radius: var(--MI-radius-sm);">
- <Sortable
- v-model="pinnedEmojisForReaction"
- :class="$style.emojis"
- :itemKey="item => item"
- :animation="150"
- :delay="100"
- :delayOnTouchOnly="true"
- >
- <template #item="{element}">
- <button class="_button" :class="$style.emojisItem" @click="removeReaction(element, $event)">
- <MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/>
- <MkEmoji v-else :emoji="element" :normal="true"/>
- </button>
- </template>
- <template #footer>
- <button class="_button" :class="$style.emojisAdd" @click="chooseReaction">
- <i class="ti ti-plus"></i>
- </button>
- </template>
- </Sortable>
- </div>
- <div :class="$style.editorCaption">{{ i18n.ts.reactionSettingDescription2 }}</div>
- </div>
-
- <div class="_buttons">
- <MkButton inline @click="previewReaction"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
- <MkButton inline danger @click="setDefaultReaction"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
- <MkButton inline danger @click="overwriteFromPinnedEmojis"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojis }}</MkButton>
- </div>
- </div>
- </MkFolder>
-
- <MkFolder>
- <template #icon><i class="ti ti-pin"></i></template>
- <template #label>{{ i18n.ts.pinned }} ({{ i18n.ts.general }})</template>
- <template #caption>{{ i18n.ts.pinnedEmojisSettingDescription }}</template>
-
- <div class="_gaps">
- <div>
- <div v-panel style="border-radius: var(--MI-radius-sm);">
- <Sortable
- v-model="pinnedEmojis"
- :class="$style.emojis"
- :itemKey="item => item"
- :animation="150"
- :delay="100"
- :delayOnTouchOnly="true"
- >
- <template #item="{element}">
- <button class="_button" :class="$style.emojisItem" @click="removeEmoji(element, $event)">
- <MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/>
- <MkEmoji v-else :emoji="element" :normal="true"/>
- </button>
- </template>
- <template #footer>
- <button class="_button" :class="$style.emojisAdd" @click="chooseEmoji">
- <i class="ti ti-plus"></i>
- </button>
- </template>
- </Sortable>
- </div>
- <div :class="$style.editorCaption">{{ i18n.ts.reactionSettingDescription2 }}</div>
- </div>
-
- <div class="_buttons">
- <MkButton inline @click="previewEmoji"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
- <MkButton inline danger @click="setDefaultEmoji"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
- <MkButton inline danger @click="overwriteFromPinnedEmojisForReaction"><i class="ti ti-copy"></i> {{ i18n.ts.overwriteFromPinnedEmojisForReaction }}</MkButton>
- </div>
- </div>
- </MkFolder>
-
- <FromSlot>
- <template #label>{{ i18n.ts.defaultLike }}</template>
- <MkCustomEmoji v-if="like && like.startsWith(':')" style="max-height: 3em; font-size: 1.1em;" :useOriginalSize="false" :name="like" :normal="true" :noStyle="true"/>
- <MkEmoji v-else-if="like && !like.startsWith(':')" :emoji="like" style="max-height: 3em; font-size: 1.1em;" :normal="true" :noStyle="true"/>
- <span v-else-if="!like">{{ i18n.ts.notSet }}</span>
- <div class="_buttons" style="padding-top: 8px;">
- <MkButton rounded :small="true" inline @click="chooseNewLike"><i class="ph-smiley ph-bold ph-lg"></i> Change</MkButton>
- <MkButton rounded :small="true" inline @click="resetLike"><i class="ph-arrow-clockwise ph-bold ph-lg"></i> Reset</MkButton>
- </div>
- </FromSlot>
-
- <FormSection>
- <template #label>{{ i18n.ts.emojiPickerDisplay }}</template>
-
- <div class="_gaps_m">
- <MkRadios v-model="emojiPickerScale">
- <template #label>{{ i18n.ts.size }}</template>
- <option :value="1">{{ i18n.ts.small }}</option>
- <option :value="2">{{ i18n.ts.medium }}</option>
- <option :value="3">{{ i18n.ts.large }}</option>
- </MkRadios>
-
- <MkRadios v-model="emojiPickerWidth">
- <template #label>{{ i18n.ts.numberOfColumn }}</template>
- <option :value="1">5</option>
- <option :value="2">6</option>
- <option :value="3">7</option>
- <option :value="4">8</option>
- <option :value="5">9</option>
- </MkRadios>
-
- <MkRadios v-model="emojiPickerHeight">
- <template #label>{{ i18n.ts.height }}</template>
- <option :value="1">{{ i18n.ts.small }}</option>
- <option :value="2">{{ i18n.ts.medium }}</option>
- <option :value="3">{{ i18n.ts.large }}</option>
- <option :value="4">{{ i18n.ts.large }}+</option>
- </MkRadios>
-
- <MkSelect v-model="emojiPickerStyle">
- <template #label>{{ i18n.ts.style }}</template>
- <template #caption>{{ i18n.ts.needReloadToApply }}</template>
- <option value="auto">{{ i18n.ts.auto }}</option>
- <option value="popup">{{ i18n.ts.popup }}</option>
- <option value="drawer">{{ i18n.ts.drawer }}</option>
- </MkSelect>
- </div>
- </FormSection>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { computed, ref, Ref, watch } from 'vue';
-import Sortable from 'vuedraggable';
-import MkRadios from '@/components/MkRadios.vue';
-import MkButton from '@/components/MkButton.vue';
-import FormSection from '@/components/form/section.vue';
-import FromSlot from '@/components/form/slot.vue';
-import MkSelect from '@/components/MkSelect.vue';
-import * as os from '@/os.js';
-import { defaultStore } from '@/store.js';
-import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { deepClone } from '@/scripts/clone.js';
-import { reactionPicker } from '@/scripts/reaction-picker.js';
-import { emojiPicker } from '@/scripts/emoji-picker.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
-import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
-import MkEmoji from '@/components/global/MkEmoji.vue';
-import MkFolder from '@/components/MkFolder.vue';
-
-const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(defaultStore.state.reactions));
-const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmojis));
-
-const emojiPickerScale = computed(defaultStore.makeGetterSetter('emojiPickerScale'));
-const emojiPickerWidth = computed(defaultStore.makeGetterSetter('emojiPickerWidth'));
-const emojiPickerHeight = computed(defaultStore.makeGetterSetter('emojiPickerHeight'));
-const emojiPickerStyle = computed(defaultStore.makeGetterSetter('emojiPickerStyle'));
-
-const removeReaction = (reaction: string, ev: MouseEvent) => remove(pinnedEmojisForReaction, reaction, ev);
-const chooseReaction = (ev: MouseEvent) => pickEmoji(pinnedEmojisForReaction, ev);
-const setDefaultReaction = () => setDefault(pinnedEmojisForReaction);
-
-const like = computed(defaultStore.makeGetterSetter('like'));
-
-const removeEmoji = (reaction: string, ev: MouseEvent) => remove(pinnedEmojis, reaction, ev);
-const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev);
-const setDefaultEmoji = () => setDefault(pinnedEmojis);
-
-function previewReaction(ev: MouseEvent) {
- reactionPicker.show(getHTMLElement(ev), null);
-}
-
-function previewEmoji(ev: MouseEvent) {
- emojiPicker.show(getHTMLElement(ev));
-}
-
-async function overwriteFromPinnedEmojis() {
- const { canceled } = await os.confirm({
- type: 'warning',
- text: i18n.ts.overwriteContentConfirm,
- });
-
- if (canceled) {
- return;
- }
-
- pinnedEmojisForReaction.value = [...pinnedEmojis.value];
-}
-
-async function overwriteFromPinnedEmojisForReaction() {
- const { canceled } = await os.confirm({
- type: 'warning',
- text: i18n.ts.overwriteContentConfirm,
- });
-
- if (canceled) {
- return;
- }
-
- pinnedEmojis.value = [...pinnedEmojisForReaction.value];
-}
-
-function remove(itemsRef: Ref<string[]>, reaction: string, ev: MouseEvent) {
- os.popupMenu([{
- text: i18n.ts.remove,
- action: () => {
- itemsRef.value = itemsRef.value.filter(x => x !== reaction);
- },
- }], getHTMLElement(ev));
-}
-
-async function setDefault(itemsRef: Ref<string[]>) {
- const { canceled } = await os.confirm({
- type: 'warning',
- text: i18n.ts.resetAreYouSure,
- });
- if (canceled) return;
-
- itemsRef.value = deepClone(defaultStore.def.reactions.default);
-}
-
-async function pickEmoji(itemsRef: Ref<string[]>, ev: MouseEvent) {
- os.pickEmoji(getHTMLElement(ev), {
- showPinned: false,
- }).then(it => {
- const emoji = it;
- if (!itemsRef.value.includes(emoji)) {
- itemsRef.value.push(emoji);
- }
- });
-}
-
-async function reloadAsk() {
- const { canceled } = await os.confirm({
- type: 'info',
- text: i18n.ts.reloadToApplySetting,
- });
- if (canceled) return;
-
- unisonReload();
-}
-
-function chooseNewLike(ev: MouseEvent) {
- os.pickEmoji(getHTMLElement(ev), {
- showPinned: false,
- }).then(async emoji => {
- defaultStore.set('like', emoji as string);
- await reloadAsk();
- });
-}
-
-async function resetLike() {
- defaultStore.set('like', null);
- await reloadAsk();
-}
-
-function getHTMLElement(ev: MouseEvent): HTMLElement {
- const target = ev.currentTarget ?? ev.target;
- return target as HTMLElement;
-}
-
-watch(pinnedEmojisForReaction, () => {
- defaultStore.set('reactions', pinnedEmojisForReaction.value);
-}, {
- deep: true,
-});
-
-watch(pinnedEmojis, () => {
- defaultStore.set('pinnedEmojis', pinnedEmojis.value);
-}, {
- deep: true,
-});
-
-definePageMetadata(() => ({
- title: i18n.ts.emojiPicker,
- icon: 'ti ti-mood-happy',
-}));
-</script>
-
-<style lang="scss" module>
-.tab {
- margin: calc(var(--MI-margin) / 2) 0;
- padding: calc(var(--MI-margin) / 2) 0;
- background: var(--MI_THEME-bg);
-}
-
-.emojis {
- padding: 12px;
- font-size: 1.1em;
-}
-
-.emojisItem {
- display: inline-block;
- padding: 8px;
- cursor: move;
-}
-
-.emojisAdd {
- display: inline-block;
- padding: 8px;
-}
-
-.editorCaption {
- font-size: 0.85em;
- padding: 8px 0 0 0;
- color: var(--MI_THEME-fgTransparentWeak);
-}
-</style>
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
deleted file mode 100644
index fc9c6aa669..0000000000
--- a/packages/frontend/src/pages/settings/general.vue
+++ /dev/null
@@ -1,623 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div class="_gaps_m">
- <MkSelect v-model="lang">
- <template #label>{{ i18n.ts.uiLanguage }}</template>
- <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option>
- <template #caption>
- <I18n :src="i18n.ts.i18nInfo" tag="span">
- <template #link>
- <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
- </template>
- </I18n>
- </template>
- </MkSelect>
-
- <MkRadios v-model="overridedDeviceKind">
- <template #label>{{ i18n.ts.overridedDeviceKind }}</template>
- <option :value="null">{{ i18n.ts.auto }}</option>
- <option value="smartphone"><i class="ti ti-device-mobile"/> {{ i18n.ts.smartphone }}</option>
- <option value="tablet"><i class="ti ti-device-tablet"/> {{ i18n.ts.tablet }}</option>
- <option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option>
- </MkRadios>
-
- <FormSection>
- <div class="_gaps_s">
- <MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
- <MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
- <MkFolder>
- <template #label>{{ i18n.ts.pinnedList }}</template>
- <!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
- <MkButton v-if="defaultStore.reactiveState.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton>
- <MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
- </MkFolder>
- </div>
- </FormSection>
-
- <FormSection>
- <template #label>{{ i18n.ts.displayOfNote }}</template>
-
- <div class="_gaps_m">
- <div class="_gaps_s">
- <MkSwitch v-model="collapseRenotes">
- <template #label>{{ i18n.ts.collapseRenotes }}</template>
- <template #caption>{{ i18n.ts.collapseRenotesDescription }}</template>
- </MkSwitch>
- <MkSwitch v-model="collapseNotesRepliedTo">{{ i18n.ts.collapseNotesRepliedTo }}</MkSwitch>
- <MkSwitch v-model="collapseFiles">{{ i18n.ts.collapseFiles }}</MkSwitch>
- <MkSwitch v-model="uncollapseCW">{{ i18n.ts.uncollapseCW }}</MkSwitch>
- <MkSwitch v-model="expandLongNote">{{ i18n.ts.expandLongNote }}</MkSwitch>
- <MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch>
- <MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch>
- <MkSwitch v-model="autoloadConversation">{{ i18n.ts.autoloadConversation }}</MkSwitch>
- <MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
- <MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
- <MkSwitch v-if="advancedMfm" v-model="enableQuickAddMfmFunction">{{ i18n.ts.enableQuickAddMfmFunction }}</MkSwitch>
- <MkSwitch v-model="showReactionsCount">{{ i18n.ts.showReactionsCount }}</MkSwitch>
- <MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
- <MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
- <MkSwitch v-model="showTickerOnReplies">{{ i18n.ts.showTickerOnReplies }}</MkSwitch>
- <MkSwitch v-model="disableCatSpeak">{{ i18n.ts.disableCatSpeak }}</MkSwitch>
- <MkSelect v-model="searchEngine" placeholder="Other">
- <template #label>{{ i18n.ts.searchEngine }}</template>
- <option
- v-for="[key, value] in Object.entries(searchEngineMap)" :key="key" :value="key"
- >
- {{ value }}
- </option>
- <!-- If the user is on Other and enters a domain add this one so that the dropdown doesnt go blank -->
- <option v-if="useCustomSearchEngine" :value="searchEngine">
- {{ i18n.ts.searchEngineOther }}
- </option>
- <!-- If one of the other options is selected show this as a blank other -->
- <option v-if="!useCustomSearchEngine" value="">{{ i18n.ts.searchEngineOther }}</option>
- </MkSelect>
-
- <div v-if="useCustomSearchEngine">
- <MkInput v-model="searchEngine" :max="300" :manualSave="true">
- <template #label>{{ i18n.ts.searchEngineCusomURI }}</template>
- <template #caption>{{ i18n.ts.searchEngineCustomURIDescription }}</template>
- </MkInput>
- </div>
-
- <MkRadios v-model="reactionsDisplaySize">
- <template #label>{{ i18n.ts.reactionsDisplaySize }}</template>
- <option value="small">{{ i18n.ts.small }}</option>
- <option value="medium">{{ i18n.ts.medium }}</option>
- <option value="large">{{ i18n.ts.large }}</option>
- </MkRadios>
- <MkRadios v-model="noteDesign">
- <template #label>Note Design</template>
- <option value="sharkey"><i class="sk-icons sk-shark sk-icons-lg" style="top: 2px;position: relative;"></i> Sharkey</option>
- <option value="misskey"><i class="sk-icons sk-misskey sk-icons-lg" style="top: 2px;position: relative;"></i> Misskey</option>
- </MkRadios>
- <MkSwitch v-model="limitWidthOfReaction">{{ i18n.ts.limitWidthOfReaction }}</MkSwitch>
- </div>
-
- <MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker">
- <template #label>{{ i18n.ts.instanceTicker }}</template>
- <option value="none">{{ i18n.ts._instanceTicker.none }}</option>
- <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
- <option value="always">{{ i18n.ts._instanceTicker.always }}</option>
- </MkSelect>
-
- <MkSelect v-model="nsfw">
- <template #label>{{ i18n.ts.displayOfSensitiveMedia }}</template>
- <option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option>
- <option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option>
- <option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option>
- </MkSelect>
-
- <MkRadios v-model="mediaListWithOneImageAppearance">
- <template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
- <option value="expand">{{ i18n.ts.default }}</option>
- <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option>
- <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option>
- <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option>
- </MkRadios>
-
- <MkRange v-model="numberOfReplies" :min="2" :max="20" :step="1" easing>
- <template #label>{{ i18n.ts.numberOfReplies }}</template>
- <template #caption>{{ i18n.ts.numberOfRepliesDescription }}</template>
- </MkRange>
- </div>
- </FormSection>
-
- <FormSection>
- <template #label>{{ i18n.ts.notificationDisplay }}</template>
-
- <div class="_gaps_m">
- <MkSwitch v-model="useGroupedNotifications">{{ i18n.ts.useGroupedNotifications }}</MkSwitch>
-
- <MkSwitch v-model="enableFaviconNotificationDot">
- {{ i18n.ts.enableFaviconNotificationDot }}
- <template #caption>
- <I18n :src="i18n.ts.notificationDotNotWorkingAdvice" tag="span">
- <template #link>
- <MkLink url="https://docs.joinsharkey.org/docs/install/faqs/#ive-enabled-the-notification-dot-but-it-doesnt-show">{{ i18n.ts._mfm.link }}</MkLink>
- </template>
- </I18n>
- </template>
- </MkSwitch>
-
- <MkButton @click="testNotificationDot">{{ i18n.ts.verifyNotificationDotWorkingButton }}</MkButton>
- <MkRadios v-model="notificationPosition">
- <template #label>{{ i18n.ts.position }}</template>
- <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option>
- <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option>
- <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option>
- <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option>
- </MkRadios>
-
- <MkRadios v-model="notificationStackAxis">
- <template #label>{{ i18n.ts.stackAxis }}</template>
- <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option>
- <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
- </MkRadios>
-
- <MkSwitch v-model="notificationClickable">{{ i18n.ts.allowClickingNotifications }}</MkSwitch>
-
- <MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
- </div>
- </FormSection>
-
- <FormSection>
- <template #label>{{ i18n.ts.appearance }}</template>
-
- <div class="_gaps_m">
- <div class="_gaps_s">
- <MkSwitch v-model="reduceAnimation">{{ i18n.ts.reduceUiAnimation }}</MkSwitch>
- <MkSwitch v-model="useBlurEffect">{{ i18n.ts.useBlurEffect }}</MkSwitch>
- <MkSwitch v-model="useBlurEffectForModal">{{ i18n.ts.useBlurEffectForModal }}</MkSwitch>
- <MkSwitch v-model="disableShowingAnimatedImages">{{ i18n.ts.disableShowingAnimatedImages }}</MkSwitch>
- <MkSwitch v-model="highlightSensitiveMedia">{{ i18n.ts.highlightSensitiveMedia }}</MkSwitch>
- <MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch>
- <MkSwitch v-model="showAvatarDecorations">{{ i18n.ts.showAvatarDecorations }}</MkSwitch>
- <MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
- <MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
- <MkSwitch v-model="oneko">{{ i18n.ts.oneko }}</MkSwitch>
- <MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
- <MkSwitch v-model="useNativeUIForVideoAudioPlayer">{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</MkSwitch>
- </div>
-
- <MkSelect v-model="menuStyle">
- <template #label>{{ i18n.ts.menuStyle }}</template>
- <option value="auto">{{ i18n.ts.auto }}</option>
- <option value="popup">{{ i18n.ts.popup }}</option>
- <option value="drawer">{{ i18n.ts.drawer }}</option>
- </MkSelect>
-
- <div>
- <MkRadios v-model="emojiStyle">
- <template #label>{{ i18n.ts.emojiStyle }}</template>
- <option value="native">{{ i18n.ts.native }}</option>
- <option value="fluentEmoji">Fluent Emoji</option>
- <option value="twemoji">Twemoji</option>
- <option value="tossface">Tossface</option>
- </MkRadios>
- <div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div>
- </div>
-
- <MkRadios v-model="fontSize">
- <template #label>{{ i18n.ts.fontSize }}</template>
- <option :value="null"><span style="font-size: 14px;">Aa</span></option>
- <option value="1"><span style="font-size: 15px;">Aa</span></option>
- <option value="2"><span style="font-size: 16px;">Aa</span></option>
- <option value="3"><span style="font-size: 17px;">Aa</span></option>
- </MkRadios>
-
- <MkRadios v-model="cornerRadius">
- <template #label>{{ i18n.ts.cornerRadius }}</template>
- <option :value="null"><i class="sk-icons sk-shark sk-icons-lg" style="top: 2px;position: relative;"></i> Sharkey</option>
- <option value="misskey"><i class="sk-icons sk-misskey sk-icons-lg" style="top: 2px;position: relative;"></i> Misskey</option>
- </MkRadios>
- </div>
- </FormSection>
-
- <FormSection>
- <template #label>{{ i18n.ts.behavior }}</template>
-
- <div class="_gaps_m">
- <div class="_gaps_s">
- <MkSwitch v-model="warnMissingAltText">{{ i18n.ts.warnForMissingAltText }}</MkSwitch>
- <MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch>
- <MkSwitch v-model="useReactionPickerForContextMenu">{{ i18n.ts.useReactionPickerForContextMenu }}</MkSwitch>
- <MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
- <MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
- <MkSwitch v-model="clickToOpen">{{ i18n.ts.clickToOpen }}</MkSwitch>
- <MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
- <MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
- <MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
- <MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch>
- <MkSwitch v-model="warnExternalUrl">{{ i18n.ts.warnExternalUrl }}</MkSwitch>
- </div>
- <MkSelect v-model="serverDisconnectedBehavior">
- <template #label>{{ i18n.ts.whenServerDisconnected }}</template>
- <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
- <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
- <option value="disabled">{{ i18n.ts._serverDisconnectedBehavior.disabled }}</option>
- </MkSelect>
- <MkSelect v-model="contextMenu">
- <template #label>{{ i18n.ts._contextMenu.title }}</template>
- <option value="app">{{ i18n.ts._contextMenu.app }}</option>
- <option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option>
- <option value="native">{{ i18n.ts._contextMenu.native }}</option>
- </MkSelect>
- <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing>
- <template #label>{{ i18n.ts.numberOfPageCache }}</template>
- <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
- </MkRange>
-
- <MkFolder>
- <template #label>{{ i18n.ts.boostSettings }}</template>
- <div class="_gaps_m">
- <MkSwitch v-model="showVisibilitySelectorOnBoost">
- {{ i18n.ts.showVisibilitySelectorOnBoost }}
- <template #caption>{{ i18n.ts.showVisibilitySelectorOnBoostDescription }}</template>
- </MkSwitch>
- <MkSelect v-model="visibilityOnBoost">
- <template #label>{{ i18n.ts.visibilityOnBoost }}</template>
- <option value="public">{{ i18n.ts._visibility['public'] }}</option>
- <option value="home">{{ i18n.ts._visibility['home'] }}</option>
- <option value="followers">{{ i18n.ts._visibility['followers'] }}</option>
- </MkSelect>
- </div>
- </MkFolder>
-
- <MkFolder>
- <template #label>{{ i18n.ts.dataSaver }}</template>
-
- <div class="_gaps_m">
- <MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>
-
- <div class="_buttons">
- <MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
- <MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
- </div>
- <div class="_gaps_m">
- <MkSwitch v-model="dataSaver.media">
- {{ i18n.ts._dataSaver._media.title }}
- <template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
- </MkSwitch>
- <MkSwitch v-model="dataSaver.avatar">
- {{ i18n.ts._dataSaver._avatar.title }}
- <template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
- </MkSwitch>
- <MkSwitch v-model="dataSaver.urlPreview">
- {{ i18n.ts._dataSaver._urlPreview.title }}
- <template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
- </MkSwitch>
- <MkSwitch v-model="dataSaver.code">
- {{ i18n.ts._dataSaver._code.title }}
- <template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
- </MkSwitch>
- </div>
- </div>
- </MkFolder>
- </div>
- </FormSection>
-
- <FormSection>
- <template #label>{{ i18n.ts.other }}</template>
-
- <div class="_gaps">
- <MkRadios v-model="hemisphere">
- <template #label>{{ i18n.ts.hemisphere }}</template>
- <option value="N">{{ i18n.ts._hemisphere.N }}</option>
- <option value="S">{{ i18n.ts._hemisphere.S }}</option>
- <template #caption>{{ i18n.ts._hemisphere.caption }}</template>
- </MkRadios>
- <MkFolder>
- <template #label>{{ i18n.ts.additionalEmojiDictionary }}</template>
- <div class="_buttons">
- <template v-for="lang in emojiIndexLangs" :key="lang">
- <MkButton v-if="defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton>
- <MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
- </template>
- </div>
- </MkFolder>
- <FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
- <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
- </div>
- </FormSection>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { computed, ref, watch } from 'vue';
-import * as Misskey from 'misskey-js';
-import { langs } from '@@/js/config.js';
-import MkSwitch from '@/components/MkSwitch.vue';
-import MkSelect from '@/components/MkSelect.vue';
-import MkRadios from '@/components/MkRadios.vue';
-import MkInput from '@/components/MkInput.vue';
-import MkRange from '@/components/MkRange.vue';
-import MkFolder from '@/components/MkFolder.vue';
-import MkButton from '@/components/MkButton.vue';
-import FormSection from '@/components/form/section.vue';
-import FormLink from '@/components/form/link.vue';
-import MkLink from '@/components/MkLink.vue';
-import MkInfo from '@/components/MkInfo.vue';
-import { searchEngineMap } from '@/scripts/search-engine-map.js';
-import { defaultStore } from '@/store.js';
-import * as os from '@/os.js';
-import { instance } from '@/instance.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { reloadAsk } from '@/scripts/reload-ask.js';
-import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { miLocalStorage } from '@/local-storage.js';
-import { globalEvents } from '@/events.js';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { worksOnInstance } from '@/scripts/favicon-dot.js';
-
-const lang = ref(miLocalStorage.getItem('lang'));
-const fontSize = ref(miLocalStorage.getItem('fontSize'));
-const cornerRadius = ref(miLocalStorage.getItem('cornerRadius'));
-const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
-const dataSaver = ref(defaultStore.state.dataSaver);
-
-const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
-const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
-const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
-const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
-const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
-const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
-const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction'));
-const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
-const collapseNotesRepliedTo = computed(defaultStore.makeGetterSetter('collapseNotesRepliedTo'));
-const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen'));
-const collapseFiles = computed(defaultStore.makeGetterSetter('collapseFiles'));
-const autoloadConversation = computed(defaultStore.makeGetterSetter('autoloadConversation'));
-const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
-const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
-const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));
-const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'));
-const animatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm'));
-const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm'));
-const showReactionsCount = computed(defaultStore.makeGetterSetter('showReactionsCount'));
-const enableQuickAddMfmFunction = computed(defaultStore.makeGetterSetter('enableQuickAddMfmFunction'));
-const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
-const menuStyle = computed(defaultStore.makeGetterSetter('menuStyle'));
-const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
-const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
-const oneko = computed(defaultStore.makeGetterSetter('oneko'));
-const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
-const disableCatSpeak = computed(defaultStore.makeGetterSetter('disableCatSpeak'));
-const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
-const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
-const enableFaviconNotificationDot = computed(defaultStore.makeGetterSetter('enableFaviconNotificationDot'));
-const warnMissingAltText = computed(defaultStore.makeGetterSetter('warnMissingAltText'));
-const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
-const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
-const showFixedPostFormInChannel = computed(defaultStore.makeGetterSetter('showFixedPostFormInChannel'));
-const numberOfPageCache = computed(defaultStore.makeGetterSetter('numberOfPageCache'));
-const numberOfReplies = computed(defaultStore.makeGetterSetter('numberOfReplies'));
-const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'));
-const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
-const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
-const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
-const showAvatarDecorations = computed(defaultStore.makeGetterSetter('showAvatarDecorations'));
-const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
-const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
-const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
-const notificationClickable = computed(defaultStore.makeGetterSetter('notificationClickable'));
-const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
-const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
-const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
-const showTickerOnReplies = computed(defaultStore.makeGetterSetter('showTickerOnReplies'));
-const searchEngine = computed(defaultStore.makeGetterSetter('searchEngine'));
-
-const noteDesign = computed(defaultStore.makeGetterSetter('noteDesign'));
-const uncollapseCW = computed(defaultStore.makeGetterSetter('uncollapseCW'));
-const expandLongNote = computed(defaultStore.makeGetterSetter('expandLongNote'));
-const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
-const showVisibilitySelectorOnBoost = computed(defaultStore.makeGetterSetter('showVisibilitySelectorOnBoost'));
-const visibilityOnBoost = computed(defaultStore.makeGetterSetter('visibilityOnBoost'));
-const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
-const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer'));
-const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
-const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
-const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
-const warnExternalUrl = computed(defaultStore.makeGetterSetter('warnExternalUrl'));
-
-watch(lang, () => {
- miLocalStorage.setItem('lang', lang.value as string);
- miLocalStorage.removeItem('locale');
- miLocalStorage.removeItem('localeVersion');
-});
-
-watch(fontSize, () => {
- if (fontSize.value == null) {
- miLocalStorage.removeItem('fontSize');
- } else {
- miLocalStorage.setItem('fontSize', fontSize.value);
- }
-});
-
-watch(cornerRadius, () => {
- if (cornerRadius.value == null) {
- miLocalStorage.removeItem('cornerRadius');
- } else {
- miLocalStorage.setItem('cornerRadius', cornerRadius.value);
- }
-});
-
-watch(useSystemFont, () => {
- if (useSystemFont.value) {
- miLocalStorage.setItem('useSystemFont', 't');
- } else {
- miLocalStorage.removeItem('useSystemFont');
- }
-});
-
-watch(noteDesign, async (newval) => {
- if (noteDesign.value === newval) {
- await reloadAsk({});
- }
-});
-
-watch([
- hemisphere,
- lang,
- fontSize,
- cornerRadius,
- useSystemFont,
- enableInfiniteScroll,
- squareAvatars,
- showNoteActionsOnlyHover,
- showGapBetweenNotesInTimeline,
- instanceTicker,
- overridedDeviceKind,
- mediaListWithOneImageAppearance,
- reactionsDisplaySize,
- limitWidthOfReaction,
- highlightSensitiveMedia,
- keepScreenOn,
- disableStreamingTimeline,
- enableSeasonalScreenEffect,
- showVisibilitySelectorOnBoost,
- visibilityOnBoost,
- alwaysConfirmFollow,
- confirmWhenRevealingSensitiveMedia,
- contextMenu,
- warnExternalUrl,
-], async () => {
- await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
-});
-
-const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
-
-function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) {
- if (langs.find(x => x[0] === targetLang)) {
- return langs.find(x => x[0] === targetLang)![1];
- } else {
- // 絵文字辞書限定の言語定義
- switch (targetLang) {
- case 'ja-JP_hira': return 'ひらがな';
- default: return targetLang;
- }
- }
-}
-
-function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
- async function main() {
- const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
-
- function download() {
- switch (lang) {
- case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default);
- case 'ja-JP': return import('../../unicode-emoji-indexes/ja-JP.json').then(x => x.default);
- case 'ja-JP_hira': return import('../../unicode-emoji-indexes/ja-JP_hira.json').then(x => x.default);
- default: throw new Error('unrecognized lang: ' + lang);
- }
- }
-
- currentIndexes[lang] = await download();
- await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
- }
-
- os.promiseDialog(main());
-}
-
-function removeEmojiIndex(lang: string) {
- async function main() {
- const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
- delete currentIndexes[lang];
- await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
- }
-
- os.promiseDialog(main());
-}
-
-async function setPinnedList() {
- const lists = await misskeyApi('users/lists/list');
- const { canceled, result: list } = await os.select({
- title: i18n.ts.selectList,
- items: lists.map(x => ({
- value: x, text: x.name,
- })),
- });
- if (canceled) return;
-
- defaultStore.set('pinnedUserLists', [list]);
-}
-
-function removePinnedList() {
- defaultStore.set('pinnedUserLists', []);
-}
-
-let smashCount = 0;
-let smashTimer: number | null = null;
-
-function testNotification(): void {
- const notification: Misskey.entities.Notification = {
- id: Math.random().toString(),
- createdAt: new Date().toUTCString(),
- isRead: false,
- type: 'test',
- };
-
- globalEvents.emit('clientNotification', notification);
-
- // セルフ通知破壊 実績関連
- smashCount++;
- if (smashCount >= 10) {
- claimAchievement('smashTestNotificationButton');
- smashCount = 0;
- }
- if (smashTimer) {
- clearTimeout(smashTimer);
- }
- smashTimer = window.setTimeout(() => {
- smashCount = 0;
- }, 300);
-}
-
-async function testNotificationDot() {
- const success = await worksOnInstance();
-
- if (success) {
- os.toast(i18n.ts.notificationDotWorking);
- } else {
- os.toast(i18n.ts.notificationDotNotWorking);
- }
-}
-
-function enableAllDataSaver() {
- const g = { ...defaultStore.state.dataSaver };
-
- Object.keys(g).forEach((key) => { g[key] = true; });
-
- dataSaver.value = g;
-}
-
-function disableAllDataSaver() {
- const g = { ...defaultStore.state.dataSaver };
-
- Object.keys(g).forEach((key) => { g[key] = false; });
-
- dataSaver.value = g;
-}
-
-watch(dataSaver, (to) => {
- defaultStore.set('dataSaver', to);
-}, {
- deep: true,
-});
-
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => []);
-
-definePageMetadata(() => ({
- title: i18n.ts.general,
- icon: 'ti ti-adjustments',
-}));
-
-const useCustomSearchEngine = computed(() => !Object.keys(searchEngineMap).includes(searchEngine.value));
-</script>
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
deleted file mode 100644
index e000c608fe..0000000000
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ /dev/null
@@ -1,263 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div class="_gaps_m">
- <FormSection first>
- <template #label><i class="ti ti-pencil"></i> {{ i18n.ts._exportOrImport.allNotes }}</template>
- <div class="_gaps_s">
- <MkFolder>
- <template #label>{{ i18n.ts.export }}</template>
- <template #icon><i class="ti ti-download"></i></template>
- <MkButton primary :class="$style.button" inline @click="exportNotes()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
- </MkFolder>
- <MkFolder v-if="$i && $i.policies.canImportNotes">
- <template #label>{{ i18n.ts.import }}</template>
- <template #icon><i class="ph-upload ph-bold ph-lg"></i></template>
- <MkRadios v-model="noteType" style="padding-bottom: 8px;" small>
- <template #label>Origin</template>
- <option value="Misskey">Misskey/Firefish</option>
- <option value="Mastodon">Mastodon/Pleroma/Akkoma</option>
- <option value="Twitter">Twitter</option>
- <option value="Instagram">Instagram</option>
- <option value="Facebook">Facebook</option>
- </MkRadios>
- <MkButton primary :class="$style.button" inline @click="importNotes($event)"><i class="ph-upload ph-bold ph-lg"></i> {{ i18n.ts.import }}</MkButton>
- </MkFolder>
- </div>
- </FormSection>
- <FormSection>
- <template #label><i class="ti ti-star"></i> {{ i18n.ts._exportOrImport.favoritedNotes }}</template>
- <MkFolder>
- <template #label>{{ i18n.ts.export }}</template>
- <template #icon><i class="ti ti-download"></i></template>
- <MkButton primary :class="$style.button" inline @click="exportFavorites()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
- </MkFolder>
- </FormSection>
- <FormSection>
- <template #label><i class="ph-paperclip ph-bold ph-lg"></i> {{ i18n.ts._exportOrImport.clips }}</template>
- <MkFolder>
- <template #label>{{ i18n.ts.export }}</template>
- <template #icon><i class="ti ti-download"></i></template>
- <MkButton primary :class="$style.button" inline @click="exportClips()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
- </MkFolder>
- </FormSection>
- <FormSection>
- <template #label><i class="ti ti-users"></i> {{ i18n.ts._exportOrImport.followingList }}</template>
- <div class="_gaps_s">
- <MkFolder>
- <template #label>{{ i18n.ts.export }}</template>
- <template #icon><i class="ti ti-download"></i></template>
- <div class="_gaps_s">
- <MkSwitch v-model="excludeMutingUsers">
- {{ i18n.ts._exportOrImport.excludeMutingUsers }}
- </MkSwitch>
- <MkSwitch v-model="excludeInactiveUsers">
- {{ i18n.ts._exportOrImport.excludeInactiveUsers }}
- </MkSwitch>
- <MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
- </div>
- </MkFolder>
- <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing">
- <template #label>{{ i18n.ts.import }}</template>
- <template #icon><i class="ti ti-upload"></i></template>
- <MkSwitch v-model="withReplies">
- {{ i18n.ts._exportOrImport.withReplies }}
- </MkSwitch>
- <MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
- </MkFolder>
- </div>
- </FormSection>
- <FormSection>
- <template #label><i class="ti ti-users"></i> {{ i18n.ts._exportOrImport.userLists }}</template>
- <div class="_gaps_s">
- <MkFolder>
- <template #label>{{ i18n.ts.export }}</template>
- <template #icon><i class="ti ti-download"></i></template>
- <MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
- </MkFolder>
- <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists">
- <template #label>{{ i18n.ts.import }}</template>
- <template #icon><i class="ti ti-upload"></i></template>
- <MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
- </MkFolder>
- </div>
- </FormSection>
- <FormSection>
- <template #label><i class="ti ti-user-off"></i> {{ i18n.ts._exportOrImport.muteList }}</template>
- <div class="_gaps_s">
- <MkFolder>
- <template #label>{{ i18n.ts.export }}</template>
- <template #icon><i class="ti ti-download"></i></template>
- <MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
- </MkFolder>
- <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting">
- <template #label>{{ i18n.ts.import }}</template>
- <template #icon><i class="ti ti-upload"></i></template>
- <MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
- </MkFolder>
- </div>
- </FormSection>
- <FormSection>
- <template #label><i class="ti ti-user-off"></i> {{ i18n.ts._exportOrImport.blockingList }}</template>
- <div class="_gaps_s">
- <MkFolder>
- <template #label>{{ i18n.ts.export }}</template>
- <template #icon><i class="ti ti-download"></i></template>
- <MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
- </MkFolder>
- <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking">
- <template #label>{{ i18n.ts.import }}</template>
- <template #icon><i class="ti ti-upload"></i></template>
- <MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
- </MkFolder>
- </div>
- </FormSection>
- <FormSection>
- <template #label><i class="ti ti-antenna"></i> {{ i18n.ts.antennas }}</template>
- <div class="_gaps_s">
- <MkFolder>
- <template #label>{{ i18n.ts.export }}</template>
- <template #icon><i class="ti ti-download"></i></template>
- <MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
- </MkFolder>
- <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas">
- <template #label>{{ i18n.ts.import }}</template>
- <template #icon><i class="ti ti-upload"></i></template>
- <MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
- </MkFolder>
- </div>
- </FormSection>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { ref, computed } from 'vue';
-import MkButton from '@/components/MkButton.vue';
-import FormSection from '@/components/form/section.vue';
-import MkFolder from '@/components/MkFolder.vue';
-import MkSwitch from '@/components/MkSwitch.vue';
-import MkRadios from '@/components/MkRadios.vue';
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { selectFile } from '@/scripts/select-file.js';
-import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i } from '@/account.js';
-import { defaultStore } from '@/store.js';
-
-const excludeMutingUsers = ref(false);
-const excludeInactiveUsers = ref(false);
-const noteType = ref(null);
-const withReplies = ref(defaultStore.state.defaultWithReplies);
-
-const onExportSuccess = () => {
- os.alert({
- type: 'info',
- text: i18n.ts.exportRequested,
- });
-};
-
-const onImportSuccess = () => {
- os.alert({
- type: 'info',
- text: i18n.ts.importRequested,
- });
-};
-
-const onError = (ev) => {
- os.alert({
- type: 'error',
- text: ev.message,
- });
-};
-
-const exportNotes = () => {
- misskeyApi('i/export-notes', {}).then(onExportSuccess).catch(onError);
-};
-
-const exportFavorites = () => {
- misskeyApi('i/export-favorites', {}).then(onExportSuccess).catch(onError);
-};
-
-const exportClips = () => {
- misskeyApi('i/export-clips', {}).then(onExportSuccess).catch(onError);
-};
-
-const exportFollowing = () => {
- misskeyApi('i/export-following', {
- excludeMuting: excludeMutingUsers.value,
- excludeInactive: excludeInactiveUsers.value,
- })
- .then(onExportSuccess).catch(onError);
-};
-
-const exportBlocking = () => {
- misskeyApi('i/export-blocking', {}).then(onExportSuccess).catch(onError);
-};
-
-const exportUserLists = () => {
- misskeyApi('i/export-user-lists', {}).then(onExportSuccess).catch(onError);
-};
-
-const exportMuting = () => {
- misskeyApi('i/export-mute', {}).then(onExportSuccess).catch(onError);
-};
-
-const exportAntennas = () => {
- misskeyApi('i/export-antennas', {}).then(onExportSuccess).catch(onError);
-};
-
-const importFollowing = async (ev) => {
- const file = await selectFile(ev.currentTarget ?? ev.target);
- misskeyApi('i/import-following', {
- fileId: file.id,
- withReplies: withReplies.value,
- }).then(onImportSuccess).catch(onError);
-};
-
-const importNotes = async (ev) => {
- const file = await selectFile(ev.currentTarget ?? ev.target);
- misskeyApi('i/import-notes', {
- fileId: file.id,
- type: noteType.value,
- }).then(onImportSuccess).catch(onError);
-};
-
-const importUserLists = async (ev) => {
- const file = await selectFile(ev.currentTarget ?? ev.target);
- misskeyApi('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError);
-};
-
-const importMuting = async (ev) => {
- const file = await selectFile(ev.currentTarget ?? ev.target);
- misskeyApi('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError);
-};
-
-const importBlocking = async (ev) => {
- const file = await selectFile(ev.currentTarget ?? ev.target);
- misskeyApi('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
-};
-
-const importAntennas = async (ev) => {
- const file = await selectFile(ev.currentTarget ?? ev.target);
- misskeyApi('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError);
-};
-
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => []);
-
-definePageMetadata(() => ({
- title: i18n.ts.importAndExport,
- icon: 'ti ti-package',
-}));
-</script>
-
-<style module>
-.button {
- margin-right: 16px;
-}
-</style>
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index b7bf8c5dc1..a1e1460da1 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -4,39 +4,50 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :tabs="headerTabs" :actions="headerActions">
<MkSpacer :contentMax="900" :marginMin="20" :marginMax="32">
<div ref="el" class="vvcocwet" :class="{ wide: !narrow }">
<div class="body">
<div v-if="!narrow || currentPage?.route.name == null" class="nav">
- <div class="baaadecd">
+ <div class="_gaps_s">
<MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
- <MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
+ <MkInfo v-if="!store.r.enablePreferencesAutoCloudBackup.value && store.r.showPreferencesAutoCloudBackupSuggestion.value" class="info">
+ <div>{{ i18n.ts._preferencesBackup.autoPreferencesBackupIsNotEnabledForThisDevice }}</div>
+ <div><button class="_textButton" @click="enableAutoBackup">{{ i18n.ts.enable }}</button> | <button class="_textButton" @click="skipAutoBackup">{{ i18n.ts.skip }}</button></div>
+ </MkInfo>
+ <MkSuperMenu :def="menuDef" :grid="narrow" :searchIndex="SETTING_INDEX"></MkSuperMenu>
</div>
</div>
<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
- <div class="bkzroven" style="container-type: inline-size;">
- <RouterView nested/>
+ <div style="container-type: inline-size;">
+ <NestedRouterView/>
</div>
</div>
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script setup lang="ts">
-import { computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { computed, onActivated, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
+import type { PageMetadata } from '@/page.js';
+import type { SuperMenuDef } from '@/components/MkSuperMenu.vue';
import { i18n } from '@/i18n.js';
import MkInfo from '@/components/MkInfo.vue';
import MkSuperMenu from '@/components/MkSuperMenu.vue';
-import { signout, $i } from '@/account.js';
-import { clearCache } from '@/scripts/clear-cache.js';
+import { $i } from '@/i.js';
+import { clearCache } from '@/utility/clear-cache.js';
import { instance } from '@/instance.js';
-import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import * as os from '@/os.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
+import { searchIndexes } from '@/utility/autogen/settings-search-index.js';
+import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utility.js';
+import { store } from '@/store.js';
+import { signout } from '@/signout.js';
+
+const SETTING_INDEX = searchIndexes; // TODO: lazy load
const indexInfo = {
title: i18n.ts.settings,
@@ -44,7 +55,7 @@ const indexInfo = {
hideHeader: true,
};
const INFO = ref<PageMetadata>(indexInfo);
-const el = shallowRef<HTMLElement | null>(null);
+const el = useTemplateRef('el');
const childInfo = ref<null | PageMetadata>(null);
const router = useRouter();
@@ -59,8 +70,11 @@ const ro = new ResizeObserver((entries, observer) => {
narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
});
-const menuDef = computed(() => [{
- title: i18n.ts.basicSettings,
+function skipAutoBackup() {
+ store.set('showPreferencesAutoCloudBackupSuggestion', false);
+}
+
+const menuDef = computed<SuperMenuDef[]>(() => [{
items: [{
icon: 'ti ti-user',
text: i18n.ts.profile,
@@ -72,16 +86,6 @@ const menuDef = computed(() => [{
to: '/settings/privacy',
active: currentPage.value?.route.name === 'privacy',
}, {
- icon: 'ti ti-mood-happy',
- text: i18n.ts.emojiPicker,
- to: '/settings/emoji-picker',
- active: currentPage.value?.route.name === 'emojiPicker',
- }, {
- icon: 'ti ti-cloud',
- text: i18n.ts.drive,
- to: '/settings/drive',
- active: currentPage.value?.route.name === 'drive',
- }, {
icon: 'ti ti-bell',
text: i18n.ts.notifications,
to: '/settings/notifications',
@@ -98,70 +102,58 @@ const menuDef = computed(() => [{
active: currentPage.value?.route.name === 'security',
}],
}, {
- title: i18n.ts.clientSettings,
items: [{
icon: 'ti ti-adjustments',
- text: i18n.ts.general,
- to: '/settings/general',
- active: currentPage.value?.route.name === 'general',
+ text: i18n.ts.preferences,
+ to: '/settings/preferences',
+ active: currentPage.value?.route.name === 'preferences',
}, {
icon: 'ti ti-palette',
text: i18n.ts.theme,
to: '/settings/theme',
active: currentPage.value?.route.name === 'theme',
}, {
- icon: 'ti ti-menu-2',
- text: i18n.ts.navbar,
- to: '/settings/navbar',
- active: currentPage.value?.route.name === 'navbar',
- }, {
- icon: 'ti ti-equal-double',
- text: i18n.ts.statusbar,
- to: '/settings/statusbar',
- active: currentPage.value?.route.name === 'statusbar',
+ icon: 'ti ti-mood-happy',
+ text: i18n.ts.emojiPalette,
+ to: '/settings/emoji-palette',
+ active: currentPage.value?.route.name === 'emoji-palette',
}, {
icon: 'ti ti-music',
text: i18n.ts.sounds,
to: '/settings/sounds',
active: currentPage.value?.route.name === 'sounds',
}, {
+ icon: 'ti ti-accessible',
+ text: i18n.ts.accessibility,
+ to: '/settings/accessibility',
+ active: currentPage.value?.route.name === 'accessibility',
+ }, {
icon: 'ti ti-plug',
text: i18n.ts.plugins,
to: '/settings/plugin',
active: currentPage.value?.route.name === 'plugin',
}],
}, {
- title: i18n.ts.otherSettings,
items: [{
- icon: 'ti ti-badges',
- text: i18n.ts.roles,
- to: '/settings/roles',
- active: currentPage.value?.route.name === 'roles',
+ icon: 'ti ti-cloud',
+ text: i18n.ts.drive,
+ to: '/settings/drive',
+ active: currentPage.value?.route.name === 'drive',
}, {
icon: 'ti ti-ban',
text: i18n.ts.muteAndBlock,
to: '/settings/mute-block',
active: currentPage.value?.route.name === 'mute-block',
}, {
- icon: 'ti ti-api',
- text: 'API',
- to: '/settings/api',
- active: currentPage.value?.route.name === 'api',
- }, {
- icon: 'ti ti-webhook',
- text: 'Webhook',
- to: '/settings/webhook',
- active: currentPage.value?.route.name === 'webhook',
+ icon: 'ti ti-link',
+ text: i18n.ts._settings.serviceConnection,
+ to: '/settings/connect',
+ active: currentPage.value?.route.name === 'connect',
}, {
icon: 'ti ti-package',
- text: i18n.ts.importAndExport,
- to: '/settings/import-export',
- active: currentPage.value?.route.name === 'import-export',
- }, {
- icon: 'ti ti-plane',
- text: `${i18n.ts.accountMigration}`,
- to: '/settings/migration',
- active: currentPage.value?.route.name === 'migration',
+ text: i18n.ts._settings.accountData,
+ to: '/settings/account-data',
+ active: currentPage.value?.route.name === 'account-data',
}, {
icon: 'ti ti-dots',
text: i18n.ts.other,
@@ -170,10 +162,12 @@ const menuDef = computed(() => [{
}],
}, {
items: [{
- icon: 'ti ti-device-floppy',
- text: i18n.ts.preferencesBackups,
- to: '/settings/preferences-backups',
- active: currentPage.value?.route.name === 'preferences-backups',
+ type: 'button',
+ icon: 'ti ti-settings-2',
+ text: i18n.ts.preferencesProfile,
+ action: async (ev: MouseEvent) => {
+ os.popupMenu(getPreferencesProfileMenu(), ev.currentTarget ?? ev.target);
+ },
}, {
type: 'button',
icon: 'ti ti-trash',
@@ -242,37 +236,13 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => INFO.value);
+definePage(() => INFO.value);
// w 890
// h 700
</script>
<style lang="scss" scoped>
.vvcocwet {
- > .body {
- > .nav {
- .baaadecd {
- > .info {
- margin: 16px 0;
- }
-
- > .accounts {
- > .avatar {
- display: block;
- width: 50px;
- height: 50px;
- margin: 8px auto 16px auto;
- }
- }
- }
- }
-
- > .main {
- .bkzroven {
- }
- }
- }
-
&.wide {
> .body {
display: flex;
diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue
index ddc23945dd..902db9116c 100644
--- a/packages/frontend/src/pages/settings/migration.vue
+++ b/packages/frontend/src/pages/settings/migration.vue
@@ -66,13 +66,12 @@ import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkUserInfo from '@/components/MkUserInfo.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { signinRequired } from '@/account.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { ensureSignin } from '@/i.js';
+import { unisonReload } from '@/utility/unison-reload.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const moveToAccount = ref('');
const movedTo = ref<Misskey.entities.UserDetailed>();
@@ -120,11 +119,6 @@ async function save(): Promise<void> {
}
init();
-
-definePageMetadata(() => ({
- title: i18n.ts.accountMigration,
- icon: 'ti ti-plane',
-}));
</script>
<style lang="scss">
diff --git a/packages/frontend/src/pages/settings/mute-block.instance-mute.vue b/packages/frontend/src/pages/settings/mute-block.instance-mute.vue
index d1fde2fc1c..a0a40e4c72 100644
--- a/packages/frontend/src/pages/settings/mute-block.instance-mute.vue
+++ b/packages/frontend/src/pages/settings/mute-block.instance-mute.vue
@@ -19,11 +19,11 @@ import { ref, watch } from 'vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkButton from '@/components/MkButton.vue';
-import { signinRequired } from '@/account.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { ensureSignin } from '@/i.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-const $i = signinRequired();
+const $i = ensureSignin();
const instanceMutes = ref($i.mutedInstances.join('\n'));
const changed = ref(false);
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index d6ee45e074..4e8e99bd9a 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -4,132 +4,177 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <MkFolder>
- <template #icon><i class="ph-envelope ph-bold ph-lg"></i></template>
- <template #label>{{ i18n.ts.wordMute }}</template>
+<SearchMarker path="/settings/mute-block" :label="i18n.ts.muteAndBlock" icon="ti ti-ban" :keywords="['mute', 'block']">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/prohibited_3d.png" color="#ff2600">
+ <SearchKeyword>{{ i18n.ts._settings.muteAndBlockBanner }}</SearchKeyword>
+ </MkFeatureBanner>
- <div class="_gaps_m">
- <MkInfo>{{ i18n.ts.wordMuteDescription }}</MkInfo>
- <MkSwitch v-model="showSoftWordMutedWord">{{ i18n.ts.showMutedWord }}</MkSwitch>
- <XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
- </div>
- </MkFolder>
+ <div class="_gaps_s">
+ <SearchMarker
+ :label="i18n.ts.wordMute"
+ :keywords="['note', 'word', 'soft', 'mute', 'hide']"
+ >
+ <MkFolder>
+ <template #icon><i class="ph-envelope ph-bold ph-lg"></i></template>
+ <template #label>{{ i18n.ts.wordMute }}</template>
- <MkFolder>
- <template #icon><i class="ph-x-square ph-bold ph-lg"></i></template>
- <template #label>{{ i18n.ts.hardWordMute }}</template>
+ <div class="_gaps_m">
+ <MkInfo>{{ i18n.ts.wordMuteDescription }}</MkInfo>
- <div class="_gaps_m">
- <MkInfo>{{ i18n.ts.hardWordMuteDescription }}</MkInfo>
- <XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
- </div>
- </MkFolder>
+ <SearchMarker
+ :label="i18n.ts.showMutedWord"
+ :keywords="['show']"
+ >
+ <MkSwitch v-model="showSoftWordMutedWord">{{ i18n.ts.showMutedWord }}</MkSwitch>
+ </SearchMarker>
- <MkFolder v-if="instance.federation !== 'none'">
- <template #icon><i class="ti ti-planet-off"></i></template>
- <template #label>{{ i18n.ts.instanceMute }}</template>
+ <XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <XInstanceMute/>
- </MkFolder>
+ <SearchMarker
+ :label="i18n.ts.hardWordMute"
+ :keywords="['note', 'word', 'hard', 'mute', 'hide']"
+ >
+ <MkFolder>
+ <template #icon><i class="ph-x-square ph-bold ph-lg"></i></template>
+ <template #label>{{ i18n.ts.hardWordMute }}</template>
- <MkFolder>
- <template #icon><i class="ti ti-repeat-off"></i></template>
- <template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template>
+ <div class="_gaps_m">
+ <MkInfo>{{ i18n.ts.hardWordMuteDescription }}</MkInfo>
+ <XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkPagination :pagination="renoteMutingPagination">
- <template #empty>
- <div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
- <div>{{ i18n.ts.noUsers }}</div>
- </div>
- </template>
+ <SearchMarker
+ :label="i18n.ts.instanceMute"
+ :keywords="['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide']"
+ >
+ <MkFolder v-if="instance.federation !== 'none'">
+ <template #icon><i class="ti ti-planet-off"></i></template>
+ <template #label>{{ i18n.ts.instanceMute }}</template>
- <template #default="{ items }">
- <div class="_gaps_s">
- <div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedRenoteMuteItems.includes(item.id) }]">
- <div :class="$style.userItemMain">
- <MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
- <MkUserCardMini :user="item.mutee"/>
- </MkA>
- <button class="_button" :class="$style.userToggle" @click="toggleRenoteMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
- <button class="_button" :class="$style.remove" @click="unrenoteMute(item.mutee, $event)"><i class="ti ti-x"></i></button>
- </div>
- <div v-if="expandedRenoteMuteItems.includes(item.id)" :class="$style.userItemSub">
- <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
- </div>
- </div>
- </div>
- </template>
- </MkPagination>
- </MkFolder>
+ <XInstanceMute/>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-eye-off"></i></template>
- <template #label>{{ i18n.ts.mutedUsers }}</template>
+ <SearchMarker
+ :label="`${i18n.ts.mutedUsers} (${ i18n.ts.renote })`"
+ :keywords="['renote', 'mute', 'hide', 'user']"
+ >
+ <MkFolder>
+ <template #icon><i class="ti ti-repeat-off"></i></template>
+ <template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template>
- <MkPagination :pagination="mutingPagination">
- <template #empty>
- <div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
- <div>{{ i18n.ts.noUsers }}</div>
- </div>
- </template>
+ <MkPagination :pagination="renoteMutingPagination">
+ <template #empty>
+ <div class="_fullinfo">
+ <img :src="infoImageUrl" draggable="false"/>
+ <div>{{ i18n.ts.noUsers }}</div>
+ </div>
+ </template>
- <template #default="{ items }">
- <div class="_gaps_s">
- <div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedMuteItems.includes(item.id) }]">
- <div :class="$style.userItemMain">
- <MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
- <MkUserCardMini :user="item.mutee"/>
- </MkA>
- <button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
- <button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button>
- </div>
- <div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
- <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
- <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
- <div v-else>Period: {{ i18n.ts.indefinitely }}</div>
- </div>
- </div>
- </div>
- </template>
- </MkPagination>
- </MkFolder>
+ <template #default="{ items }">
+ <div class="_gaps_s">
+ <div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedRenoteMuteItems.includes(item.id) }]">
+ <div :class="$style.userItemMain">
+ <MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
+ <MkUserCardMini :user="item.mutee"/>
+ </MkA>
+ <button class="_button" :class="$style.userToggle" @click="toggleRenoteMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
+ <button class="_button" :class="$style.remove" @click="unrenoteMute(item.mutee, $event)"><i class="ti ti-x"></i></button>
+ </div>
+ <div v-if="expandedRenoteMuteItems.includes(item.id)" :class="$style.userItemSub">
+ <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
+ </div>
+ </div>
+ </div>
+ </template>
+ </MkPagination>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-ban"></i></template>
- <template #label>{{ i18n.ts.blockedUsers }}</template>
+ <SearchMarker
+ :label="i18n.ts.mutedUsers"
+ :keywords="['note', 'mute', 'hide', 'user']"
+ >
+ <MkFolder>
+ <template #icon><i class="ti ti-eye-off"></i></template>
+ <template #label>{{ i18n.ts.mutedUsers }}</template>
- <MkPagination :pagination="blockingPagination">
- <template #empty>
- <div class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
- <div>{{ i18n.ts.noUsers }}</div>
- </div>
- </template>
+ <MkPagination :pagination="mutingPagination">
+ <template #empty>
+ <div class="_fullinfo">
+ <img :src="infoImageUrl" draggable="false"/>
+ <div>{{ i18n.ts.noUsers }}</div>
+ </div>
+ </template>
- <template #default="{ items }">
- <div class="_gaps_s">
- <div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]">
- <div :class="$style.userItemMain">
- <MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)">
- <MkUserCardMini :user="item.blockee"/>
- </MkA>
- <button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
- <button class="_button" :class="$style.remove" @click="unblock(item.blockee, $event)"><i class="ti ti-x"></i></button>
- </div>
- <div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub">
- <div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div>
- <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
- <div v-else>Period: {{ i18n.ts.indefinitely }}</div>
- </div>
- </div>
- </div>
- </template>
- </MkPagination>
- </MkFolder>
-</div>
+ <template #default="{ items }">
+ <div class="_gaps_s">
+ <div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedMuteItems.includes(item.id) }]">
+ <div :class="$style.userItemMain">
+ <MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
+ <MkUserCardMini :user="item.mutee"/>
+ </MkA>
+ <button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
+ <button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button>
+ </div>
+ <div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
+ <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
+ <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
+ <div v-else>Period: {{ i18n.ts.indefinitely }}</div>
+ </div>
+ </div>
+ </div>
+ </template>
+ </MkPagination>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker
+ :label="i18n.ts.blockedUsers"
+ :keywords="['block', 'user']"
+ >
+ <MkFolder>
+ <template #icon><i class="ti ti-ban"></i></template>
+ <template #label>{{ i18n.ts.blockedUsers }}</template>
+
+ <MkPagination :pagination="blockingPagination">
+ <template #empty>
+ <div class="_fullinfo">
+ <img :src="infoImageUrl" draggable="false"/>
+ <div>{{ i18n.ts.noUsers }}</div>
+ </div>
+ </template>
+
+ <template #default="{ items }">
+ <div class="_gaps_s">
+ <div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]">
+ <div :class="$style.userItemMain">
+ <MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)">
+ <MkUserCardMini :user="item.blockee"/>
+ </MkA>
+ <button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
+ <button class="_button" :class="$style.remove" @click="unblock(item.blockee, $event)"><i class="ti ti-x"></i></button>
+ </div>
+ <div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub">
+ <div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div>
+ <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
+ <div v-else>Period: {{ i18n.ts.indefinitely }}</div>
+ </div>
+ </div>
+ </div>
+ </template>
+ </MkPagination>
+ </MkFolder>
+ </SearchMarker>
+ </div>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -139,18 +184,19 @@ import XWordMute from './mute-block.word-mute.vue';
import MkPagination from '@/components/MkPagination.vue';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import * as os from '@/os.js';
import { instance, infoImageUrl } from '@/instance.js';
-import { signinRequired } from '@/account.js';
+import { ensureSignin } from '@/i.js';
import MkInfo from '@/components/MkInfo.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import { defaultStore } from '@/store';
-import { reloadAsk } from '@/scripts/reload-ask.js';
+import { reloadAsk } from '@/utility/reload-ask.js';
+import { prefer } from '@/preferences.js';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
-const $i = signinRequired();
+const $i = ensureSignin();
const renoteMutingPagination = {
endpoint: 'renote-mute/list' as const,
@@ -171,7 +217,7 @@ const expandedRenoteMuteItems = ref([]);
const expandedMuteItems = ref([]);
const expandedBlockItems = ref([]);
-const showSoftWordMutedWord = computed(defaultStore.makeGetterSetter('showSoftWordMutedWord'));
+const showSoftWordMutedWord = prefer.model('showSoftWordMutedWord');
watch([
showSoftWordMutedWord,
@@ -248,7 +294,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.muteAndBlock,
icon: 'ti ti-ban',
}));
diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue
index c38cdc4fc2..706cb731eb 100644
--- a/packages/frontend/src/pages/settings/navbar.vue
+++ b/packages/frontend/src/pages/settings/navbar.vue
@@ -53,22 +53,24 @@ import FormSlot from '@/components/form/slot.vue';
import MkContainer from '@/components/MkContainer.vue';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar.js';
-import { defaultStore } from '@/store.js';
-import { reloadAsk } from '@/scripts/reload-ask.js';
+import { store } from '@/store.js';
+import { reloadAsk } from '@/utility/reload-ask.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
+import { prefer } from '@/preferences.js';
+import { PREF_DEF } from '@/preferences/def.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
-const items = ref(defaultStore.state.menu.map(x => ({
+const items = ref(prefer.s.menu.map(x => ({
id: Math.random().toString(),
type: x,
})));
-const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
+const menuDisplay = computed(store.makeGetterSetter('menuDisplay'));
async function addItem() {
- const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
+ const menu = Object.keys(navbarItemDef).filter(k => !prefer.s.menu.includes(k));
const { canceled, result: item } = await os.select({
title: i18n.ts.addItem,
items: [...menu.map(k => ({
@@ -89,12 +91,12 @@ function removeItem(index: number) {
}
async function save() {
- defaultStore.set('menu', items.value.map(x => x.type));
+ prefer.commit('menu', items.value.map(x => x.type));
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
}
function reset() {
- items.value = defaultStore.def.menu.default.map(x => ({
+ items.value = PREF_DEF.menu.default.map(x => ({
id: Math.random().toString(),
type: x,
}));
@@ -104,7 +106,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.navbar,
icon: 'ti ti-list',
}));
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index 8573fbf2b3..3c1b6c8032 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -5,6 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/bell_3d.png" color="#ffff00">
+ <SearchKeyword>{{ i18n.ts._settings.notificationsBanner }}</SearchKeyword>
+ </MkFeatureBanner>
+
<FormSection first>
<template #label>{{ i18n.ts.notificationRecieveConfig }}</template>
<div class="_gaps_s">
@@ -34,7 +38,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormSection>
<div class="_gaps_m">
<FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink>
- <FormLink @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink>
</div>
</FormSection>
<FormSection>
@@ -62,35 +65,33 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef, computed } from 'vue';
-import XNotificationConfig, { type NotificationConfig } from './notifications.notification-config.vue';
+import { useTemplateRef, computed } from 'vue';
+import { notificationTypes } from '@@/js/const.js';
+import XNotificationConfig from './notifications.notification-config.vue';
+import 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';
import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os.js';
-import { signinRequired } from '@/account.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { ensureSignin } from '@/i.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
-import { notificationTypes } from '@@/js/const.js';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
-const $i = signinRequired();
+const $i = ensureSignin();
const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] satisfies (typeof notificationTypes[number])[] as string[];
-const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login', 'scheduledNoteFailed', 'scheduledNotePosted'] satisfies (typeof notificationTypes[number])[] as string[];
+const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login', 'createToken', 'scheduledNoteFailed', 'scheduledNotePosted'] satisfies (typeof notificationTypes[number])[] as string[];
-const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
+const allowButton = useTemplateRef('allowButton');
const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
const sendReadMessage = computed(() => pushRegistrationInServer.value?.sendReadMessage || false);
const userLists = await misskeyApi('users/lists/list');
-async function readAllUnreadNotes() {
- await os.apiWithDialog('i/read-all-unread-notes');
-}
-
async function readAllNotifications() {
await os.apiWithDialog('notifications/mark-all-as-read');
}
@@ -137,7 +138,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.notifications,
icon: 'ti ti-bell',
}));
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index abe4524126..e6405954e8 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -4,124 +4,180 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <!--
- <MkSwitch v-model="$i.injectFeaturedNote" @update:model-value="onChangeInjectFeaturedNote">
- <template #label>{{ i18n.ts.showFeaturedNotesInTimeline }}</template>
- </MkSwitch>
- -->
+<SearchMarker path="/settings/other" :label="i18n.ts.other" :keywords="['other']" icon="ti ti-dots">
+ <div class="_gaps_m">
+ <!--
+ <MkSwitch v-model="$i.injectFeaturedNote" @update:model-value="onChangeInjectFeaturedNote">
+ <template #label>{{ i18n.ts.showFeaturedNotesInTimeline }}</template>
+ </MkSwitch>
+ -->
- <!--
- <MkSwitch v-model="reportError">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></MkSwitch>
- -->
+ <!--
+ <MkSwitch v-model="reportError">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></MkSwitch>
+ -->
- <FormSection first>
<div class="_gaps_s">
- <MkFolder>
- <template #icon><i class="ti ti-info-circle"></i></template>
- <template #label>{{ i18n.ts.accountInfo }}</template>
+ <SearchMarker :keywords="['account', 'info']">
+ <MkFolder>
+ <template #icon><i class="ti ti-info-circle"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.accountInfo }}</SearchLabel></template>
- <div class="_gaps_m">
- <MkKeyValue>
- <template #key>ID</template>
- <template #value><span class="_monospace">{{ $i.id }}</span></template>
- </MkKeyValue>
+ <div class="_gaps_m">
+ <MkKeyValue>
+ <template #key>ID</template>
+ <template #value><span class="_monospace">{{ $i.id }}</span></template>
+ </MkKeyValue>
- <MkKeyValue>
- <template #key>{{ i18n.ts.registeredDate }}</template>
- <template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
- </MkKeyValue>
- </div>
- </MkFolder>
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.registeredDate }}</template>
+ <template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
+ </MkKeyValue>
- <MkFolder>
- <template #icon><i class="ph-database ph-bold ph-lg"></i></template>
- <template #label>{{ i18n.ts._dataRequest.title }}</template>
+ <MkFolder>
+ <template #icon><i class="ti ti-badges"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template>
- <div class="_gaps_m">
- <FormInfo warn>{{ i18n.ts._dataRequest.warn }}</FormInfo>
- <FormInfo>{{ i18n.ts._dataRequest.text }}</FormInfo>
- <MkButton primary @click="exportData">{{ i18n.ts._dataRequest.button }}</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps_s">
+ <div v-for="policy in Object.keys($i.policies)" :key="policy">
+ {{ policy }} ... {{ $i.policies[policy] }}
+ </div>
+ </div>
+ </MkFolder>
+ </div>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-alert-triangle"></i></template>
- <template #label>{{ i18n.ts.closeAccount }}</template>
+ <SearchMarker :keywords="['roles']">
+ <MkFolder>
+ <template #icon><i class="ti ti-badges"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.rolesAssignedToMe }}</SearchLabel></template>
- <div class="_gaps_m">
- <FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo>
- <FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo>
- <MkButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</MkButton>
- <MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton>
- </div>
- </MkFolder>
+ <MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-flask"></i></template>
- <template #label>{{ i18n.ts.experimentalFeatures }}</template>
+ <SearchMarker :keywords="['account', 'move', 'migration']">
+ <MkFolder>
+ <template #icon><i class="ti ti-plane"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.accountMigration }}</SearchLabel></template>
- <div class="_gaps_m">
- <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>
+ <XMigration/>
+ </MkFolder>
+ </SearchMarker>
- <MkFolder>
- <template #icon><i class="ti ti-code"></i></template>
- <template #label>{{ i18n.ts.developer }}</template>
+ <SearchMarker :keywords="['account', 'export', 'data']">
+ <MkFolder>
+ <template #icon><i class="ph-database ph-bold ph-lg"></i></template>
+ <template #label>{{ i18n.ts._dataRequest.title }}</template>
- <div class="_gaps_m">
- <MkSwitch v-model="devMode">
- <template #label>{{ i18n.ts.devMode }}</template>
- </MkSwitch>
- </div>
- </MkFolder>
+ <div class="_gaps_m">
+ <FormInfo warn>{{ i18n.ts._dataRequest.warn }}</FormInfo>
+ <FormInfo>{{ i18n.ts._dataRequest.text }}</FormInfo>
+ <MkButton primary @click="exportData">{{ i18n.ts._dataRequest.button }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['account', 'close', 'delete']">
+ <MkFolder>
+ <template #icon><i class="ti ti-alert-triangle"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.closeAccount }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo>
+ <FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo>
+ <MkButton v-if="!$i.isDeleted" danger @click="deleteAccount"><SearchKeyword>{{ i18n.ts._accountDelete.requestAccountDelete }}</SearchKeyword></MkButton>
+ <MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['experimental', 'feature', 'flags']">
+ <MkFolder>
+ <template #icon><i class="ti ti-flask"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.experimentalFeatures }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <MkSwitch v-model="enableCondensedLine">
+ <template #label>Enable condensed line</template>
+ </MkSwitch>
+ <MkSwitch v-model="skipNoteRender">
+ <template #label>Enable note render skipping</template>
+ </MkSwitch>
+ <MkSwitch v-model="stackingRouterView">
+ <template #label>Enable stacking router view</template>
+ </MkSwitch>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['developer', 'mode', 'debug']">
+ <MkFolder>
+ <template #icon><i class="ti ti-code"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.developer }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <MkSwitch v-model="devMode">
+ <template #label>{{ i18n.ts.devMode }}</template>
+ </MkSwitch>
+ </div>
+ </MkFolder>
+ </SearchMarker>
</div>
- </FormSection>
- <FormSection>
+ <hr>
+
<FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink>
- </FormSection>
- <FormSection>
- <div class="_gaps_s">
- <MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch>
- <MkButton danger @click="updateRepliesAll(true)"><i class="ph-chats ph-bold ph-lg"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton>
- <MkButton danger @click="updateRepliesAll(false)"><i class="ph-chat ph-bold ph-lg"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton>
- </div>
- </FormSection>
-</div>
+ <hr>
+
+ <FormSection>
+ <div class="_gaps_s">
+ <MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch>
+ <MkButton danger @click="updateRepliesAll(true)"><i class="ph-chats ph-bold ph-lg"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton>
+ <MkButton danger @click="updateRepliesAll(false)"><i class="ph-chat ph-bold ph-lg"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton>
+ </div>
+ </FormSection>
+
+ <hr>
+
+ <FormSlot>
+ <MkButton danger @click="migrate"><i class="ti ti-refresh"></i> {{ i18n.ts.migrateOldSettings }}</MkButton>
+ <template #caption>{{ i18n.ts.migrateOldSettings_description }}</template>
+ </FormSlot>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
import { computed, watch } from 'vue';
+import XMigration from './migration.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormLink from '@/components/form/link.vue';
import MkFolder from '@/components/MkFolder.vue';
import FormInfo from '@/components/MkInfo.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
+import FormSlot from '@/components/form/slot.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { signout, signinRequired } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { ensureSignin } from '@/i.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { reloadAsk } from '@/scripts/reload-ask.js';
+import { definePage } from '@/page.js';
+import { reloadAsk } from '@/utility/reload-ask.js';
import FormSection from '@/components/form/section.vue';
+import { prefer } from '@/preferences.js';
+import MkRolePreview from '@/components/MkRolePreview.vue';
+import { signout } from '@/signout.js';
+import { migrateOldSettings } from '@/pref-migrate.js';
-const $i = signinRequired();
+const $i = ensureSignin();
-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'));
+const reportError = prefer.model('reportError');
+const enableCondensedLine = prefer.model('enableCondensedLine');
+const skipNoteRender = prefer.model('skipNoteRender');
+const devMode = prefer.model('devMode');
+const stackingRouterView = prefer.model('experimental.stackingRouterView');
watch(skipNoteRender, async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
@@ -151,14 +207,9 @@ async function deleteAccount() {
await signout();
}
-async function updateRepliesAll(withReplies: boolean) {
- const { canceled } = await os.confirm({
- type: 'warning',
- text: withReplies ? i18n.ts.confirmShowRepliesAll : i18n.ts.confirmHideRepliesAll,
- });
- if (canceled) return;
-
- misskeyApi('following/update-all', { withReplies });
+function migrate() {
+ os.waiting();
+ migrateOldSettings();
}
const exportData = () => {
@@ -185,7 +236,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.other,
icon: 'ti ti-dots',
}));
diff --git a/packages/frontend/src/pages/settings/plugin.install.vue b/packages/frontend/src/pages/settings/plugin.install.vue
index 3ab26e80d9..22b53b4b96 100644
--- a/packages/frontend/src/pages/settings/plugin.install.vue
+++ b/packages/frontend/src/pages/settings/plugin.install.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkCodeEditor>
<div>
- <MkButton :disabled="code == null" primary inline @click="install"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
+ <MkButton :disabled="code == null || code.trim() === ''" primary inline @click="install"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
</div>
</div>
</template>
@@ -23,11 +23,12 @@ import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkButton from '@/components/MkButton.vue';
import FormInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
-import { installPlugin } from '@/scripts/install-plugin.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
+import { installPlugin } from '@/plugin.js';
+import { useRouter } from '@/router.js';
+const router = useRouter();
const code = ref<string | null>(null);
async function install() {
@@ -36,10 +37,9 @@ async function install() {
try {
await installPlugin(code.value);
os.success();
+ code.value = null;
- nextTick(() => {
- unisonReload();
- });
+ router.push('/settings/plugin');
} catch (err) {
os.alert({
type: 'error',
@@ -53,7 +53,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts._plugin.install,
icon: 'ti ti-download',
}));
diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue
index 3c3dcfe41e..16d5947ad2 100644
--- a/packages/frontend/src/pages/settings/plugin.vue
+++ b/packages/frontend/src/pages/settings/plugin.vue
@@ -4,76 +4,97 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
+<SearchMarker path="/settings/plugin" :label="i18n.ts.plugins" :keywords="['plugin', 'addon', 'extension']" icon="ti ti-plug">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/electric_plug_3d.png" color="#ffbb00">
+ <SearchKeyword>{{ i18n.ts._settings.pluginBanner }}</SearchKeyword>
+ </MkFeatureBanner>
- <FormSection>
- <template #label>{{ i18n.ts.manage }}</template>
- <div class="_gaps_s">
- <div v-for="plugin in plugins" :key="plugin.id" class="_panel _gaps_m" style="padding: 20px;">
- <div class="_gaps_s">
- <span style="display: flex; align-items: center;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
- <MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch>
- </div>
+ <FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
- <div class="_gaps_s">
- <MkKeyValue>
- <template #key>{{ i18n.ts.author }}</template>
- <template #value>{{ plugin.author }}</template>
- </MkKeyValue>
- <MkKeyValue>
- <template #key>{{ i18n.ts.description }}</template>
- <template #value>{{ plugin.description }}</template>
- </MkKeyValue>
- <MkKeyValue>
- <template #key>{{ i18n.ts.permission }}</template>
- <template #value>
- <ul style="margin-top: 0; margin-bottom: 0;">
- <li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
- <li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li>
- </ul>
- </template>
- </MkKeyValue>
- </div>
-
- <div class="_buttons">
- <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="ti ti-settings"></i> {{ i18n.ts.settings }}</MkButton>
- <MkButton inline danger @click="uninstall(plugin)"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton>
- </div>
+ <FormSection>
+ <template #label>{{ i18n.ts.manage }}</template>
+ <div class="_gaps_s">
+ <MkFolder v-for="plugin in plugins" :key="plugin.installId">
+ <template #icon><i class="ti ti-plug"></i></template>
+ <template #suffix>
+ <i v-if="plugin.active" class="ti ti-player-play" style="color: var(--MI_THEME-success);"></i>
+ <i v-else class="ti ti-player-pause" style="opacity: 0.7;"></i>
+ </template>
+ <template #label>
+ <div :style="plugin.active ? '' : 'opacity: 0.7;'">
+ {{ plugin.name }}
+ <span style="margin-left: 1em; opacity: 0.7;">v{{ plugin.version }}</span>
+ </div>
+ </template>
+ <template #caption>
+ {{ plugin.description }}
+ </template>
+ <template #footer>
+ <div class="_buttons">
+ <MkButton :disabled="!plugin.active" @click="reload(plugin)"><i class="ti ti-refresh"></i> {{ i18n.ts.reload }}</MkButton>
+ <MkButton danger @click="uninstall(plugin)"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton>
+ <MkButton v-if="plugin.config" style="margin-left: auto;" @click="config(plugin)"><i class="ti ti-settings"></i> {{ i18n.ts.settings }}</MkButton>
+ </div>
+ </template>
- <MkFolder>
- <template #icon><i class="ti ti-terminal-2"></i></template>
- <template #label>{{ i18n.ts._plugin.viewLog }}</template>
+ <div class="_gaps_m">
+ <div class="_gaps_s">
+ <MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch>
+ </div>
- <div class="_gaps_s">
- <div class="_buttons">
- <MkButton inline @click="copy(pluginLogs.get(plugin.id)?.join('\n'))"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
+ <div class="_gaps_s">
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.author }}</template>
+ <template #value>{{ plugin.author }}</template>
+ </MkKeyValue>
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.description }}</template>
+ <template #value>{{ plugin.description }}</template>
+ </MkKeyValue>
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.permission }}</template>
+ <template #value>
+ <ul style="margin-top: 0; margin-bottom: 0;">
+ <li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
+ <li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li>
+ </ul>
+ </template>
+ </MkKeyValue>
</div>
- <MkCode :code="pluginLogs.get(plugin.id)?.join('\n') ?? ''"/>
- </div>
- </MkFolder>
+ <div class="_gaps_s">
+ <MkFolder>
+ <template #icon><i class="ti ti-terminal-2"></i></template>
+ <template #label>{{ i18n.ts.logs }}</template>
- <MkFolder>
- <template #icon><i class="ti ti-code"></i></template>
- <template #label>{{ i18n.ts._plugin.viewSource }}</template>
+ <div>
+ <div v-for="log in pluginLogs.get(plugin.installId)" :class="[$style.log, { [$style.isSystemLog]: log.isSystem }]">
+ <div class="_monospace">{{ timeToHhMmSs(log.at) }} {{ log.message }}</div>
+ </div>
+ </div>
+ </MkFolder>
- <div class="_gaps_s">
- <div class="_buttons">
- <MkButton inline @click="copy(plugin.src)"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
- </div>
+ <MkFolder :withSpacer="false">
+ <template #icon><i class="ti ti-code"></i></template>
+ <template #label>{{ i18n.ts._plugin.viewSource }}</template>
- <MkCode :code="plugin.src ?? ''" lang="is"/>
+ <div class="_gaps_s">
+ <MkCode :code="plugin.src ?? ''" lang="ais"/>
+ </div>
+ </MkFolder>
+ </div>
</div>
</MkFolder>
</div>
- </div>
- </FormSection>
-</div>
+ </FormSection>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
import { nextTick, ref, computed } from 'vue';
+import type { Plugin } from '@/plugin.js';
import FormLink from '@/components/form/link.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSection from '@/components/form/section.vue';
@@ -81,66 +102,58 @@ import MkButton from '@/components/MkButton.vue';
import MkCode from '@/components/MkCode.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
-import * as os from '@/os.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { ColdDeviceStorage } from '@/store.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { pluginLogs } from '@/plugin.js';
+import { definePage } from '@/page.js';
+import { changePluginActive, configPlugin, pluginLogs, uninstallPlugin, reloadPlugin } from '@/plugin.js';
+import { prefer } from '@/preferences.js';
+import * as os from '@/os.js';
-const plugins = ref(ColdDeviceStorage.get('plugins'));
+const plugins = prefer.r.plugins;
-async function uninstall(plugin) {
- ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id));
- await os.apiWithDialog('i/revoke-token', {
- token: plugin.token,
- });
- nextTick(() => {
- unisonReload();
+async function uninstall(plugin: Plugin) {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ text: i18n.tsx.removeAreYouSure({ x: plugin.name }),
});
-}
+ if (canceled) return;
+
+ await uninstallPlugin(plugin);
-function copy(text) {
- copyToClipboard(text ?? '');
os.success();
}
-// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする
-async function config(plugin) {
- const config = plugin.config;
- for (const key in plugin.configData) {
- config[key].default = plugin.configData[key];
- }
-
- const { canceled, result } = await os.form(plugin.name, config);
- if (canceled) return;
-
- const coldPlugins = ColdDeviceStorage.get('plugins');
- coldPlugins.find(p => p.id === plugin.id)!.configData = result;
- ColdDeviceStorage.set('plugins', coldPlugins);
+function reload(plugin: Plugin) {
+ reloadPlugin(plugin);
+}
- nextTick(() => {
- location.reload();
- });
+async function config(plugin: Plugin) {
+ await configPlugin(plugin);
}
-function changeActive(plugin, active) {
- const coldPlugins = ColdDeviceStorage.get('plugins');
- coldPlugins.find(p => p.id === plugin.id)!.active = active;
- ColdDeviceStorage.set('plugins', coldPlugins);
+function changeActive(plugin: Plugin, active: boolean) {
+ changePluginActive(plugin, active);
+}
- nextTick(() => {
- location.reload();
- });
+function timeToHhMmSs(unixtime: number) {
+ return new Date(unixtime).toTimeString().split(' ')[0];
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.plugins,
icon: 'ti ti-plug',
}));
</script>
+
+<style module>
+.log {
+}
+
+.isSystemLog {
+ opacity: 0.5;
+}
+</style>
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
deleted file mode 100644
index f7dcc7b139..0000000000
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ /dev/null
@@ -1,489 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div class="_gaps_m">
- <div :class="$style.buttons">
- <MkButton inline primary @click="saveNew">{{ i18n.ts._preferencesBackups.saveNew }}</MkButton>
- <MkButton inline @click="loadFile">{{ i18n.ts._preferencesBackups.loadFile }}</MkButton>
- </div>
-
- <FormSection>
- <template #label>{{ i18n.ts._preferencesBackups.list }}</template>
- <template v-if="profiles && Object.keys(profiles).length > 0">
- <div class="_gaps_s">
- <div
- v-for="(profile, id) in profiles"
- :key="id"
- class="_panel"
- :class="$style.profile"
- @click="$event => menu($event, id)"
- @contextmenu.prevent.stop="$event => menu($event, id)"
- >
- <div :class="$style.profileName">{{ profile.name }}</div>
- <div :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.createdAt({ date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div>
- <div v-if="profile.updatedAt" :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.updatedAt({ date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div>
- </div>
- </div>
- </template>
- <div v-else-if="profiles">
- <MkInfo>{{ i18n.ts._preferencesBackups.noBackups }}</MkInfo>
- </div>
- <MkLoading v-else/>
- </FormSection>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, onUnmounted, ref } from 'vue';
-import { v4 as uuid } from 'uuid';
-import { version, host } from '@@/js/config.js';
-import FormSection from '@/components/form/section.vue';
-import MkButton from '@/components/MkButton.vue';
-import MkInfo from '@/components/MkInfo.vue';
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { ColdDeviceStorage, defaultStore } from '@/store.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
-import { useStream } from '@/stream.js';
-import { $i } from '@/account.js';
-import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { miLocalStorage } from '@/local-storage.js';
-
-const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
- 'collapseRenotes',
- 'collapseNotesRepliedTo',
- 'menu',
- 'visibility',
- 'localOnly',
- 'statusbars',
- 'widgets',
- 'tl',
- 'pinnedUserLists',
- 'overridedDeviceKind',
- 'serverDisconnectedBehavior',
- 'nsfw',
- 'highlightSensitiveMedia',
- 'animation',
- 'animatedMfm',
- 'advancedMfm',
- 'showReactionsCount',
- 'loadRawImages',
- 'warnMissingAltText',
- 'enableFaviconNotificationDot',
- 'imageNewTab',
- 'dataSaver',
- 'disableCatSpeak',
- 'disableShowingAnimatedImages',
- 'emojiStyle',
- 'menuStyle',
- 'useBlurEffectForModal',
- 'useBlurEffect',
- 'showFixedPostForm',
- 'showFixedPostFormInChannel',
- 'enableInfiniteScroll',
- 'useReactionPickerForContextMenu',
- 'showGapBetweenNotesInTimeline',
- 'instanceTicker',
- 'emojiPickerScale',
- 'emojiPickerWidth',
- 'emojiPickerHeight',
- 'emojiPickerStyle',
- 'defaultSideView',
- 'menuDisplay',
- 'reportError',
- 'squareAvatars',
- 'showAvatarDecorations',
- 'numberOfPageCache',
- 'showNoteActionsOnlyHover',
- 'showClipButtonInNoteFooter',
- 'reactionsDisplaySize',
- 'forceShowAds',
- 'oneko',
- 'numberOfReplies',
- 'aiChanMode',
- 'devMode',
- 'mediaListWithOneImageAppearance',
- 'notificationPosition',
- 'notificationStackAxis',
- 'keepScreenOn',
- 'defaultWithReplies',
- 'disableStreamingTimeline',
- 'useGroupedNotifications',
- 'sound_masterVolume',
- 'sound_note',
- 'sound_noteMy',
- 'sound_notification',
-];
-const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
- 'lightTheme',
- 'darkTheme',
- 'syncDeviceDarkMode',
- 'plugins',
-];
-
-const scope = ['clientPreferencesProfiles'];
-
-const profileProps = ['name', 'createdAt', 'updatedAt', 'misskeyVersion', 'settings', 'host'];
-
-type Profile = {
- name: string;
- createdAt: string;
- updatedAt: string | null;
- misskeyVersion: string;
- host: string;
- settings: {
- hot: Record<keyof typeof defaultStoreSaveKeys, unknown>;
- cold: Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
- fontSize: string | null;
- lang: string | null;
- cornerRadius: string | null;
- useSystemFont: 't' | null;
- wallpaper: string | null;
- };
-};
-
-const connection = $i && useStream().useChannel('main');
-
-const profiles = ref<Record<string, Profile> | null>(null);
-
-misskeyApi('i/registry/get-all', { scope })
- .then(res => {
- profiles.value = res || {};
- });
-
-function isObject(value: unknown): value is Record<string, unknown> {
- return value != null && typeof value === 'object' && !Array.isArray(value);
-}
-
-function validate(profile: any): void {
- if (!isObject(profile)) throw new Error('not an object');
-
- // Check if unnecessary properties exist
- if (Object.keys(profile).some(key => !profileProps.includes(key))) throw new Error('Unnecessary properties exist');
-
- if (!profile.name) throw new Error('Missing required prop: name');
- if (!profile.misskeyVersion) throw new Error('Missing required prop: misskeyVersion');
-
- // Check if createdAt and updatedAt is Date
- // https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date
- if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt as any).getTime())) throw new Error('createdAt is falsy or not Date');
- if (profile.updatedAt) {
- if (Number.isNaN(new Date(profile.updatedAt as any).getTime())) {
- throw new Error('updatedAt is not Date');
- }
- } else if (profile.updatedAt !== null) {
- throw new Error('updatedAt is not null');
- }
-
- if (!profile.settings) throw new Error('Missing required prop: settings');
- if (!isObject(profile.settings)) throw new Error('Invalid prop: settings');
-}
-
-function getSettings(): Profile['settings'] {
- const hot = {} as Record<keyof typeof defaultStoreSaveKeys, unknown>;
- for (const key of defaultStoreSaveKeys) {
- hot[key] = defaultStore.state[key];
- }
-
- const cold = {} as Record<keyof typeof coldDeviceStorageSaveKeys, unknown>;
- for (const key of coldDeviceStorageSaveKeys) {
- cold[key] = ColdDeviceStorage.get(key);
- }
-
- return {
- hot,
- cold,
- fontSize: miLocalStorage.getItem('fontSize'),
- lang: miLocalStorage.getItem('lang'),
- cornerRadius: miLocalStorage.getItem('cornerRadius'),
- useSystemFont: miLocalStorage.getItem('useSystemFont') as 't' | null,
- wallpaper: miLocalStorage.getItem('wallpaper'),
- };
-}
-
-async function saveNew(): Promise<void> {
- if (!profiles.value) return;
-
- const { canceled, result: name } = await os.inputText({
- title: i18n.ts._preferencesBackups.inputName,
- default: '',
- });
- if (canceled) return;
-
- if (Object.values(profiles.value).some(x => x.name === name)) {
- return os.alert({
- title: i18n.ts._preferencesBackups.cannotSave,
- text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }),
- });
- }
-
- const id = uuid();
- const profile: Profile = {
- name,
- createdAt: (new Date()).toISOString(),
- updatedAt: null,
- misskeyVersion: version,
- host,
- settings: getSettings(),
- };
- await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
-}
-
-function loadFile(): void {
- const input = document.createElement('input');
- input.type = 'file';
- input.multiple = false;
- input.onchange = async () => {
- if (!profiles.value) return;
- if (!input.files || input.files.length === 0) return;
-
- const file = input.files[0];
-
- if (file.type !== 'application/json') {
- return os.alert({
- type: 'error',
- title: i18n.ts._preferencesBackups.cannotLoad,
- text: i18n.ts._preferencesBackups.invalidFile,
- });
- }
-
- let profile: Profile;
- try {
- profile = JSON.parse(await file.text()) as unknown as Profile;
- validate(profile);
- } catch (err) {
- return os.alert({
- type: 'error',
- title: i18n.ts._preferencesBackups.cannotLoad,
- text: (err as any)?.message ?? '',
- });
- }
-
- const id = uuid();
- await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
-
- // 一応廃棄
- (window as any).__misskey_input_ref__ = null;
- };
-
- // https://qiita.com/fukasawah/items/b9dc732d95d99551013d
- // iOS Safari で正常に動かす為のおまじない
- (window as any).__misskey_input_ref__ = input;
-
- input.click();
-}
-
-async function applyProfile(id: string): Promise<void> {
- if (!profiles.value) return;
-
- const profile = profiles.value[id];
-
- const { canceled: cancel1 } = await os.confirm({
- type: 'warning',
- title: i18n.ts._preferencesBackups.apply,
- text: i18n.tsx._preferencesBackups.applyConfirm({ name: profile.name }),
- });
- if (cancel1) return;
-
- // TODO: バージョン or ホストが違ったらさらに警告を表示
-
- const settings = profile.settings;
-
- // defaultStore
- for (const key of defaultStoreSaveKeys) {
- if (settings.hot[key] !== undefined) {
- defaultStore.set(key, settings.hot[key]);
- }
- }
-
- // coldDeviceStorage
- for (const key of coldDeviceStorageSaveKeys) {
- if (settings.cold[key] !== undefined) {
- ColdDeviceStorage.set(key, settings.cold[key]);
- }
- }
-
- // fontSize
- if (settings.fontSize) {
- miLocalStorage.setItem('fontSize', settings.fontSize);
- } else {
- miLocalStorage.removeItem('fontSize');
- }
-
- // lang
- if (settings.lang) {
- miLocalStorage.setItem('lang', settings.lang);
- } else {
- miLocalStorage.removeItem('lang');
- }
-
- // cornerRadius
- if (settings.cornerRadius) {
- miLocalStorage.setItem('cornerRadius', settings.cornerRadius);
- } else {
- miLocalStorage.removeItem('cornerRadius');
- }
-
- // useSystemFont
- if (settings.useSystemFont) {
- miLocalStorage.setItem('useSystemFont', settings.useSystemFont);
- } else {
- miLocalStorage.removeItem('useSystemFont');
- }
-
- // wallpaper
- if (settings.wallpaper != null) {
- miLocalStorage.setItem('wallpaper', settings.wallpaper);
- } else {
- miLocalStorage.removeItem('wallpaper');
- }
-
- const { canceled: cancel2 } = await os.confirm({
- type: 'info',
- text: i18n.ts.reloadToApplySetting,
- });
- if (cancel2) return;
-
- unisonReload();
-}
-
-async function deleteProfile(id: string): Promise<void> {
- if (!profiles.value) return;
-
- const { canceled } = await os.confirm({
- type: 'info',
- title: i18n.ts.delete,
- text: i18n.tsx.deleteAreYouSure({ x: profiles.value[id].name }),
- });
- if (canceled) return;
-
- await os.apiWithDialog('i/registry/remove', { scope, key: id });
- delete profiles.value[id];
-}
-
-async function save(id: string): Promise<void> {
- if (!profiles.value) return;
-
- const { name, createdAt } = profiles.value[id];
-
- const { canceled } = await os.confirm({
- type: 'info',
- title: i18n.ts._preferencesBackups.save,
- text: i18n.tsx._preferencesBackups.saveConfirm({ name }),
- });
- if (canceled) return;
-
- const profile: Profile = {
- name,
- createdAt,
- updatedAt: (new Date()).toISOString(),
- misskeyVersion: version,
- host,
- settings: getSettings(),
- };
- await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile });
-}
-
-async function rename(id: string): Promise<void> {
- if (!profiles.value) return;
-
- const { canceled: cancel1, result: name } = await os.inputText({
- title: i18n.ts._preferencesBackups.inputName,
- default: '',
- });
- if (cancel1 || profiles.value[id].name === name) return;
-
- if (Object.values(profiles.value).some(x => x.name === name)) {
- return os.alert({
- title: i18n.ts._preferencesBackups.cannotSave,
- text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }),
- });
- }
-
- const registry = Object.assign({}, { ...profiles.value[id] });
-
- const { canceled: cancel2 } = await os.confirm({
- type: 'info',
- title: i18n.ts.rename,
- text: i18n.tsx._preferencesBackups.renameConfirm({ old: registry.name, new: name }),
- });
- if (cancel2) return;
-
- registry.name = name;
- await os.apiWithDialog('i/registry/set', { scope, key: id, value: registry });
-}
-
-function menu(ev: MouseEvent, profileId: string) {
- if (!profiles.value) return;
-
- return os.popupMenu([{
- text: i18n.ts._preferencesBackups.apply,
- icon: 'ti ti-check',
- action: () => applyProfile(profileId),
- }, {
- type: 'a',
- text: i18n.ts.download,
- icon: 'ti ti-download',
- href: URL.createObjectURL(new Blob([JSON.stringify(profiles.value[profileId], null, 2)], { type: 'application/json' })),
- download: `${profiles.value[profileId].name}.json`,
- }, { type: 'divider' }, {
- text: i18n.ts.rename,
- icon: 'ti ti-forms',
- action: () => rename(profileId),
- }, {
- text: i18n.ts._preferencesBackups.save,
- icon: 'ti ti-device-floppy',
- action: () => save(profileId),
- }, { type: 'divider' }, {
- text: i18n.ts.delete,
- icon: 'ti ti-trash',
- action: () => deleteProfile(profileId),
- danger: true,
- }], (ev.currentTarget ?? ev.target ?? undefined) as unknown as HTMLElement | undefined);
-}
-
-onMounted(() => {
- // streamingのuser storage updateイベントを監視して更新
- connection?.on('registryUpdated', ({ scope: recievedScope, key, value }) => {
- if (!recievedScope || recievedScope.length !== scope.length || recievedScope[0] !== scope[0]) return;
- if (!profiles.value) return;
-
- profiles.value[key] = value;
- });
-});
-
-onUnmounted(() => {
- connection?.off('registryUpdated');
-});
-
-definePageMetadata(() => ({
- title: i18n.ts.preferencesBackups,
- icon: 'ti ti-device-floppy',
-}));
-</script>
-
-<style lang="scss" module>
-.buttons {
- display: flex;
- gap: var(--MI-margin);
- flex-wrap: wrap;
-}
-
-.profile {
- padding: 20px;
- cursor: pointer;
-
- &Name {
- font-weight: 700;
- }
-
- &Time {
- font-size: .85em;
- opacity: .7;
- }
-}
-</style>
diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue
new file mode 100644
index 0000000000..9256a565c4
--- /dev/null
+++ b/packages/frontend/src/pages/settings/preferences.vue
@@ -0,0 +1,756 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<SearchMarker path="/settings/preferences" :label="i18n.ts.preferences" :keywords="['general', 'preferences']" icon="ti ti-adjustments">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/gear_3d.png" color="#00ff9d">
+ <SearchKeyword>{{ i18n.ts._settings.preferencesBanner }}</SearchKeyword>
+ </MkFeatureBanner>
+
+ <div class="_gaps_s">
+ <SearchMarker :keywords="['general']">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.general }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['language']">
+ <MkSelect v-model="lang">
+ <template #label><SearchLabel>{{ i18n.ts.uiLanguage }}</SearchLabel></template>
+ <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option>
+ <template #caption>
+ <I18n :src="i18n.ts.i18nInfo" tag="span">
+ <template #link>
+ <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
+ </template>
+ </I18n>
+ </template>
+ </MkSelect>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop']">
+ <MkRadios v-model="overridedDeviceKind">
+ <template #label><SearchLabel>{{ i18n.ts.overridedDeviceKind }}</SearchLabel></template>
+ <option :value="null">{{ i18n.ts.auto }}</option>
+ <option value="smartphone"><i class="ti ti-device-mobile"/> {{ i18n.ts.smartphone }}</option>
+ <option value="tablet"><i class="ti ti-device-tablet"/> {{ i18n.ts.tablet }}</option>
+ <option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option>
+ </MkRadios>
+ </SearchMarker>
+
+ <div class="_gaps_s">
+ <SearchMarker :keywords="['blur']">
+ <MkPreferenceContainer k="useBlurEffect">
+ <MkSwitch v-model="useBlurEffect">
+ <template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['blur', 'modal']">
+ <MkPreferenceContainer k="useBlurEffectForModal">
+ <MkSwitch v-model="useBlurEffectForModal">
+ <template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']">
+ <MkPreferenceContainer k="showAvatarDecorations">
+ <MkSwitch v-model="showAvatarDecorations">
+ <template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['follow', 'confirm', 'always']">
+ <MkPreferenceContainer k="alwaysConfirmFollow">
+ <MkSwitch v-model="alwaysConfirmFollow">
+ <template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail']">
+ <MkPreferenceContainer k="highlightSensitiveMedia">
+ <MkSwitch v-model="highlightSensitiveMedia">
+ <template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm']">
+ <MkPreferenceContainer k="confirmWhenRevealingSensitiveMedia">
+ <MkSwitch v-model="confirmWhenRevealingSensitiveMedia">
+ <template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced']">
+ <MkPreferenceContainer k="advancedMfm">
+ <MkSwitch v-model="advancedMfm">
+ <template #label><SearchLabel>{{ i18n.ts.enableAdvancedMfm }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['auto', 'load', 'auto', 'more', 'scroll']">
+ <MkPreferenceContainer k="enableInfiniteScroll">
+ <MkSwitch v-model="enableInfiniteScroll">
+ <template #label><SearchLabel>{{ i18n.ts.enableInfiniteScroll }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+
+ <SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']">
+ <MkPreferenceContainer k="emojiStyle">
+ <div>
+ <MkRadios v-model="emojiStyle">
+ <template #label><SearchLabel>{{ i18n.ts.emojiStyle }}</SearchLabel></template>
+ <option value="native">{{ i18n.ts.native }}</option>
+ <option value="fluentEmoji">Fluent Emoji</option>
+ <option value="twemoji">Twemoji</option>
+ </MkRadios>
+ <div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div>
+ </div>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['pinned', 'list']">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template>
+ <!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
+ <MkButton v-if="prefer.r.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton>
+ <MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
+ </MkFolder>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['timeline', 'note']">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts._settings.timelineAndNote }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <div class="_gaps_s">
+ <SearchMarker :keywords="['post', 'form', 'timeline']">
+ <MkPreferenceContainer k="showFixedPostForm">
+ <MkSwitch v-model="showFixedPostForm">
+ <template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['post', 'form', 'timeline', 'channel']">
+ <MkPreferenceContainer k="showFixedPostFormInChannel">
+ <MkSwitch v-model="showFixedPostFormInChannel">
+ <template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['renote']">
+ <MkPreferenceContainer k="collapseRenotes">
+ <MkSwitch v-model="collapseRenotes">
+ <template #label><SearchLabel>{{ i18n.ts.collapseRenotes }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.collapseRenotesDescription }}</SearchKeyword></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['note', 'timeline', 'gap']">
+ <MkPreferenceContainer k="showGapBetweenNotesInTimeline">
+ <MkSwitch v-model="showGapBetweenNotesInTimeline">
+ <template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['disable', 'streaming', 'timeline']">
+ <MkPreferenceContainer k="disableStreamingTimeline">
+ <MkSwitch v-model="disableStreamingTimeline">
+ <template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+
+ <hr>
+
+ <div class="_gaps_m">
+ <div class="_gaps_s">
+ <SearchMarker :keywords="['hover', 'show', 'footer', 'action']">
+ <MkPreferenceContainer k="showNoteActionsOnlyHover">
+ <MkSwitch v-model="showNoteActionsOnlyHover">
+ <template #label><SearchLabel>{{ i18n.ts.showNoteActionsOnlyHover }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['footer', 'action', 'clip', 'show']">
+ <MkPreferenceContainer k="showClipButtonInNoteFooter">
+ <MkSwitch v-model="showClipButtonInNoteFooter">
+ <template #label><SearchLabel>{{ i18n.ts.showClipButtonInNoteFooter }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['reaction', 'count', 'show']">
+ <MkPreferenceContainer k="showReactionsCount">
+ <MkSwitch v-model="showReactionsCount">
+ <template #label><SearchLabel>{{ i18n.ts.showReactionsCount }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['reaction', 'confirm']">
+ <MkPreferenceContainer k="confirmOnReact">
+ <MkSwitch v-model="confirmOnReact">
+ <template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment']">
+ <MkPreferenceContainer k="loadRawImages">
+ <MkSwitch v-model="loadRawImages">
+ <template #label><SearchLabel>{{ i18n.ts.loadRawImages }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['reaction', 'picker', 'contextmenu', 'open']">
+ <MkPreferenceContainer k="useReactionPickerForContextMenu">
+ <MkSwitch v-model="useReactionPickerForContextMenu">
+ <template #label><SearchLabel>{{ i18n.ts.useReactionPickerForContextMenu }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+
+ <SearchMarker :keywords="['reaction', 'size', 'scale', 'display']">
+ <MkPreferenceContainer k="reactionsDisplaySize">
+ <MkRadios v-model="reactionsDisplaySize">
+ <template #label><SearchLabel>{{ i18n.ts.reactionsDisplaySize }}</SearchLabel></template>
+ <option value="small">{{ i18n.ts.small }}</option>
+ <option value="medium">{{ i18n.ts.medium }}</option>
+ <option value="large">{{ i18n.ts.large }}</option>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['reaction', 'size', 'scale', 'display', 'width', 'limit']">
+ <MkPreferenceContainer k="limitWidthOfReaction">
+ <MkSwitch v-model="limitWidthOfReaction">
+ <template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height']">
+ <MkPreferenceContainer k="mediaListWithOneImageAppearance">
+ <MkRadios v-model="mediaListWithOneImageAppearance">
+ <template #label><SearchLabel>{{ i18n.ts.mediaListWithOneImageAppearance }}</SearchLabel></template>
+ <option value="expand">{{ i18n.ts.default }}</option>
+ <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option>
+ <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option>
+ <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation']">
+ <MkPreferenceContainer k="instanceTicker">
+ <MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker">
+ <template #label><SearchLabel>{{ i18n.ts.instanceTicker }}</SearchLabel></template>
+ <option value="none">{{ i18n.ts._instanceTicker.none }}</option>
+ <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
+ <option value="always">{{ i18n.ts._instanceTicker.always }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility']">
+ <MkPreferenceContainer k="nsfw">
+ <MkSelect v-model="nsfw">
+ <template #label><SearchLabel>{{ i18n.ts.displayOfSensitiveMedia }}</SearchLabel></template>
+ <option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option>
+ <option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option>
+ <option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['post', 'form']">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.postForm }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <div class="_gaps_s">
+ <SearchMarker :keywords="['remember', 'keep', 'note', 'cw']">
+ <MkPreferenceContainer k="keepCw">
+ <MkSwitch v-model="keepCw">
+ <template #label><SearchLabel>{{ i18n.ts.keepCw }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['remember', 'keep', 'note', 'visibility']">
+ <MkPreferenceContainer k="rememberNoteVisibility">
+ <MkSwitch v-model="rememberNoteVisibility">
+ <template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn']">
+ <MkPreferenceContainer k="enableQuickAddMfmFunction">
+ <MkSwitch v-model="enableQuickAddMfmFunction">
+ <template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+
+ <SearchMarker :keywords="['default', 'note', 'visibility']">
+ <MkDisableSection :disabled="rememberNoteVisibility">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.defaultNoteVisibility }}</SearchLabel></template>
+ <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
+ <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template>
+ <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template>
+ <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template>
+
+ <div class="_gaps_m">
+ <MkPreferenceContainer k="defaultNoteVisibility">
+ <MkSelect v-model="defaultNoteVisibility">
+ <option value="public">{{ i18n.ts._visibility.public }}</option>
+ <option value="home">{{ i18n.ts._visibility.home }}</option>
+ <option value="followers">{{ i18n.ts._visibility.followers }}</option>
+ <option value="specified">{{ i18n.ts._visibility.specified }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+
+ <MkPreferenceContainer k="defaultNoteLocalOnly">
+ <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
+ </MkPreferenceContainer>
+ </div>
+ </MkFolder>
+ </MkDisableSection>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['notification']">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.notifications }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['group']">
+ <MkPreferenceContainer k="useGroupedNotifications">
+ <MkSwitch v-model="useGroupedNotifications">
+ <template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['position']">
+ <MkPreferenceContainer k="notificationPosition">
+ <MkRadios v-model="notificationPosition">
+ <template #label><SearchLabel>{{ i18n.ts.position }}</SearchLabel></template>
+ <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option>
+ <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option>
+ <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option>
+ <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['stack', 'axis', 'direction']">
+ <MkPreferenceContainer k="notificationStackAxis">
+ <MkRadios v-model="notificationStackAxis">
+ <template #label><SearchLabel>{{ i18n.ts.stackAxis }}</SearchLabel></template>
+ <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option>
+ <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['datasaver']">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>
+
+ <div class="_buttons">
+ <MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
+ <MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
+ </div>
+ <div class="_gaps_m">
+ <MkSwitch v-model="dataSaver.media">
+ {{ i18n.ts._dataSaver._media.title }}
+ <template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
+ </MkSwitch>
+ <MkSwitch v-model="dataSaver.avatar">
+ {{ i18n.ts._dataSaver._avatar.title }}
+ <template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
+ </MkSwitch>
+ <MkSwitch v-model="dataSaver.urlPreview">
+ {{ i18n.ts._dataSaver._urlPreview.title }}
+ <template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
+ </MkSwitch>
+ <MkSwitch v-model="dataSaver.code">
+ {{ i18n.ts._dataSaver._code.title }}
+ <template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
+ </MkSwitch>
+ </div>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['other']">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <div class="_gaps_s">
+ <SearchMarker :keywords="['avatar', 'icon', 'square']">
+ <MkPreferenceContainer k="squareAvatars">
+ <MkSwitch v-model="squareAvatars">
+ <template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['effect', 'show']">
+ <MkPreferenceContainer k="enableSeasonalScreenEffect">
+ <MkSwitch v-model="enableSeasonalScreenEffect">
+ <template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab']">
+ <MkPreferenceContainer k="imageNewTab">
+ <MkSwitch v-model="imageNewTab">
+ <template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['follow', 'replies']">
+ <MkPreferenceContainer k="defaultFollowWithReplies">
+ <MkSwitch v-model="defaultFollowWithReplies">
+ <template #label><SearchLabel>{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+ </div>
+
+ <SearchMarker :keywords="['server', 'disconnect', 'reconnect', 'reload', 'streaming']">
+ <MkPreferenceContainer k="serverDisconnectedBehavior">
+ <MkSelect v-model="serverDisconnectedBehavior">
+ <template #label><SearchLabel>{{ i18n.ts.whenServerDisconnected }}</SearchLabel></template>
+ <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option>
+ <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
+ <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
+ </MkSelect>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['cache', 'page']">
+ <MkPreferenceContainer k="numberOfPageCache">
+ <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing>
+ <template #label><SearchLabel>{{ i18n.ts.numberOfPageCache }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
+ </MkRange>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['ad', 'show']">
+ <MkPreferenceContainer k="forceShowAds">
+ <MkSwitch v-model="forceShowAds">
+ <template #label><SearchLabel>{{ i18n.ts.forceShowAds }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker>
+ <MkPreferenceContainer k="hemisphere">
+ <MkRadios v-model="hemisphere">
+ <template #label><SearchLabel>{{ i18n.ts.hemisphere }}</SearchLabel></template>
+ <option value="N">{{ i18n.ts._hemisphere.N }}</option>
+ <option value="S">{{ i18n.ts._hemisphere.S }}</option>
+ <template #caption>{{ i18n.ts._hemisphere.caption }}</template>
+ </MkRadios>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['emoji', 'dictionary', 'additional', 'extra']">
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.additionalEmojiDictionary }}</SearchLabel></template>
+ <div class="_buttons">
+ <template v-for="lang in emojiIndexLangs" :key="lang">
+ <MkButton v-if="store.r.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton>
+ <MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ store.r.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
+ </template>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+ </div>
+
+ <hr>
+
+ <div class="_gaps_s">
+ <FormLink to="/settings/navbar"><template #icon><i class="ti ti-list"></i></template>{{ i18n.ts.navbar }}</FormLink>
+ <FormLink to="/settings/statusbar"><template #icon><i class="ti ti-list"></i></template>{{ i18n.ts.statusbar }}</FormLink>
+ <FormLink to="/settings/deck"><template #icon><i class="ti ti-columns"></i></template>{{ i18n.ts.deck }}</FormLink>
+ <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
+ </div>
+ </div>
+</SearchMarker>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from 'vue';
+import { langs } from '@@/js/config.js';
+import * as Misskey from 'misskey-js';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkSelect from '@/components/MkSelect.vue';
+import MkRadios from '@/components/MkRadios.vue';
+import MkRange from '@/components/MkRange.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkButton from '@/components/MkButton.vue';
+import FormSection from '@/components/form/section.vue';
+import FormLink from '@/components/form/link.vue';
+import MkLink from '@/components/MkLink.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import { store } from '@/store.js';
+import * as os from '@/os.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { reloadAsk } from '@/utility/reload-ask.js';
+import { i18n } from '@/i18n.js';
+import { definePage } from '@/page.js';
+import { miLocalStorage } from '@/local-storage.js';
+import { prefer } from '@/preferences.js';
+import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
+import { globalEvents } from '@/events.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { instance } from '@/instance.js';
+
+const lang = ref(miLocalStorage.getItem('lang'));
+const dataSaver = ref(prefer.s.dataSaver);
+
+const overridedDeviceKind = prefer.model('overridedDeviceKind');
+const keepCw = prefer.model('keepCw');
+const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior');
+const hemisphere = prefer.model('hemisphere');
+const showNoteActionsOnlyHover = prefer.model('showNoteActionsOnlyHover');
+const showClipButtonInNoteFooter = prefer.model('showClipButtonInNoteFooter');
+const collapseRenotes = prefer.model('collapseRenotes');
+const advancedMfm = prefer.model('advancedMfm');
+const showReactionsCount = prefer.model('showReactionsCount');
+const enableQuickAddMfmFunction = prefer.model('enableQuickAddMfmFunction');
+const forceShowAds = prefer.model('forceShowAds');
+const loadRawImages = prefer.model('loadRawImages');
+const imageNewTab = prefer.model('imageNewTab');
+const showFixedPostForm = prefer.model('showFixedPostForm');
+const showFixedPostFormInChannel = prefer.model('showFixedPostFormInChannel');
+const numberOfPageCache = prefer.model('numberOfPageCache');
+const enableInfiniteScroll = prefer.model('enableInfiniteScroll');
+const useReactionPickerForContextMenu = prefer.model('useReactionPickerForContextMenu');
+const disableStreamingTimeline = prefer.model('disableStreamingTimeline');
+const useGroupedNotifications = prefer.model('useGroupedNotifications');
+const alwaysConfirmFollow = prefer.model('alwaysConfirmFollow');
+const confirmWhenRevealingSensitiveMedia = prefer.model('confirmWhenRevealingSensitiveMedia');
+const confirmOnReact = prefer.model('confirmOnReact');
+const defaultNoteVisibility = prefer.model('defaultNoteVisibility');
+const defaultNoteLocalOnly = prefer.model('defaultNoteLocalOnly');
+const rememberNoteVisibility = prefer.model('rememberNoteVisibility');
+const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline');
+const notificationPosition = prefer.model('notificationPosition');
+const notificationStackAxis = prefer.model('notificationStackAxis');
+const instanceTicker = prefer.model('instanceTicker');
+const highlightSensitiveMedia = prefer.model('highlightSensitiveMedia');
+const mediaListWithOneImageAppearance = prefer.model('mediaListWithOneImageAppearance');
+const reactionsDisplaySize = prefer.model('reactionsDisplaySize');
+const limitWidthOfReaction = prefer.model('limitWidthOfReaction');
+const squareAvatars = prefer.model('squareAvatars');
+const enableSeasonalScreenEffect = prefer.model('enableSeasonalScreenEffect');
+const showAvatarDecorations = prefer.model('showAvatarDecorations');
+const nsfw = prefer.model('nsfw');
+const emojiStyle = prefer.model('emojiStyle');
+const useBlurEffectForModal = prefer.model('useBlurEffectForModal');
+const useBlurEffect = prefer.model('useBlurEffect');
+const defaultFollowWithReplies = prefer.model('defaultFollowWithReplies');
+
+watch(lang, () => {
+ miLocalStorage.setItem('lang', lang.value as string);
+ miLocalStorage.removeItem('locale');
+ miLocalStorage.removeItem('localeVersion');
+});
+
+watch([
+ hemisphere,
+ lang,
+ enableInfiniteScroll,
+ showNoteActionsOnlyHover,
+ overridedDeviceKind,
+ disableStreamingTimeline,
+ alwaysConfirmFollow,
+ confirmWhenRevealingSensitiveMedia,
+ showGapBetweenNotesInTimeline,
+ mediaListWithOneImageAppearance,
+ reactionsDisplaySize,
+ limitWidthOfReaction,
+ mediaListWithOneImageAppearance,
+ reactionsDisplaySize,
+ limitWidthOfReaction,
+ instanceTicker,
+ squareAvatars,
+ highlightSensitiveMedia,
+ enableSeasonalScreenEffect,
+], async () => {
+ await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
+});
+
+const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
+
+function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) {
+ if (langs.find(x => x[0] === targetLang)) {
+ return langs.find(x => x[0] === targetLang)![1];
+ } else {
+ // 絵文字辞書限定の言語定義
+ switch (targetLang) {
+ case 'ja-JP_hira': return 'ひらがな';
+ default: return targetLang;
+ }
+ }
+}
+
+function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
+ async function main() {
+ const currentIndexes = store.s.additionalUnicodeEmojiIndexes;
+
+ function download() {
+ switch (lang) {
+ case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default);
+ case 'ja-JP': return import('../../unicode-emoji-indexes/ja-JP.json').then(x => x.default);
+ case 'ja-JP_hira': return import('../../unicode-emoji-indexes/ja-JP_hira.json').then(x => x.default);
+ default: throw new Error('unrecognized lang: ' + lang);
+ }
+ }
+
+ currentIndexes[lang] = await download();
+ await store.set('additionalUnicodeEmojiIndexes', currentIndexes);
+ }
+
+ os.promiseDialog(main());
+}
+
+function removeEmojiIndex(lang: string) {
+ async function main() {
+ const currentIndexes = store.s.additionalUnicodeEmojiIndexes;
+ delete currentIndexes[lang];
+ await store.set('additionalUnicodeEmojiIndexes', currentIndexes);
+ }
+
+ os.promiseDialog(main());
+}
+
+async function setPinnedList() {
+ const lists = await misskeyApi('users/lists/list');
+ const { canceled, result: list } = await os.select({
+ title: i18n.ts.selectList,
+ items: lists.map(x => ({
+ value: x, text: x.name,
+ })),
+ });
+ if (canceled) return;
+ if (list == null) return;
+
+ prefer.commit('pinnedUserLists', [list]);
+}
+
+function removePinnedList() {
+ prefer.commit('pinnedUserLists', []);
+}
+
+function enableAllDataSaver() {
+ const g = { ...prefer.s.dataSaver };
+
+ Object.keys(g).forEach((key) => { g[key] = true; });
+
+ dataSaver.value = g;
+}
+
+function disableAllDataSaver() {
+ const g = { ...prefer.s.dataSaver };
+
+ Object.keys(g).forEach((key) => { g[key] = false; });
+
+ dataSaver.value = g;
+}
+
+watch(dataSaver, (to) => {
+ prefer.commit('dataSaver', to);
+}, {
+ deep: true,
+});
+
+let smashCount = 0;
+let smashTimer: number | null = null;
+
+function testNotification(): void {
+ const notification: Misskey.entities.Notification = {
+ id: Math.random().toString(),
+ createdAt: new Date().toUTCString(),
+ isRead: false,
+ type: 'test',
+ };
+
+ globalEvents.emit('clientNotification', notification);
+
+ // セルフ通知破壊 実績関連
+ smashCount++;
+ if (smashCount >= 10) {
+ claimAchievement('smashTestNotificationButton');
+ smashCount = 0;
+ }
+ if (smashTimer) {
+ clearTimeout(smashTimer);
+ }
+ smashTimer = window.setTimeout(() => {
+ smashCount = 0;
+ }, 300);
+}
+
+const headerActions = computed(() => []);
+
+const headerTabs = computed(() => []);
+
+definePage(() => ({
+ title: i18n.ts.general,
+ icon: 'ti ti-adjustments',
+}));
+</script>
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index bcedb8b139..f672d9d44f 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -4,190 +4,263 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <MkSwitch v-model="isLocked" @update:modelValue="save()">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch>
- <MkSwitch v-if="isLocked" v-model="autoAcceptFollowed" @update:modelValue="save()">{{ i18n.ts.autoAcceptFollowed }}</MkSwitch>
+<SearchMarker path="/settings/privacy" :label="i18n.ts.privacy" :keywords="['privacy']" icon="ti ti-lock-open">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/unlocked_3d.png" color="#aeff00">
+ <SearchKeyword>{{ i18n.ts._settings.privacyBanner }}</SearchKeyword>
+ </MkFeatureBanner>
- <MkSwitch v-model="publicReactions" @update:modelValue="save()">
- {{ i18n.ts.makeReactionsPublic }}
- <template #caption>{{ i18n.ts.makeReactionsPublicDescription }}</template>
- </MkSwitch>
+ <SearchMarker :keywords="['follow', 'lock']">
+ <MkSwitch v-model="isLocked" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.makeFollowManuallyApprove }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.lockedAccountInfo }}</SearchKeyword></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSelect v-model="followingVisibility" @update:modelValue="save()">
- <template #label>{{ i18n.ts.followingVisibility }}</template>
- <option value="public">{{ i18n.ts._ffVisibility.public }}</option>
- <option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
- <option value="private">{{ i18n.ts._ffVisibility.private }}</option>
- </MkSelect>
+ <MkDisableSection :disabled="!isLocked">
+ <SearchMarker :keywords="['follow', 'auto', 'accept']">
+ <MkSwitch v-model="autoAcceptFollowed" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.autoAcceptFollowed }}</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
+ </MkDisableSection>
- <MkSelect v-model="followersVisibility" @update:modelValue="save()">
- <template #label>{{ i18n.ts.followersVisibility }}</template>
- <option value="public">{{ i18n.ts._ffVisibility.public }}</option>
- <option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
- <option value="private">{{ i18n.ts._ffVisibility.private }}</option>
- </MkSelect>
+ <SearchMarker :keywords="['reaction', 'public']">
+ <MkSwitch v-model="publicReactions" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.makeReactionsPublic }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.makeReactionsPublicDescription }}</SearchKeyword></template>
+ </MkSwitch>
+ </SearchMarker>
- <MkSwitch v-model="hideOnlineStatus" @update:modelValue="save()">
- {{ i18n.ts.hideOnlineStatus }}
- <template #caption>{{ i18n.ts.hideOnlineStatusDescription }}</template>
- </MkSwitch>
- <MkSwitch v-model="noCrawle" @update:modelValue="save()">
- {{ i18n.ts.noCrawle }}
- <template #caption>{{ i18n.ts.noCrawleDescription }}</template>
- </MkSwitch>
- <MkSwitch v-model="noindex" @update:modelValue="save()">
- {{ i18n.ts.makeIndexable }}
- <template #caption>{{ i18n.ts.makeIndexableDescription }}</template>
- </MkSwitch>
- <MkSwitch v-model="isExplorable" @update:modelValue="save()">
- {{ i18n.ts.makeExplorable }}
- <template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
- </MkSwitch>
- <MkSwitch v-model="enableRss" @update:modelValue="save()">
- {{ i18n.ts.enableRss }}
- <template #caption>{{ i18n.ts.enableRssDescription }}</template>
- </MkSwitch>
+ <SearchMarker :keywords="['following', 'visibility']">
+ <MkSelect v-model="followingVisibility" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.followingVisibility }}</SearchLabel></template>
+ <option value="public">{{ i18n.ts._ffVisibility.public }}</option>
+ <option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
+ <option value="private">{{ i18n.ts._ffVisibility.private }}</option>
+ </MkSelect>
+ </SearchMarker>
- <FormSection>
- <template #label>{{ i18n.ts.lockdown }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
+ <SearchMarker :keywords="['follower', 'visibility']">
+ <MkSelect v-model="followersVisibility" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.followersVisibility }}</SearchLabel></template>
+ <option value="public">{{ i18n.ts._ffVisibility.public }}</option>
+ <option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
+ <option value="private">{{ i18n.ts._ffVisibility.private }}</option>
+ </MkSelect>
+ </SearchMarker>
- <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 v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
- </template>
+ <SearchMarker :keywords="['online', 'status']">
+ <MkSwitch v-model="hideOnlineStatus" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.hideOnlineStatus }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.hideOnlineStatusDescription }}</SearchKeyword></template>
</MkSwitch>
+ </SearchMarker>
- <FormSlot>
- <template #label>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBefore }}</template>
+ <SearchMarker :keywords="['crawle', 'index', 'search']">
+ <MkSwitch v-model="noCrawle" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.noCrawle }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.noCrawleDescription }}</SearchKeyword></template>
+ </MkSwitch>
+ </SearchMarker>
- <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>
+ <SearchMarker :keywords="['index', 'search']">
+ <MkSwitch v-model="noindex" @update:modelValue="save()">
+ {{ i18n.ts.makeIndexable }}
+ <template #caption>{{ i18n.ts.makeIndexableDescription }}</template>
+ </MkSwitch>
+ </SearchMarker>
- <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>
+ <SearchMarker :keywords="['explore']">
+ <MkSwitch v-model="isExplorable" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.makeExplorable }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.makeExplorableDescription }}</SearchKeyword></template>
+ </MkSwitch>
+ </SearchMarker>
- <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>
+ <SearchMarker :keywords="['rss', 'feed']">
+ <MkSwitch v-model="enableRss" @update:modelValue="save()">
+ {{ i18n.ts.enableRss }}
+ <template #caption>{{ i18n.ts.enableRssDescription }}</template>
+ </MkSwitch>
+ </SearchMarker>
- <template #caption>
- <div>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</div>
- <div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
- </template>
- </FormSlot>
+ <SearchMarker :keywords="['crawle', 'ai']">
+ <MkSwitch v-model="preventAiLearning" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts.preventAiLearning }}</SearchLabel></template>
+ <template #caption><SearchKeyword>{{ i18n.ts.preventAiLearningDescription }}</SearchKeyword></template>
+ </MkSwitch>
+ </SearchMarker>
- <FormSlot>
- <template #label>{{ i18n.ts._accountSettings.makeNotesHiddenBefore }}</template>
+ <FormSection>
+ <SearchMarker :keywords="['chat']">
+ <MkSelect v-model="chatScope" @update:modelValue="save()">
+ <template #label><SearchLabel>{{ i18n.ts._chat.chatAllowedUsers }}</SearchLabel></template>
+ <option value="everyone">{{ i18n.ts._chat._chatAllowedUsers.everyone }}</option>
+ <option value="followers">{{ i18n.ts._chat._chatAllowedUsers.followers }}</option>
+ <option value="following">{{ i18n.ts._chat._chatAllowedUsers.following }}</option>
+ <option value="mutual">{{ i18n.ts._chat._chatAllowedUsers.mutual }}</option>
+ <option value="none">{{ i18n.ts._chat._chatAllowedUsers.none }}</option>
+ <template #caption>{{ i18n.ts._chat.chatAllowedUsers_note }}</template>
+ </MkSelect>
+ </SearchMarker>
+ </FormSection>
- <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>
+ <SearchMarker :keywords="['lockdown']">
+ <FormSection>
+ <template #label><SearchLabel>{{ i18n.ts.lockdown }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
- <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>
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['login', 'signin']">
+ <MkSwitch :modelValue="requireSigninToViewContents" @update:modelValue="update_requireSigninToViewContents">
+ <template #label><SearchLabel>{{ i18n.ts._accountSettings.requireSigninToViewContents }}</SearchLabel></template>
+ <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>
+ </template>
+ </MkSwitch>
+ </SearchMarker>
- <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>
+ <SearchMarker :keywords="['follower']">
+ <FormSlot>
+ <template #label><SearchLabel>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBefore }}</SearchLabel></template>
- <template #caption>
- <div>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</div>
- <div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
- </template>
- </FormSlot>
+ <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>
- <MkFolder v-if="instance.federation !== 'none'">
- <template #label>{{ i18n.ts.authorizedFetchSection }}</template>
- <template #suffix>{{ computedAllowUnsignedFetch !== 'always' ? i18n.ts.enabled : i18n.ts.disabled }}</template>
+ <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>
- <MkRadios v-model="allowUnsignedFetch" @update:modelValue="save()">
- <template #label>{{ i18n.ts.authorizedFetchLabel }}</template>
- <template #caption>{{ i18n.ts.authorizedFetchDescription }}</template>
- <option value="never">{{ i18n.ts._authorizedFetchValue.never }} - {{ i18n.ts._authorizedFetchValueDescription.never }}</option>
- <option value="always">{{ i18n.ts._authorizedFetchValue.always }} - {{ i18n.ts._authorizedFetchValueDescription.always }}</option>
- <option value="essential">{{ i18n.ts._authorizedFetchValue.essential }} - {{ i18n.ts._authorizedFetchValueDescription.essential }}</option>
- <option value="staff">{{ i18n.ts._authorizedFetchValue.staff }} - {{ i18n.tsx._authorizedFetchValueDescription.staff({ value: i18n.ts._authorizedFetchValue[instance.allowUnsignedFetch] }) }}</option>
- </MkRadios>
- </MkFolder>
- </div>
- </FormSection>
+ <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>
- <FormSection>
- <div class="_gaps_m">
- <MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
- <MkFolder v-if="!rememberNoteVisibility">
- <template #label>{{ i18n.ts.defaultNoteVisibility }}</template>
- <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
- <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template>
- <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template>
- <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template>
+ <template #caption>
+ <div><SearchKeyword>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</SearchKeyword></div>
+ </template>
+ </FormSlot>
+ </SearchMarker>
- <div class="_gaps_m">
- <MkSelect v-model="defaultNoteVisibility">
- <option value="public">{{ i18n.ts._visibility.public }}</option>
- <option value="home">{{ i18n.ts._visibility.home }}</option>
- <option value="followers">{{ i18n.ts._visibility.followers }}</option>
- <option value="specified">{{ i18n.ts._visibility.specified }}</option>
- </MkSelect>
- <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
- </div>
- </MkFolder>
+ <SearchMarker :keywords="['hidden']">
+ <FormSlot>
+ <template #label><SearchLabel>{{ i18n.ts._accountSettings.makeNotesHiddenBefore }}</SearchLabel></template>
- <MkSwitch v-model="keepCw" @update:modelValue="save()">{{ i18n.ts.keepCw }}</MkSwitch>
+ <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>
- <MkInput v-model="defaultCW" type="text" manualSave @update:modelValue="save()">
- <template #label>{{ i18n.ts.defaultCW }}</template>
- <template #caption>{{ i18n.ts.defaultCWDescription }}</template>
- </MkInput>
+ <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>
- <MkSelect v-model="defaultCWPriority" :disabled="!defaultCW || !keepCw" @update:modelValue="save()">
- <template #label>{{ i18n.ts.defaultCWPriority }}</template>
- <template #caption>{{ i18n.ts.defaultCWPriorityDescription }}</template>
- <option value="default">{{ i18n.ts._defaultCWPriority.default }}</option>
- <option value="parent">{{ i18n.ts._defaultCWPriority.parent }}</option>
- <option value="parentDefault">{{ i18n.ts._defaultCWPriority.parentDefault }}</option>
- <option value="defaultParent">{{ i18n.ts._defaultCWPriority.defaultParent }}</option>
- </MkSelect>
- </div>
- </FormSection>
-</div>
+ <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><SearchKeyword>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</SearchKeyword></div>
+ <div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
+ </template>
+ </FormSlot>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['federate', 'auth', 'fetch']">
+ <MkFolder v-if="instance.federation !== 'none'">
+ <template #label>{{ i18n.ts.authorizedFetchSection }}</template>
+ <template #suffix>{{ computedAllowUnsignedFetch !== 'always' ? i18n.ts.enabled : i18n.ts.disabled }}</template>
+
+ <MkRadios v-model="allowUnsignedFetch" @update:modelValue="save()">
+ <template #label>{{ i18n.ts.authorizedFetchLabel }}</template>
+ <template #caption>{{ i18n.ts.authorizedFetchDescription }}</template>
+ <option value="never">{{ i18n.ts._authorizedFetchValue.never }} - {{ i18n.ts._authorizedFetchValueDescription.never }}</option>
+ <option value="always">{{ i18n.ts._authorizedFetchValue.always }} - {{ i18n.ts._authorizedFetchValueDescription.always }}</option>
+ <option value="essential">{{ i18n.ts._authorizedFetchValue.essential }} - {{ i18n.ts._authorizedFetchValueDescription.essential }}</option>
+ <option value="staff">{{ i18n.ts._authorizedFetchValue.staff }} - {{ i18n.tsx._authorizedFetchValueDescription.staff({ value: i18n.ts._authorizedFetchValue[instance.allowUnsignedFetch] }) }}</option>
+ </MkRadios>
+ </MkFolder>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['note', 'visib']">
+ <div class="_gaps_m">
+ <MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
+ <MkFolder v-if="!rememberNoteVisibility">
+ <template #label>{{ i18n.ts.defaultNoteVisibility }}</template>
+ <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
+ <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template>
+ <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template>
+ <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template>
+
+ <div class="_gaps_m">
+ <MkSelect v-model="defaultNoteVisibility">
+ <option value="public">{{ i18n.ts._visibility.public }}</option>
+ <option value="home">{{ i18n.ts._visibility.home }}</option>
+ <option value="followers">{{ i18n.ts._visibility.followers }}</option>
+ <option value="specified">{{ i18n.ts._visibility.specified }}</option>
+ </MkSelect>
+ <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch>
+ </div>
+ </MkFolder>
+ </div>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['keep', 'cw', 'content', 'warning']">
+ <div class="_gaps_m">
+ <MkSwitch v-model="keepCw" @update:modelValue="save()">{{ i18n.ts.keepCw }}</MkSwitch>
+
+ <MkInput v-model="defaultCW" type="text" manualSave @update:modelValue="save()">
+ <template #label>{{ i18n.ts.defaultCW }}</template>
+ <template #caption>{{ i18n.ts.defaultCWDescription }}</template>
+ </MkInput>
+
+ <MkSelect v-model="defaultCWPriority" :disabled="!defaultCW || !keepCw" @update:modelValue="save()">
+ <template #label>{{ i18n.ts.defaultCWPriority }}</template>
+ <template #caption>{{ i18n.ts.defaultCWPriorityDescription }}</template>
+ <option value="default">{{ i18n.ts._defaultCWPriority.default }}</option>
+ <option value="parent">{{ i18n.ts._defaultCWPriority.parent }}</option>
+ <option value="parentDefault">{{ i18n.ts._defaultCWPriority.parentDefault }}</option>
+ <option value="defaultParent">{{ i18n.ts._defaultCWPriority.defaultParent }}</option>
+ </MkSelect>
+ </div>
+ </SearchMarker>
+
+ <MkInfo warn>{{ i18n.ts._accountSettings.mayNotEffectSomeSituations }}</MkInfo>
+ </div>
+ </FormSection>
+ </SearchMarker>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -196,19 +269,21 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';
import FormSection from '@/components/form/section.vue';
import MkFolder from '@/components/MkFolder.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { signinRequired } from '@/account.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { ensureSignin } from '@/i.js';
+import { definePage } from '@/page.js';
import FormSlot from '@/components/form/slot.vue';
-import { formatDateTimeString } from '@/scripts/format-time-string.js';
+import { formatDateTimeString } from '@/utility/format-time-string.js';
import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js';
+import MkDisableSection from '@/components/MkDisableSection.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
import MkRadios from '@/components/MkRadios.vue';
-const $i = signinRequired();
+const $i = ensureSignin();
const isLocked = ref($i.isLocked);
const autoAcceptFollowed = ref($i.autoAcceptFollowed);
@@ -223,6 +298,7 @@ const hideOnlineStatus = ref($i.hideOnlineStatus);
const publicReactions = ref($i.publicReactions);
const followingVisibility = ref($i.followingVisibility);
const followersVisibility = ref($i.followersVisibility);
+const chatScope = ref($i.chatScope);
const defaultCW = ref($i.defaultCW);
const defaultCWPriority = ref($i.defaultCWPriority);
const allowUnsignedFetch = ref($i.allowUnsignedFetch);
@@ -290,6 +366,7 @@ function save() {
publicReactions: !!publicReactions.value,
followingVisibility: followingVisibility.value,
followersVisibility: followersVisibility.value,
+ chatScope: chatScope.value,
defaultCWPriority: defaultCWPriority.value,
defaultCW: defaultCW.value,
allowUnsignedFetch: allowUnsignedFetch.value,
@@ -300,7 +377,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.privacy,
icon: 'ti ti-lock-open',
}));
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 67ba833c5c..fc2743df0f 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -4,122 +4,168 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <div class="_panel">
- <div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
- <MkButton primary rounded :class="$style.bannerEdit" @click="changeOrRemoveBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
- <MkButton primary rounded :class="$style.backgroundEdit" @click="changeOrRemoveBackground">{{ i18n.ts._profile.changeBackground }}</MkButton>
- </div>
- <div :class="$style.avatarContainer">
- <MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeOrRemoveAvatar"/>
- <div class="_buttonsCenter">
- <MkButton primary rounded @click="changeOrRemoveAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
- <MkButton primary rounded link to="/settings/avatar-decoration">{{ i18n.ts.decorate }} <i class="ti ti-sparkles"></i></MkButton>
+<SearchMarker path="/settings/profile" :label="i18n.ts.profile" :keywords="['profile']" icon="ti ti-user">
+ <div class="_gaps_m">
+ <div class="_panel">
+ <div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
+ <div :class="$style.bannerEdit">
+ <SearchMarker :keywords="['banner', 'change', 'remove']">
+ <MkButton primary rounded @click="changeOrRemoveBanner">{{ <SearchLabel>{{ i18n.ts._profile.changeBanner }}</SearchLabel> }}</MkButton>
+ </SearchMarker>
+ </div>
+ <div :class="$style.backgroundEdit">
+ <SearchMarker :keywords="['background', 'change', 'remove']">
+ <MkButton primary rounded @click="changeOrRemoveBackground">{{ <SearchLabel>{{ i18n.ts._profile.changeBackground }}</SearchLabel> }}</MkButton>
+ </SearchMarker>
+ </div>
+ </div>
+ <div :class="$style.avatarContainer">
+ <MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration @click="changeOrRemoveAvatar"/>
+ <div class="_buttonsCenter">
+ <SearchMarker :keywords="['avatar', 'icon', 'change']">
+ <MkButton primary rounded @click="changeOrRemoveAvatar"><SearchLabel>{{ i18n.ts._profile.changeAvatar }}</SearchLabel></MkButton>
+ </SearchMarker>
+ <MkButton primary rounded link to="/settings/avatar-decoration">{{ i18n.ts.decorate }} <i class="ti ti-sparkles"></i></MkButton>
+ </div>
</div>
</div>
- </div>
-
- <MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
- <template #label>{{ i18n.ts._profile.name }}</template>
- </MkInput>
- <MkTextarea v-model="profile.description" :max="500" tall manualSave mfmAutocomplete :mfmPreview="true">
- <template #label>{{ i18n.ts._profile.description }}</template>
- <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
- </MkTextarea>
+ <SearchMarker :keywords="['name']">
+ <MkInput v-model="profile.name" :max="30" manualSave :mfmAutocomplete="['emoji']">
+ <template #label><SearchLabel>{{ i18n.ts._profile.name }}</SearchLabel></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="profile.location" manualSave>
- <template #label>{{ i18n.ts.location }}</template>
- <template #prefix><i class="ti ti-map-pin"></i></template>
- </MkInput>
+ <SearchMarker :keywords="['description', 'bio']">
+ <MkTextarea v-model="profile.description" :max="500" tall manualSave mfmAutocomplete :mfmPreview="true">
+ <template #label><SearchLabel>{{ i18n.ts._profile.description }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
+ </MkTextarea>
+ </SearchMarker>
- <MkInput v-model="profile.birthday" :max="setMaxBirthDate()" type="date" manualSave>
- <template #label>{{ i18n.ts.birthday }}</template>
- <template #prefix><i class="ti ti-cake"></i></template>
- </MkInput>
+ <SearchMarker :keywords="['location', 'locale']">
+ <MkInput v-model="profile.location" manualSave>
+ <template #label><SearchLabel>{{ i18n.ts.location }}</SearchLabel></template>
+ <template #prefix><i class="ti ti-map-pin"></i></template>
+ </MkInput>
+ </SearchMarker>
- <MkInput v-model="profile.listenbrainz" manualSave>
- <template #label>{{ i18n.ts._profile.listenbrainz }}</template>
- <template #prefix><i class="ph-headphones ph-bold ph-lg"></i></template>
- </MkInput>
+ <SearchMarker :keywords="['birthday', 'birthdate', 'age']">
+ <MkInput v-model="profile.birthday" type="date" manualSave>
+ <template #label><SearchLabel>{{ i18n.ts.birthday }}</SearchLabel></template>
+ <template #prefix><i class="ti ti-cake"></i></template>
+ </MkInput>
+ </SearchMarker>
- <MkSelect v-model="profile.lang">
- <template #label>{{ i18n.ts.language }}</template>
- <option v-for="x in Object.keys(langmap)" :key="x" :value="x">{{ langmap[x].nativeName }}</option>
- </MkSelect>
-
- <FormSlot>
- <MkFolder>
- <template #icon><i class="ti ti-list"></i></template>
- <template #label>{{ i18n.ts._profile.metadataEdit }}</template>
- <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>
+ <SearchMarker :keywords="['listenbrain', 'music']">
+ <MkInput v-model="profile.listenbrainz" manualSave>
+ <template #label><SearchLabel>{{ i18n.ts._profile.listenbrainz }}</SearchLabel></template>
+ <template #prefix><i class="ph-headphones ph-bold ph-lg"></i></template>
+ </MkInput>
+ </SearchMarker>
- <div :class="$style.metadataRoot" class="_gaps_s">
- <MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo>
+ <SearchMarker :keywords="['language', 'locale']">
+ <MkSelect v-model="profile.lang">
+ <template #label><SearchLabel>{{ i18n.ts.language }}</SearchLabel></template>
+ <option v-for="x in Object.keys(langmap)" :key="x" :value="x">{{ langmap[x].nativeName }}</option>
+ </MkSelect>
+ </SearchMarker>
- <Sortable
- v-model="fields"
- class="_gaps_s"
- itemKey="id"
- :animation="150"
- :handle="'.' + $style.dragItemHandle"
- @start="e => e.item.classList.add('active')"
- @end="e => e.item.classList.remove('active')"
- >
- <template #item="{element, index}">
- <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 :placeholder="i18n.ts._profile.metadataLabel">
- </MkInput>
- <MkInput v-model="element.value" small :placeholder="i18n.ts._profile.metadataContent">
- </MkInput>
- </FormSplit>
- </div>
+ <SearchMarker :keywords="['metadata']">
+ <FormSlot>
+ <MkFolder>
+ <template #icon><i class="ti ti-list"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts._profile.metadataEdit }}</SearchLabel></template>
+ <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>
- </Sortable>
- </div>
- </MkFolder>
- <template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
- </FormSlot>
- <MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false">
- <template #label>{{ i18n.ts._profile.followedMessage }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
- <template #caption>
- <div>{{ i18n.ts._profile.followedMessageDescription }}</div>
- <div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div>
- </template>
- </MkInput>
+ <div :class="$style.metadataRoot" class="_gaps_s">
+ <MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo>
- <MkSelect v-model="reactionAcceptance">
- <template #label>{{ i18n.ts.reactionAcceptance }}</template>
- <option :value="null">{{ i18n.ts.all }}</option>
- <option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
- <option value="nonSensitiveOnly">{{ i18n.ts.nonSensitiveOnly }}</option>
- <option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
- <option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
- </MkSelect>
+ <Sortable
+ v-model="fields"
+ class="_gaps_s"
+ itemKey="id"
+ :animation="150"
+ :handle="'.' + $style.dragItemHandle"
+ @start="e => e.item.classList.add('active')"
+ @end="e => e.item.classList.remove('active')"
+ >
+ <template #item="{element, index}">
+ <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 :placeholder="i18n.ts._profile.metadataLabel">
+ </MkInput>
+ <MkInput v-model="element.value" small :placeholder="i18n.ts._profile.metadataContent">
+ </MkInput>
+ </FormSplit>
+ </div>
+ </div>
+ </template>
+ </Sortable>
+ </div>
+ </MkFolder>
+ <template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
+ </FormSlot>
+ </SearchMarker>
- <MkFolder>
- <template #label>{{ i18n.ts.advancedSettings }}</template>
+ <SearchMarker :keywords="['follow', 'message']">
+ <MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false">
+ <template #label><SearchLabel>{{ i18n.ts._profile.followedMessage }}</SearchLabel><span class="_beta">{{ i18n.ts.beta }}</span></template>
+ <template #caption>
+ <div><SearchKeyword>{{ i18n.ts._profile.followedMessageDescription }}</SearchKeyword></div>
+ <div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div>
+ </template>
+ </MkInput>
+ </SearchMarker>
- <div class="_gaps_m">
- <MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch>
- <MkSwitch v-if="profile.isCat" v-model="profile.speakAsCat">{{ i18n.ts.flagSpeakAsCat }}<template #caption>{{ i18n.ts.flagSpeakAsCatDescription }}</template></MkSwitch>
- <MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
- </div>
- </MkFolder>
-</div>
+ <SearchMarker :keywords="['reaction']">
+ <MkSelect v-model="reactionAcceptance">
+ <template #label><SearchLabel>{{ i18n.ts.reactionAcceptance }}</SearchLabel></template>
+ <option :value="null">{{ i18n.ts.all }}</option>
+ <option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
+ <option value="nonSensitiveOnly">{{ i18n.ts.nonSensitiveOnly }}</option>
+ <option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
+ <option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
+ </MkSelect>
+ </SearchMarker>
+
+ <SearchMarker>
+ <MkFolder>
+ <template #label><SearchLabel>{{ i18n.ts.advancedSettings }}</SearchLabel></template>
+
+ <div class="_gaps_m">
+ <SearchMarker :keywords="['cat']">
+ <MkSwitch v-model="profile.isCat">
+ <template #label><SearchLabel>{{ i18n.ts.flagAsCat }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.flagAsCatDescription }}</template>
+ </MkSwitch>
+ <MkSwitch v-if="profile.isCat" v-model="profile.speakAsCat">
+ <template #label><SearchLabel>{{ i18n.ts.flagSpeakAsCat }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.flagSpeakAsCatDescription }}</template>
+ </MkSwitch>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['bot']">
+ <MkSwitch v-model="profile.isBot">
+ <template #label><SearchLabel>{{ i18n.ts.flagAsBot }}</SearchLabel></template>
+ <template #caption>{{ i18n.ts.flagAsBotDescription }}</template>
+ </MkSwitch>
+ </SearchMarker>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -131,23 +177,22 @@ import MkSelect from '@/components/MkSelect.vue';
import FormSplit from '@/components/form/split.vue';
import MkFolder from '@/components/MkFolder.vue';
import FormSlot from '@/components/form/slot.vue';
-import { selectFile } from '@/scripts/select-file.js';
+import { selectFile } from '@/utility/select-file.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { signinRequired } from '@/account.js';
-import { langmap } from '@/scripts/langmap.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { defaultStore } from '@/store.js';
-import { globalEvents } from '@/events.js';
+import { ensureSignin } from '@/i.js';
+import { langmap } from '@/utility/langmap.js';
+import { definePage } from '@/page.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { store } from '@/store.js';
import MkInfo from '@/components/MkInfo.vue';
import MkTextarea from '@/components/MkTextarea.vue';
-const $i = signinRequired();
+const $i = ensureSignin();
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
-const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
+const reactionAcceptance = computed(store.makeGetterSetter('reactionAcceptance'));
const now = new Date();
@@ -203,7 +248,6 @@ function saveFields() {
os.apiWithDialog('i/update', {
fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
});
- globalEvents.emit('requestClearPageCache');
}
function save() {
@@ -238,7 +282,6 @@ function save() {
text: i18n.ts.yourNameContainsProhibitedWordsDescription,
},
});
- globalEvents.emit('requestClearPageCache');
claimAchievement('profileFilled');
if (profile.name === 'syuilo' || profile.name === 'しゅいろ') {
claimAchievement('setNameToSyuilo');
@@ -270,6 +313,7 @@ function changeAvatar(ev) {
});
$i.avatarId = i.avatarId;
$i.avatarUrl = i.avatarUrl;
+ claimAchievement('profileFilled');
globalEvents.emit('requestClearPageCache');
});
}
@@ -296,7 +340,6 @@ function changeBanner(ev) {
});
$i.bannerId = i.bannerId;
$i.bannerUrl = i.bannerUrl;
- globalEvents.emit('requestClearPageCache');
});
}
@@ -396,7 +439,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.profile,
icon: 'ti ti-user',
}));
diff --git a/packages/frontend/src/pages/settings/roles.vue b/packages/frontend/src/pages/settings/roles.vue
deleted file mode 100644
index 5346a58a79..0000000000
--- a/packages/frontend/src/pages/settings/roles.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div class="_gaps_m">
- <FormSection first>
- <template #label>{{ i18n.ts.rolesAssignedToMe }}</template>
- <div class="_gaps_s">
- <MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/>
- </div>
- </FormSection>
- <FormSection>
- <template #label>{{ i18n.ts._role.policies }}</template>
- <div class="_gaps_s">
- <div v-for="policy in Object.keys($i.policies)" :key="policy">
- {{ policy }} ... {{ $i.policies[policy] }}
- </div>
- </div>
- </FormSection>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { computed } from 'vue';
-import FormSection from '@/components/form/section.vue';
-import * as os from '@/os.js';
-import { i18n } from '@/i18n.js';
-import { signinRequired } from '@/account.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import MkRolePreview from '@/components/MkRolePreview.vue';
-
-const $i = signinRequired();
-
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => []);
-
-definePageMetadata(() => ({
- title: i18n.ts.roles,
- icon: 'ti ti-badges',
-}));
-</script>
-
-<style lang="scss" module>
-
-</style>
diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue
index 8f9d4f858b..391118effd 100644
--- a/packages/frontend/src/pages/settings/security.vue
+++ b/packages/frontend/src/pages/settings/security.vue
@@ -4,39 +4,52 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <FormSection first>
- <template #label>{{ i18n.ts.password }}</template>
- <MkButton primary @click="change()">{{ i18n.ts.changePassword }}</MkButton>
- </FormSection>
+<SearchMarker path="/settings/security" :label="i18n.ts.security" :keywords="['security']" icon="ti ti-lock" :inlining="['2fa']">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/locked_with_key_3d.png" color="#ffbf00">
+ <SearchKeyword>{{ i18n.ts._settings.securityBanner }}</SearchKeyword>
+ </MkFeatureBanner>
- <X2fa/>
+ <SearchMarker :keywords="['password']">
+ <FormSection first>
+ <template #label><SearchLabel>{{ i18n.ts.password }}</SearchLabel></template>
- <FormSection>
- <template #label>{{ i18n.ts.signinHistory }}</template>
- <MkPagination :pagination="pagination" disableAutoLoad>
- <template #default="{items}">
- <div>
- <div v-for="item in items" :key="item.id" v-panel class="timnmucd">
- <header>
- <i v-if="item.success" class="ti ti-check icon succ"></i>
- <i v-else class="ti ti-circle-x icon fail"></i>
- <code class="ip _monospace">{{ item.ip }}</code>
- <MkTime :time="item.createdAt" class="time"/>
- </header>
+ <SearchMarker>
+ <MkButton primary @click="change()">
+ <SearchLabel>{{ i18n.ts.changePassword }}</SearchLabel>
+ </MkButton>
+ </SearchMarker>
+ </FormSection>
+ </SearchMarker>
+
+ <X2fa/>
+
+ <FormSection>
+ <template #label>{{ i18n.ts.signinHistory }}</template>
+ <MkPagination :pagination="pagination" disableAutoLoad>
+ <template #default="{items}">
+ <div>
+ <div v-for="item in items" :key="item.id" v-panel class="timnmucd">
+ <header>
+ <i v-if="item.success" class="ti ti-check icon succ"></i>
+ <i v-else class="ti ti-circle-x icon fail"></i>
+ <code class="ip _monospace">{{ item.ip }}</code>
+ <MkTime :time="item.createdAt" class="time"/>
+ </header>
+ </div>
</div>
- </div>
- </template>
- </MkPagination>
- </FormSection>
+ </template>
+ </MkPagination>
+ </FormSection>
- <FormSection>
- <FormSlot>
- <MkButton danger @click="regenerateToken"><i class="ti ti-refresh"></i> {{ i18n.ts.regenerateLoginToken }}</MkButton>
- <template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template>
- </FormSlot>
- </FormSection>
-</div>
+ <FormSection>
+ <FormSlot>
+ <MkButton danger @click="regenerateToken"><i class="ti ti-refresh"></i> {{ i18n.ts.regenerateLoginToken }}</MkButton>
+ <template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template>
+ </FormSlot>
+ </FormSection>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
@@ -47,9 +60,10 @@ import FormSlot from '@/components/form/slot.vue';
import MkButton from '@/components/MkButton.vue';
import MkPagination from '@/components/MkPagination.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
const pagination = {
endpoint: 'i/signin-history' as const,
@@ -103,7 +117,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.security,
icon: 'ti ti-lock',
}));
diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue
index 56f65e2309..1bac19fe47 100644
--- a/packages/frontend/src/pages/settings/sounds.sound.vue
+++ b/packages/frontend/src/pages/settings/sounds.sound.vue
@@ -32,15 +32,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
-import type { SoundType } from '@/scripts/sound.js';
+import type { SoundType } from '@/utility/sound.js';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
import MkRange from '@/components/MkRange.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/scripts/sound.js';
-import { selectFile } from '@/scripts/select-file.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js';
+import { selectFile } from '@/utility/select-file.js';
const props = defineProps<{
type: SoundType;
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index 9fcf564e55..4461ee1ab1 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -4,63 +4,88 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m">
- <MkSwitch v-model="notUseSound">
- <template #label>{{ i18n.ts.notUseSound }}</template>
- </MkSwitch>
- <MkSwitch v-model="useSoundOnlyWhenActive">
- <template #label>{{ i18n.ts.useSoundOnlyWhenActive }}</template>
- </MkSwitch>
- <MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`">
- <template #label>{{ i18n.ts.masterVolume }}</template>
- </MkRange>
+<SearchMarker path="/settings/sounds" :label="i18n.ts.sounds" :keywords="['sounds']" icon="ti ti-music">
+ <div class="_gaps_m">
+ <MkFeatureBanner icon="/client-assets/speaker_high_volume_3d.png" color="#ff006f">
+ <SearchKeyword>{{ i18n.ts._settings.soundsBanner }}</SearchKeyword>
+ </MkFeatureBanner>
- <FormSection>
- <template #label>{{ i18n.ts.sounds }}</template>
- <div class="_gaps_s">
- <MkFolder v-for="type in operationTypes" :key="type">
- <template #label>{{ i18n.ts._sfx[type] }}</template>
- <template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
- <Suspense>
- <template #default>
- <XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
- </template>
- <template #fallback>
- <MkLoading/>
- </template>
- </Suspense>
- </MkFolder>
- </div>
- </FormSection>
+ <SearchMarker :keywords="['mute']">
+ <MkPreferenceContainer k="sound.notUseSound">
+ <MkSwitch v-model="notUseSound">
+ <template #label><SearchLabel>{{ i18n.ts.notUseSound }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
- <MkButton danger @click="reset()"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
-</div>
+ <SearchMarker :keywords="['active', 'mute']">
+ <MkPreferenceContainer k="sound.useSoundOnlyWhenActive">
+ <MkSwitch v-model="useSoundOnlyWhenActive">
+ <template #label><SearchLabel>{{ i18n.ts.useSoundOnlyWhenActive }}</SearchLabel></template>
+ </MkSwitch>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <SearchMarker :keywords="['volume', 'master']">
+ <MkPreferenceContainer k="sound.masterVolume">
+ <MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`">
+ <template #label><SearchLabel>{{ i18n.ts.masterVolume }}</SearchLabel></template>
+ </MkRange>
+ </MkPreferenceContainer>
+ </SearchMarker>
+
+ <FormSection>
+ <template #label>{{ i18n.ts.sounds }}</template>
+ <div class="_gaps_s">
+ <MkFolder v-for="type in operationTypes" :key="type">
+ <template #label>{{ i18n.ts._sfx[type] }}</template>
+ <template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
+ <Suspense>
+ <template #default>
+ <XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
+ </template>
+ <template #fallback>
+ <MkLoading/>
+ </template>
+ </Suspense>
+ </MkFolder>
+ </div>
+ </FormSection>
+
+ <MkButton danger @click="reset()"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
-import { Ref, computed, ref } from 'vue';
+import { computed, ref } from 'vue';
import XSound from './sounds.sound.vue';
-import type { SoundType, OperationType } from '@/scripts/sound.js';
-import type { SoundStore } from '@/store.js';
+import type { Ref } from 'vue';
+import type { SoundType, OperationType } from '@/utility/sound.js';
+import type { SoundStore } from '@/preferences/def.js';
+import { prefer } from '@/preferences.js';
import MkRange from '@/components/MkRange.vue';
import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
import MkFolder from '@/components/MkFolder.vue';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { operationTypes } from '@/scripts/sound.js';
-import { defaultStore } from '@/store.js';
+import { definePage } from '@/page.js';
+import { operationTypes } from '@/utility/sound.js';
import MkSwitch from '@/components/MkSwitch.vue';
+import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
+import { PREF_DEF } from '@/preferences/def.js';
+import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
-const notUseSound = computed(defaultStore.makeGetterSetter('sound_notUseSound'));
-const useSoundOnlyWhenActive = computed(defaultStore.makeGetterSetter('sound_useSoundOnlyWhenActive'));
-const masterVolume = computed(defaultStore.makeGetterSetter('sound_masterVolume'));
+const notUseSound = prefer.model('sound.notUseSound');
+const useSoundOnlyWhenActive = prefer.model('sound.useSoundOnlyWhenActive');
+const masterVolume = prefer.model('sound.masterVolume');
const sounds = ref<Record<OperationType, Ref<SoundStore>>>({
- note: defaultStore.reactiveState.sound_note,
- noteMy: defaultStore.reactiveState.sound_noteMy,
- notification: defaultStore.reactiveState.sound_notification,
- reaction: defaultStore.reactiveState.sound_reaction,
+ note: prefer.r['sound.on.note'],
+ noteMy: prefer.r['sound.on.noteMy'],
+ notification: prefer.r['sound.on.notification'],
+ reaction: prefer.r['sound.on.reaction'],
+ chatMessage: prefer.r['sound.on.chatMessage'],
});
function getSoundTypeName(f: SoundType): string {
@@ -82,14 +107,14 @@ async function updated(type: keyof typeof sounds.value, sound) {
volume: sound.volume,
};
- defaultStore.set(`sound_${type}`, v);
+ prefer.commit(`sound.on.${type}`, v);
sounds.value[type] = v;
}
function reset() {
for (const sound of Object.keys(sounds.value) as Array<keyof typeof sounds.value>) {
- const v = defaultStore.def[`sound_${sound}`].default;
- defaultStore.set(`sound_${sound}`, v);
+ const v = PREF_DEF[`sound.on.${sound}`].default;
+ prefer.commit(`sound.on.${sound}`, v);
sounds.value[sound] = v;
}
}
@@ -98,7 +123,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.sounds,
icon: 'ti ti-music',
}));
diff --git a/packages/frontend/src/pages/settings/statusbar.statusbar.vue b/packages/frontend/src/pages/settings/statusbar.statusbar.vue
index 140b6beb14..dbb640123a 100644
--- a/packages/frontend/src/pages/settings/statusbar.statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.statusbar.vue
@@ -94,17 +94,17 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import MkRange from '@/components/MkRange.vue';
-import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { deepClone } from '@/scripts/clone.js';
+import { deepClone } from '@/utility/clone.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
_id: string;
userLists: Misskey.entities.UserList[] | null;
}>();
-const statusbar = reactive(deepClone(defaultStore.state.statusbars.find(x => x.id === props._id)));
+const statusbar = reactive(deepClone(prefer.s.statusbars.find(x => x.id === props._id)));
watch(() => statusbar.type, () => {
if (statusbar.type === 'rss') {
@@ -134,13 +134,13 @@ watch(() => statusbar.type, () => {
watch(statusbar, save);
async function save() {
- const i = defaultStore.state.statusbars.findIndex(x => x.id === props._id);
- const statusbars = deepClone(defaultStore.state.statusbars);
+ const i = prefer.s.statusbars.findIndex(x => x.id === props._id);
+ const statusbars = deepClone(prefer.s.statusbars);
statusbars[i] = deepClone(statusbar);
- defaultStore.set('statusbars', statusbars);
+ prefer.commit('statusbars', statusbars);
}
function del() {
- defaultStore.set('statusbars', defaultStore.state.statusbars.filter(x => x.id !== props._id));
+ prefer.commit('statusbars', prefer.s.statusbars.filter(x => x.id !== props._id));
}
</script>
diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue
index 1ae3de7994..7e6a536216 100644
--- a/packages/frontend/src/pages/settings/statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.vue
@@ -21,12 +21,12 @@ import { v4 as uuid } from 'uuid';
import XStatusbar from './statusbar.statusbar.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
+import { prefer } from '@/preferences.js';
-const statusbars = defaultStore.reactiveState.statusbars;
+const statusbars = prefer.r.statusbars;
const userLists = ref<Misskey.entities.UserList[] | null>(null);
@@ -37,20 +37,20 @@ onMounted(() => {
});
async function add() {
- defaultStore.push('statusbars', {
+ prefer.commit('statusbars', [...statusbars.value, {
id: uuid(),
type: null,
black: false,
size: 'medium',
props: {},
- });
+ }]);
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.statusbar,
icon: 'ti ti-list',
}));
diff --git a/packages/frontend/src/pages/settings/theme.install.vue b/packages/frontend/src/pages/settings/theme.install.vue
index 4f05d3784c..ac95279402 100644
--- a/packages/frontend/src/pages/settings/theme.install.vue
+++ b/packages/frontend/src/pages/settings/theme.install.vue
@@ -10,8 +10,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkCodeEditor>
<div class="_buttons">
- <MkButton :disabled="installThemeCode == null" inline @click="() => previewTheme(installThemeCode)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
- <MkButton :disabled="installThemeCode == null" primary inline @click="() => install(installThemeCode)"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
+ <MkButton :disabled="installThemeCode == null || installThemeCode.trim() === ''" inline @click="() => previewTheme(installThemeCode)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
+ <MkButton :disabled="installThemeCode == null || installThemeCode.trim() === ''" primary inline @click="() => install(installThemeCode)"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
</div>
</div>
</template>
@@ -20,11 +20,13 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, computed } from 'vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkButton from '@/components/MkButton.vue';
-import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js';
+import { parseThemeCode, previewTheme, installTheme } from '@/theme.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
+import { useRouter } from '@/router.js';
+const router = useRouter();
const installThemeCode = ref<string | null>(null);
async function install(code: string): Promise<void> {
@@ -35,6 +37,8 @@ async function install(code: string): Promise<void> {
type: 'success',
text: i18n.tsx._theme.installed({ name: theme.name }),
});
+ installThemeCode.value = null;
+ router.push('/settings/theme');
} catch (err) {
switch (err.message.toLowerCase()) {
case 'this theme is already installed':
@@ -59,7 +63,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts._theme.install,
icon: 'ti ti-download',
}));
diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue
index 579ca6b20b..c68c04bb44 100644
--- a/packages/frontend/src/pages/settings/theme.manage.vue
+++ b/packages/frontend/src/pages/settings/theme.manage.vue
@@ -33,16 +33,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref } from 'vue';
import JSON5 from 'json5';
+import type { Theme } from '@/theme.js';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
-import { Theme, getBuiltinThemesRef } from '@/scripts/theme.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { getBuiltinThemesRef } from '@/theme.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import * as os from '@/os.js';
import { getThemes, removeTheme } from '@/theme-store.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const installedThemes = ref(getThemes());
const builtinThemes = getBuiltinThemesRef();
@@ -62,7 +63,6 @@ const selectedThemeCode = computed(() => {
function copyThemeCode() {
copyToClipboard(selectedThemeCode.value);
- os.success();
}
function uninstall() {
@@ -76,7 +76,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts._theme.manage,
icon: 'ti ti-tool',
}));
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index 9f7842ecdc..b0c0f0b5bb 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -4,137 +4,271 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_gaps_m rsljpzjq">
- <div v-adaptive-border class="rfqxtzch _panel">
- <div class="toggle">
- <div class="toggleWrapper">
- <input id="dn" v-model="darkMode" type="checkbox" class="dn"/>
- <label for="dn" class="toggle">
- <span class="before">{{ i18n.ts.light }}</span>
- <span class="after">{{ i18n.ts.dark }}</span>
- <span class="toggle__handler">
- <span class="crater crater--1"></span>
- <span class="crater crater--2"></span>
- <span class="crater crater--3"></span>
- </span>
- <span class="star star--1"></span>
- <span class="star star--2"></span>
- <span class="star star--3"></span>
- <span class="star star--4"></span>
- <span class="star star--5"></span>
- <span class="star star--6"></span>
- </label>
+<SearchMarker path="/settings/theme" :label="i18n.ts.theme" :keywords="['theme']" icon="ti ti-palette">
+ <div class="_gaps_m">
+ <div v-adaptive-border class="rfqxtzch _panel">
+ <div class="toggle">
+ <div class="toggleWrapper">
+ <input id="dn" v-model="darkMode" type="checkbox" class="dn"/>
+ <label for="dn" class="toggle">
+ <span class="before">{{ i18n.ts.light }}</span>
+ <span class="after">{{ i18n.ts.dark }}</span>
+ <span class="toggle__handler">
+ <span class="crater crater--1"></span>
+ <span class="crater crater--2"></span>
+ <span class="crater crater--3"></span>
+ </span>
+ <span class="star star--1"></span>
+ <span class="star star--2"></span>
+ <span class="star star--3"></span>
+ <span class="star star--4"></span>
+ <span class="star star--5"></span>
+ <span class="star star--6"></span>
+ </label>
+ </div>
+ </div>
+ <div class="sync">
+ <SearchMarker :keywords="['sync', 'device', 'dark', 'light', 'mode']">
+ <MkSwitch v-model="syncDeviceDarkMode">
+ <template #label><SearchLabel>{{ i18n.ts.syncDeviceDarkMode }}</SearchLabel></template>
+ </MkSwitch>
+ </SearchMarker>
</div>
</div>
- <div class="sync">
- <MkSwitch v-model="syncDeviceDarkMode">{{ i18n.ts.syncDeviceDarkMode }}</MkSwitch>
- </div>
- </div>
- <div class="selects">
- <MkSelect v-model="lightThemeId" large class="select">
- <template #label>{{ i18n.ts.themeForLightMode }}</template>
- <template #prefix><i class="ti ti-sun"></i></template>
- <option v-if="instanceLightTheme" :key="'instance:' + instanceLightTheme.id" :value="instanceLightTheme.id">{{ instanceLightTheme.name }}</option>
- <optgroup v-if="installedLightThemes.length > 0" :label="i18n.ts._theme.installedThemes">
- <option v-for="x in installedLightThemes" :key="'installed:' + x.id" :value="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="i18n.ts._theme.builtinThemes">
- <option v-for="x in builtinLightThemes" :key="'builtin:' + x.id" :value="x.id">{{ x.name }}</option>
- </optgroup>
- </MkSelect>
- <MkSelect v-model="darkThemeId" large class="select">
- <template #label>{{ i18n.ts.themeForDarkMode }}</template>
- <template #prefix><i class="ti ti-moon"></i></template>
- <option v-if="instanceDarkTheme" :key="'instance:' + instanceDarkTheme.id" :value="instanceDarkTheme.id">{{ instanceDarkTheme.name }}</option>
- <optgroup v-if="installedDarkThemes.length > 0" :label="i18n.ts._theme.installedThemes">
- <option v-for="x in installedDarkThemes" :key="'installed:' + x.id" :value="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="i18n.ts._theme.builtinThemes">
- <option v-for="x in builtinDarkThemes" :key="'builtin:' + x.id" :value="x.id">{{ x.name }}</option>
- </optgroup>
- </MkSelect>
- </div>
+ <div class="_gaps">
+ <template v-if="!darkMode">
+ <SearchMarker :keywords="['light', 'theme']">
+ <MkFolder :defaultOpen="true" :max-height="500">
+ <template #icon><i class="ti ti-sun"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.themeForLightMode }}</SearchLabel></template>
+ <template #caption>{{ lightThemeName }}</template>
+
+ <div class="_gaps_m">
+ <FormSection v-if="instanceLightTheme != null" first>
+ <template #label>{{ i18n.ts._theme.instanceTheme }}</template>
+ <div :class="$style.themeSelect">
+ <div :class="$style.themeItemOuter">
+ <input
+ :id="`themeRadio_${instanceLightTheme.id}`"
+ v-model="lightThemeId"
+ type="radio"
+ name="lightTheme"
+ :class="$style.themeRadio"
+ :value="instanceLightTheme.id"
+ />
+ <label :for="`themeRadio_${instanceLightTheme.id}`" :class="$style.themeItemRoot" class="_button">
+ <MkThemePreview :theme="instanceLightTheme" :class="$style.themeItemPreview"/>
+ <div :class="$style.themeItemCaption">{{ instanceLightTheme.name }}</div>
+ </label>
+ </div>
+ </div>
+ </FormSection>
- <FormSection>
- <div class="_formLinksGrid">
- <FormLink to="/settings/theme/manage"><template #icon><i class="ti ti-tool"></i></template>{{ i18n.ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink>
- <FormLink to="https://assets.misskey.io/theme/list" external><template #icon><i class="ti ti-world"></i></template>{{ i18n.ts._theme.explore }}</FormLink>
- <FormLink to="/settings/theme/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._theme.install }}</FormLink>
- <FormLink to="/theme-editor"><template #icon><i class="ti ti-paint"></i></template>{{ i18n.ts._theme.make }}</FormLink>
+ <FormSection v-if="installedLightThemes.length > 0" :first="instanceLightTheme == null">
+ <template #label>{{ i18n.ts._theme.installedThemes }}</template>
+ <div :class="$style.themeSelect">
+ <div v-for="theme in installedLightThemes" :class="$style.themeItemOuter">
+ <input
+ :id="`themeRadio_${theme.id}`"
+ v-model="lightThemeId"
+ type="radio"
+ name="lightTheme"
+ :class="$style.themeRadio"
+ :value="theme.id"
+ />
+ <label :for="`themeRadio_${theme.id}`" :class="$style.themeItemRoot" class="_button">
+ <MkThemePreview :theme="theme" :class="$style.themeItemPreview"/>
+ <div :class="$style.themeItemCaption">{{ theme.name }}</div>
+ </label>
+ </div>
+ </div>
+ </FormSection>
+
+ <FormSection :first="installedLightThemes.length === 0 && instanceLightTheme == null">
+ <template #label>{{ i18n.ts._theme.builtinThemes }}</template>
+ <div :class="$style.themeSelect">
+ <div v-for="theme in builtinLightThemes" :class="$style.themeItemOuter">
+ <input
+ :id="`themeRadio_${theme.id}`"
+ v-model="lightThemeId"
+ type="radio"
+ name="lightTheme"
+ :class="$style.themeRadio"
+ :value="theme.id"
+ />
+ <label :for="`themeRadio_${theme.id}`" :class="$style.themeItemRoot" class="_button">
+ <MkThemePreview :theme="theme" :class="$style.themeItemPreview"/>
+ <div :class="$style.themeItemCaption">{{ theme.name }}</div>
+ </label>
+ </div>
+ </div>
+ </FormSection>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+ </template>
+ <template v-else>
+ <SearchMarker :keywords="['dark', 'theme']">
+ <MkFolder :defaultOpen="true" :max-height="500">
+ <template #icon><i class="ti ti-moon"></i></template>
+ <template #label><SearchLabel>{{ i18n.ts.themeForDarkMode }}</SearchLabel></template>
+ <template #caption>{{ darkThemeName }}</template>
+
+ <div class="_gaps_m">
+ <FormSection v-if="instanceDarkTheme != null" first>
+ <template #label>{{ i18n.ts._theme.instanceTheme }}</template>
+ <div :class="$style.themeSelect">
+ <div :class="$style.themeItemOuter">
+ <input
+ :id="`themeRadio_${instanceDarkTheme.id}`"
+ v-model="darkThemeId"
+ type="radio"
+ name="darkTheme"
+ :class="$style.themeRadio"
+ :value="instanceDarkTheme.id"
+ />
+ <label :for="`themeRadio_${instanceDarkTheme.id}`" :class="$style.themeItemRoot" class="_button">
+ <MkThemePreview :theme="instanceDarkTheme" :class="$style.themeItemPreview"/>
+ <div :class="$style.themeItemCaption">{{ instanceDarkTheme.name }}</div>
+ </label>
+ </div>
+ </div>
+ </FormSection>
+
+ <FormSection v-if="installedDarkThemes.length > 0" :first="instanceDarkTheme == null">
+ <template #label>{{ i18n.ts._theme.installedThemes }}</template>
+ <div :class="$style.themeSelect">
+ <div v-for="theme in installedDarkThemes" :class="$style.themeItemOuter">
+ <input
+ :id="`themeRadio_${theme.id}`"
+ v-model="darkThemeId"
+ type="radio"
+ name="darkTheme"
+ :class="$style.themeRadio"
+ :value="theme.id"
+ />
+ <label :for="`themeRadio_${theme.id}`" :class="$style.themeItemRoot" class="_button">
+ <MkThemePreview :theme="theme" :class="$style.themeItemPreview"/>
+ <div :class="$style.themeItemCaption">{{ theme.name }}</div>
+ </label>
+ </div>
+ </div>
+ </FormSection>
+
+ <FormSection :first="installedDarkThemes.length === 0 && instanceDarkTheme == null">
+ <template #label>{{ i18n.ts._theme.builtinThemes }}</template>
+ <div :class="$style.themeSelect">
+ <div v-for="theme in builtinDarkThemes" :class="$style.themeItemOuter">
+ <input
+ :id="`themeRadio_${theme.id}`"
+ v-model="darkThemeId"
+ type="radio"
+ name="darkTheme"
+ :class="$style.themeRadio"
+ :value="theme.id"
+ />
+ <label :for="`themeRadio_${theme.id}`" :class="$style.themeItemRoot" class="_button">
+ <MkThemePreview :theme="theme" :class="$style.themeItemPreview"/>
+ <div :class="$style.themeItemCaption">{{ theme.name }}</div>
+ </label>
+ </div>
+ </div>
+ </FormSection>
+ </div>
+ </MkFolder>
+ </SearchMarker>
+ </template>
</div>
- </FormSection>
- <MkButton v-if="wallpaper == null" @click="setWallpaper">{{ i18n.ts.setWallpaper }}</MkButton>
- <MkButton v-else @click="wallpaper = null">{{ i18n.ts.removeWallpaper }}</MkButton>
-</div>
+ <FormSection>
+ <div class="_formLinksGrid">
+ <FormLink to="/settings/theme/manage"><template #icon><i class="ti ti-tool"></i></template>{{ i18n.ts._theme.manage }}<template #suffix>{{ themesCount }}</template></FormLink>
+ <FormLink to="https://assets.misskey.io/theme/list" external><template #icon><i class="ti ti-world"></i></template>{{ i18n.ts._theme.explore }}</FormLink>
+ <FormLink to="/settings/theme/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._theme.install }}</FormLink>
+ <FormLink to="/theme-editor"><template #icon><i class="ti ti-paint"></i></template>{{ i18n.ts._theme.make }}</FormLink>
+ </div>
+ </FormSection>
+
+ <SearchMarker :keywords="['wallpaper']">
+ <MkButton v-if="wallpaper == null" @click="setWallpaper"><SearchLabel>{{ i18n.ts.setWallpaper }}</SearchLabel></MkButton>
+ <MkButton v-else @click="wallpaper = null">{{ i18n.ts.removeWallpaper }}</MkButton>
+ </SearchMarker>
+ </div>
+</SearchMarker>
</template>
<script lang="ts" setup>
import { computed, onActivated, ref, watch } from 'vue';
import JSON5 from 'json5';
+import defaultLightTheme from '@@/themes/l-light.json5';
+import defaultDarkTheme from '@@/themes/d-green-lime.json5';
+import type { Theme } from '@/theme.js';
import MkSwitch from '@/components/MkSwitch.vue';
-import MkSelect from '@/components/MkSelect.vue';
import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue';
import MkButton from '@/components/MkButton.vue';
-import { getBuiltinThemesRef } from '@/scripts/theme.js';
-import { selectFile } from '@/scripts/select-file.js';
-import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
-import { ColdDeviceStorage, defaultStore } from '@/store.js';
+import MkFolder from '@/components/MkFolder.vue';
+import MkThemePreview from '@/components/MkThemePreview.vue';
+import { getBuiltinThemesRef } from '@/theme.js';
+import { selectFile } from '@/utility/select-file.js';
+import { isDeviceDarkmode } from '@/utility/is-device-darkmode.js';
+import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { uniqueBy } from '@/scripts/array.js';
-import { fetchThemes, getThemes } from '@/theme-store.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { uniqueBy } from '@/utility/array.js';
+import { getThemes } from '@/theme-store.js';
+import { definePage } from '@/page.js';
import { miLocalStorage } from '@/local-storage.js';
-import { reloadAsk } from '@/scripts/reload-ask.js';
-import * as os from '@/os.js';
+import { reloadAsk } from '@/utility/reload-ask.js';
+import { prefer } from '@/preferences.js';
const installedThemes = ref(getThemes());
const builtinThemes = getBuiltinThemesRef();
-const instanceDarkTheme = computed(() => instance.defaultDarkTheme ? JSON5.parse(instance.defaultDarkTheme) : null);
+const instanceDarkTheme = computed<Theme | null>(() => instance.defaultDarkTheme ? JSON5.parse(instance.defaultDarkTheme) : null);
const installedDarkThemes = computed(() => installedThemes.value.filter(t => t.base === 'dark' || t.kind === 'dark'));
const builtinDarkThemes = computed(() => builtinThemes.value.filter(t => t.base === 'dark' || t.kind === 'dark'));
-const instanceLightTheme = computed(() => instance.defaultLightTheme ? JSON5.parse(instance.defaultLightTheme) : null);
+const instanceLightTheme = computed<Theme | null>(() => instance.defaultLightTheme ? JSON5.parse(instance.defaultLightTheme) : null);
const installedLightThemes = computed(() => installedThemes.value.filter(t => t.base === 'light' || t.kind === 'light'));
const builtinLightThemes = computed(() => builtinThemes.value.filter(t => t.base === 'light' || t.kind === 'light'));
const themes = computed(() => uniqueBy([instanceDarkTheme.value, instanceLightTheme.value, ...builtinThemes.value, ...installedThemes.value].filter(x => x != null), theme => theme.id));
-const darkTheme = ColdDeviceStorage.ref('darkTheme');
+const darkTheme = prefer.r.darkTheme;
+const darkThemeName = computed(() => darkTheme.value?.name ?? defaultDarkTheme.name);
const darkThemeId = computed({
get() {
- return darkTheme.value.id;
+ return darkTheme.value ? darkTheme.value.id : defaultDarkTheme.id;
},
set(id) {
const t = themes.value.find(x => x.id === id);
if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる
- ColdDeviceStorage.set('darkTheme', t);
+ prefer.commit('darkTheme', t);
}
},
});
-const lightTheme = ColdDeviceStorage.ref('lightTheme');
+const lightTheme = prefer.r.lightTheme;
+const lightThemeName = computed(() => lightTheme.value?.name ?? defaultLightTheme.name);
const lightThemeId = computed({
get() {
- return lightTheme.value.id;
+ return lightTheme.value ? lightTheme.value.id : defaultLightTheme.id;
},
set(id) {
const t = themes.value.find(x => x.id === id);
if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる
- ColdDeviceStorage.set('lightTheme', t);
+ prefer.commit('lightTheme', t);
}
},
});
-const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
-const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
+const darkMode = computed(store.makeGetterSetter('darkMode'));
+const syncDeviceDarkMode = prefer.model('syncDeviceDarkMode');
const wallpaper = ref(miLocalStorage.getItem('wallpaper'));
const themesCount = installedThemes.value.length;
watch(syncDeviceDarkMode, () => {
if (syncDeviceDarkMode.value) {
- defaultStore.set('darkMode', isDeviceDarkmode());
+ store.set('darkMode', isDeviceDarkmode());
}
});
@@ -148,12 +282,6 @@ watch(wallpaper, async () => {
});
onActivated(() => {
- fetchThemes().then(() => {
- installedThemes.value = getThemes();
- });
-});
-
-fetchThemes().then(() => {
installedThemes.value = getThemes();
});
@@ -167,12 +295,63 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.theme,
icon: 'ti ti-palette',
}));
</script>
+<style module>
+.themeSelect {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: var(--MI-margin);
+}
+
+.themeItemOuter {
+ position: relative;
+}
+
+.themeRadio {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+ pointer-events: none;
+}
+
+.themeItemRoot {
+ position: relative;
+ display: block;
+ overflow: clip;
+ box-sizing: border-box;
+ border: 2px solid var(--MI_THEME-divider);
+ border-radius: var(--MI-radius);
+}
+
+.themeRadio:focus-visible + .themeItemRoot {
+ outline: 2px solid var(--MI_THEME-focus);
+ outline-offset: 2px;
+}
+
+.themeRadio:checked + .themeItemRoot {
+ border-color: var(--MI_THEME-accent);
+}
+
+.themeItemPreview {
+ display: block;
+ width: calc(100% + 2px);
+ height: auto;
+ margin-left: -1px;
+ border-bottom: 1px solid var(--MI_THEME-divider);
+}
+
+.themeItemCaption {
+ box-sizing: border-box;
+ padding: 8px 12px;
+ text-align: center;
+ font-size: 80%;
+}
+</style>
+
<style lang="scss" scoped>
.rfqxtzch {
border-radius: var(--MI-radius-sm);
@@ -408,17 +587,4 @@ definePageMetadata(() => ({
border-top: solid 0.5px var(--MI_THEME-divider);
}
}
-
-.rsljpzjq {
- > .selects {
- display: flex;
- gap: 1.5em var(--MI-margin);
- flex-wrap: wrap;
-
- > .select {
- flex: 1;
- min-width: 280px;
- }
- }
-}
</style>
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index 22b008fb61..6a6cec70ba 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -76,10 +76,10 @@ import FormSection from '@/components/form/section.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { useRouter } from '@/router/supplier.js';
+import { definePage } from '@/page.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
@@ -155,7 +155,7 @@ const headerActions = computed(() => []);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: 'Edit webhook',
icon: 'ti ti-webhook',
}));
diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue
index 727c4df2d6..e853f967cb 100644
--- a/packages/frontend/src/pages/settings/webhook.new.vue
+++ b/packages/frontend/src/pages/settings/webhook.new.vue
@@ -46,7 +46,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const name = ref('');
const url = ref('');
@@ -82,7 +82,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: 'Create new webhook',
icon: 'ti ti-webhook',
}));
diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue
deleted file mode 100644
index af8b7ca945..0000000000
--- a/packages/frontend/src/pages/settings/webhook.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div class="_gaps_m">
- <FormLink :to="`/settings/webhook/new`">
- {{ i18n.ts._webhookSettings.createWebhook }}
- </FormLink>
-
- <FormSection>
- <MkPagination :pagination="pagination">
- <template #default="{items}">
- <div class="_gaps">
- <FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`">
- <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(--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>
- <MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime>
- </template>
- </FormLink>
- </div>
- </template>
- </MkPagination>
- </FormSection>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { computed } from 'vue';
-import MkPagination from '@/components/MkPagination.vue';
-import FormSection from '@/components/form/section.vue';
-import FormLink from '@/components/form/link.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { i18n } from '@/i18n.js';
-
-const pagination = {
- endpoint: 'i/webhooks/list' as const,
- limit: 100,
- noPaging: true,
-};
-
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => []);
-
-definePageMetadata(() => ({
- title: 'Webhook',
- icon: 'ti ti-webhook',
-}));
-</script>
diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue
index 37f6558d64..57afdb9121 100644
--- a/packages/frontend/src/pages/share.vue
+++ b/packages/frontend/src/pages/share.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<MkPostForm
v-if="state === 'writing'"
@@ -26,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton @click="goToMisskey">{{ i18n.ts.goToMisskey }}</MkButton>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -37,9 +36,9 @@ import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkPostForm from '@/components/MkPostForm.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { postMessageToParentWindow } from '@/scripts/post-message.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
+import { postMessageToParentWindow } from '@/utility/post-message.js';
import { i18n } from '@/i18n.js';
const urlParams = new URLSearchParams(window.location.search);
@@ -182,12 +181,12 @@ function close(): void {
// 閉じなければ100ms後タイムラインに
window.setTimeout(() => {
- location.href = '/';
+ window.location.href = '/';
}, 100);
}
function goToMisskey(): void {
- location.href = '/';
+ window.location.href = '/';
}
function onPosted(): void {
@@ -199,7 +198,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.share,
icon: 'ti ti-share',
}));
diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue
index 503e6e0f73..27f6b35859 100644
--- a/packages/frontend/src/pages/signup-complete.vue
+++ b/packages/frontend/src/pages/signup-complete.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div>
- <MkAnimBg style="position: fixed; top: 0;"/>
+<PageWithAnimBg>
<div :class="$style.formContainer">
<form :class="$style.form" class="_panel" @submit.prevent="submit()">
<div :class="$style.banner">
@@ -21,17 +20,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</form>
</div>
-</div>
+</PageWithAnimBg>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
-import MkAnimBg from '@/components/MkAnimBg.vue';
-import { login } from '@/account.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { login } from '@/accounts.js';
const submitting = ref(false);
@@ -71,8 +69,8 @@ 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 {
diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue
index a45b61fb10..f0f7390d2c 100644
--- a/packages/frontend/src/pages/tag.vue
+++ b/packages/frontend/src/pages/tag.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<MkNotes ref="notes" class="" :pagination="pagination"/>
</MkSpacer>
@@ -16,19 +15,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSpacer>
</div>
</template>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import MkNotes from '@/components/MkNotes.vue';
import MkButton from '@/components/MkButton.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
-import { defaultStore } from '@/store.js';
+import { $i } from '@/i.js';
+import { store } from '@/store.js';
import * as os from '@/os.js';
-import { genEmbedCode } from '@/scripts/get-embed-code.js';
+import { genEmbedCode } from '@/utility/get-embed-code.js';
const props = defineProps<{
tag: string;
@@ -44,11 +43,11 @@ const pagination = {
const notes = ref<InstanceType<typeof MkNotes>>();
async function post() {
- defaultStore.set('postFormHashtags', props.tag);
- defaultStore.set('postFormWithHashtags', true);
+ store.set('postFormHashtags', props.tag);
+ store.set('postFormWithHashtags', true);
await os.post();
- defaultStore.set('postFormHashtags', '');
- defaultStore.set('postFormWithHashtags', false);
+ store.set('postFormHashtags', '');
+ store.set('postFormWithHashtags', false);
notes.value?.pagingComponent?.reload();
}
@@ -63,12 +62,12 @@ const headerActions = computed(() => [{
genEmbedCode('tags', props.tag);
},
}], ev.currentTarget ?? ev.target);
- }
+ },
}]);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: props.tag,
icon: 'ti ti-hash',
}));
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index e49d6af470..d3c37a32b6 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800" :marginMin="16" :marginMax="32">
<div class="cwepdizn _gaps_m">
<MkFolder :defaultOpen="true">
@@ -69,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
@@ -78,23 +77,23 @@ import { toUnicode } from 'punycode.js';
import tinycolor from 'tinycolor2';
import { v4 as uuid } from 'uuid';
import JSON5 from 'json5';
-
import lightTheme from '@@/themes/_light.json5';
import darkTheme from '@@/themes/_dark.json5';
+import { host } from '@@/js/config.js';
+import type { Theme } from '@/theme.js';
import MkButton from '@/components/MkButton.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkFolder from '@/components/MkFolder.vue';
-
-import { $i } from '@/account.js';
-import { Theme, applyTheme } from '@/scripts/theme.js';
-import { host } from '@@/js/config.js';
+import { $i } from '@/i.js';
+import { applyTheme } from '@/theme.js';
import * as os from '@/os.js';
-import { ColdDeviceStorage, defaultStore } from '@/store.js';
+import { store } from '@/store.js';
import { addTheme } from '@/theme-store.js';
import { i18n } from '@/i18n.js';
-import { useLeaveGuard } from '@/scripts/use-leave-guard.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { useLeaveGuard } from '@/use/use-leave-guard.js';
+import { definePage } from '@/page.js';
+import { prefer } from '@/preferences.js';
const bgColors = [
{ color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' },
@@ -196,10 +195,10 @@ async function saveAs() {
if (description.value) theme.value.desc = description.value;
await addTheme(theme.value);
applyTheme(theme.value);
- if (defaultStore.state.darkMode) {
- ColdDeviceStorage.set('darkTheme', theme.value);
+ if (store.s.darkMode) {
+ prefer.commit('darkTheme', theme.value);
} else {
- ColdDeviceStorage.set('lightTheme', theme.value);
+ prefer.commit('lightTheme', theme.value);
}
changed.value = false;
os.alert({
@@ -219,7 +218,7 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.themeEditor,
icon: 'ti ti-palette',
}));
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 032a19a6eb..095d1651e1 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -4,15 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
+<PageWithHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true">
<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(--MI-margin);" closable @close="closeTutorial()">
+ <div ref="rootEl">
+ <MkInfo v-if="isBasicTimeline(src) && !store.r.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(--MI-margin);"/>
+ <MkPostForm v-if="prefer.r.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
@@ -32,56 +31,60 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkHorizontalSwipe>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue';
+import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated } from 'vue';
+import { scroll } from '@@/js/scroll.js';
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
+import type { MenuItem } from '@/types/menu.js';
+import type { BasicTimelineType } from '@/timelines.js';
import MkTimeline from '@/components/MkTimeline.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkPostForm from '@/components/MkPostForm.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { scroll } from '@@/js/scroll.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { $i } from '@/i.js';
+import { definePage } from '@/page.js';
import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js';
-import { deviceKind } from '@/scripts/device-kind.js';
-import { deepMerge } from '@/scripts/merge.js';
-import type { MenuItem } from '@/types/menu.js';
+import { deviceKind } from '@/utility/device-kind.js';
+import { deepMerge } from '@/utility/merge.js';
import { miLocalStorage } from '@/local-storage.js';
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
-import type { BasicTimelineType } from '@/timelines.js';
-import { useRouter } from '@/router/supplier.js';
+import { prefer } from '@/preferences.js';
+import { useRouter } from '@/router.js';
provide('shouldOmitHeaderTitle', true);
-const router = useRouter();
+const tlComponent = useTemplateRef('tlComponent');
+const rootEl = useTemplateRef('rootEl');
-const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>();
-const rootEl = shallowRef<HTMLElement>();
+const router = useRouter();
+router.useListener('same', () => {
+ top();
+});
type TimelinePageSrc = BasicTimelineType | `list:${string}`;
const queue = ref(0);
const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global');
const src = computed<TimelinePageSrc>({
- get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value),
+ get: () => ($i ? store.r.tl.value.src : srcWhenNotSignin.value),
set: (x) => saveSrc(x),
});
const withRenotes = computed<boolean>({
- get: () => defaultStore.reactiveState.tl.value.filter.withRenotes,
+ get: () => store.r.tl.value.filter.withRenotes,
set: (x) => saveTlFilter('withRenotes', x),
});
// computed内での無限ループを防ぐためのフラグ
const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>(
- defaultStore.reactiveState.tl.value.filter.withReplies ? 'withReplies' :
- defaultStore.reactiveState.tl.value.filter.onlyFiles ? 'onlyFiles' :
+ store.r.tl.value.filter.withReplies ? 'withReplies' :
+ store.r.tl.value.filter.onlyFiles ? 'onlyFiles' :
false,
);
@@ -91,7 +94,7 @@ const withReplies = computed<boolean>({
if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'onlyFiles') {
return false;
} else {
- return defaultStore.reactiveState.tl.value.filter.withReplies;
+ return store.r.tl.value.filter.withReplies;
}
},
set: (x) => saveTlFilter('withReplies', x),
@@ -106,7 +109,7 @@ const onlyFiles = computed<boolean>({
if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'withReplies') {
return false;
} else {
- return defaultStore.reactiveState.tl.value.filter.onlyFiles;
+ return store.r.tl.value.filter.onlyFiles;
}
},
set: (x) => saveTlFilter('onlyFiles', x),
@@ -123,7 +126,7 @@ watch([withReplies, onlyFiles], ([withRepliesTo, onlyFilesTo]) => {
});
const withSensitive = computed<boolean>({
- get: () => defaultStore.reactiveState.tl.value.filter.withSensitive,
+ get: () => store.r.tl.value.filter.withSensitive,
set: (x) => saveTlFilter('withSensitive', x),
});
@@ -136,7 +139,7 @@ function queueUpdated(q: number): void {
}
function top(): void {
- if (rootEl.value) scroll(rootEl.value, { top: 0 });
+ if (rootEl.value) scroll(rootEl.value, { top: 0, behavior: 'smooth' });
}
async function chooseList(ev: MouseEvent): Promise<void> {
@@ -204,23 +207,23 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
}
function saveSrc(newSrc: TimelinePageSrc): void {
- const out = deepMerge({ src: newSrc }, defaultStore.state.tl);
+ const out = deepMerge({ src: newSrc }, store.s.tl);
if (newSrc.startsWith('userList:')) {
const id = newSrc.substring('userList:'.length);
- out.userList = defaultStore.reactiveState.pinnedUserLists.value.find(l => l.id === id) ?? null;
+ out.userList = prefer.r.pinnedUserLists.value.find(l => l.id === id) ?? null;
}
- defaultStore.set('tl', out);
+ store.set('tl', out);
if (['local', 'global'].includes(newSrc)) {
srcWhenNotSignin.value = newSrc as 'local' | 'global';
}
}
-function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) {
+function saveTlFilter(key: keyof typeof store.s.tl.filter, newValue: boolean) {
if (key !== 'withReplies' || $i) {
- const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl);
- defaultStore.set('tl', out);
+ const out = deepMerge({ filter: { [key]: newValue } }, store.s.tl);
+ store.set('tl', out);
}
}
@@ -239,9 +242,9 @@ function focus(): void {
function closeTutorial(): void {
if (!isBasicTimeline(src.value)) return;
- const before = defaultStore.state.timelineTutorials;
+ const before = store.s.timelineTutorials;
before[src.value] = true;
- defaultStore.set('timelineTutorials', before);
+ store.set('timelineTutorials', before);
}
function switchTlIfNeeded() {
@@ -311,7 +314,7 @@ const headerActions = computed(() => {
return tmp;
});
-const headerTabs = computed(() => [...(defaultStore.reactiveState.pinnedUserLists.value.map(l => ({
+const headerTabs = computed(() => [...(prefer.r.pinnedUserLists.value.map(l => ({
key: 'list:' + l.id,
title: l.name,
icon: 'ti ti-star',
@@ -350,7 +353,7 @@ const headerTabsWhenNotLogin = computed(() => [...availableBasicTimelines().map(
iconOnly: true,
}))] as Tab[]);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.timeline,
icon: isBasicTimeline(src.value) ? basicTimelineIconClass(src.value) : 'ti ti-home',
}));
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index 85e44b2503..a57d2bb51b 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<div ref="rootEl">
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
@@ -22,18 +21,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
-import { computed, watch, ref, shallowRef } from 'vue';
+import { computed, watch, ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
-import MkTimeline from '@/components/MkTimeline.vue';
import { scroll } from '@@/js/scroll.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import MkTimeline from '@/components/MkTimeline.vue';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import { defaultStore } from '@/store.js';
import { deepMerge } from '@/scripts/merge.js';
import * as os from '@/os.js';
@@ -47,12 +46,14 @@ const props = defineProps<{
const list = ref<Misskey.entities.UserList | null>(null);
const queue = ref(0);
-const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
-const rootEl = shallowRef<HTMLElement>();
+const tlEl = useTemplateRef('tlEl');
+const rootEl = useTemplateRef('rootEl');
+
const withRenotes = computed<boolean>({
get: () => defaultStore.reactiveState.tl.value.filter.withRenotes,
set: (x) => saveTlFilter('withRenotes', x),
});
+
const onlyFiles = computed<boolean>({
get: () => defaultStore.reactiveState.tl.value.filter.onlyFiles,
set: (x) => saveTlFilter('onlyFiles', x),
@@ -105,7 +106,7 @@ const headerActions = computed(() => list.value ? [{
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: list.value ? list.value.name : i18n.ts.lists,
icon: 'ti ti-list',
}));
diff --git a/packages/frontend/src/pages/user-tag.vue b/packages/frontend/src/pages/user-tag.vue
index a77493fe47..d1dc721a4b 100644
--- a/packages/frontend/src/pages/user-tag.vue
+++ b/packages/frontend/src/pages/user-tag.vue
@@ -4,21 +4,19 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader/></template>
-
+<PageWithHeader>
<MkSpacer :contentMax="1200">
<div class="_gaps_s">
<MkUserList :pagination="tagUsers"/>
</div>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import MkUserList from '@/components/MkUserList.vue';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
const props = defineProps<{
tag: string;
@@ -34,7 +32,7 @@ const tagUsers = computed(() => ({
},
}));
-definePageMetadata(() => ({
+definePage(() => ({
title: props.tag,
icon: 'ti ti-user-search',
}));
diff --git a/packages/frontend/src/pages/user/achievements.vue b/packages/frontend/src/pages/user/achievements.vue
index 403e74904c..8f13e959e1 100644
--- a/packages/frontend/src/pages/user/achievements.vue
+++ b/packages/frontend/src/pages/user/achievements.vue
@@ -13,8 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue';
import * as Misskey from 'misskey-js';
import MkAchievements from '@/components/MkAchievements.vue';
-import { claimAchievement } from '@/scripts/achievements.js';
-import { $i } from '@/account.js';
+import { claimAchievement } from '@/utility/achievements.js';
+import { $i } from '@/i.js';
const props = defineProps<{
user: Misskey.entities.User;
diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue
index aa2c791c76..f5d2002669 100644
--- a/packages/frontend/src/pages/user/activity.following.vue
+++ b/packages/frontend/src/pages/user/activity.following.vue
@@ -14,16 +14,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
-import { Chart, ChartDataset } from 'chart.js';
+import { onMounted, useTemplateRef, ref } from 'vue';
+import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js';
import gradient from 'chartjs-plugin-gradient';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { initChart } from '@/scripts/init-chart.js';
-import { chartLegend } from '@/scripts/chart-legend.js';
+import type { ChartDataset } from 'chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { initChart } from '@/utility/init-chart.js';
+import { chartLegend } from '@/utility/chart-legend.js';
import MkChartLegend from '@/components/MkChartLegend.vue';
initChart();
@@ -32,8 +33,8 @@ const props = defineProps<{
user: Misskey.entities.User;
}>();
-const chartEl = shallowRef<HTMLCanvasElement>(null);
-const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = useTemplateRef('chartEl');
+const legendEl = useTemplateRef('legendEl');
const now = new Date();
let chartInstance: Chart = null;
const chartLimit = 30;
@@ -63,7 +64,7 @@ async function renderChart() {
const raw = await misskeyApi('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' });
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const colorFollowLocal = '#008FFB';
const colorFollowRemote = '#008FFB88';
diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue
index 64514716d6..01c62810d4 100644
--- a/packages/frontend/src/pages/user/activity.notes.vue
+++ b/packages/frontend/src/pages/user/activity.notes.vue
@@ -14,16 +14,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
-import { Chart, ChartDataset } from 'chart.js';
+import { onMounted, useTemplateRef, ref } from 'vue';
+import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js';
import gradient from 'chartjs-plugin-gradient';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { initChart } from '@/scripts/init-chart.js';
-import { chartLegend } from '@/scripts/chart-legend.js';
+import type { ChartDataset } from 'chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { initChart } from '@/utility/init-chart.js';
+import { chartLegend } from '@/utility/chart-legend.js';
import MkChartLegend from '@/components/MkChartLegend.vue';
initChart();
@@ -32,8 +33,8 @@ const props = defineProps<{
user: Misskey.entities.User;
}>();
-const chartEl = shallowRef<HTMLCanvasElement>(null);
-const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = useTemplateRef('chartEl');
+const legendEl = useTemplateRef('legendEl');
const now = new Date();
let chartInstance: Chart = null;
const chartLimit = 50;
@@ -63,7 +64,7 @@ async function renderChart() {
const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const colorNormal = '#008FFB';
const colorReply = '#FEB019';
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index ce24807f93..ed12b1b5c7 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -14,16 +14,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
-import { Chart, ChartDataset } from 'chart.js';
+import { onMounted, useTemplateRef, ref } from 'vue';
+import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js';
import gradient from 'chartjs-plugin-gradient';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { initChart } from '@/scripts/init-chart.js';
-import { chartLegend } from '@/scripts/chart-legend.js';
+import type { ChartDataset } from 'chart.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { store } from '@/store.js';
+import { useChartTooltip } from '@/use/use-chart-tooltip.js';
+import { chartVLine } from '@/utility/chart-vline.js';
+import { initChart } from '@/utility/init-chart.js';
+import { chartLegend } from '@/utility/chart-legend.js';
import MkChartLegend from '@/components/MkChartLegend.vue';
initChart();
@@ -32,8 +33,8 @@ const props = defineProps<{
user: Misskey.entities.User;
}>();
-const chartEl = shallowRef<HTMLCanvasElement>(null);
-const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = useTemplateRef('chartEl');
+const legendEl = useTemplateRef('legendEl');
const now = new Date();
let chartInstance: Chart = null;
const chartLimit = 30;
@@ -63,7 +64,7 @@ async function renderChart() {
const raw = await misskeyApi('charts/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' });
- const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+ const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
const colorUser = '#3498db';
const colorVisitor = '#2ecc71';
diff --git a/packages/frontend/src/pages/user/followers.vue b/packages/frontend/src/pages/user/followers.vue
index 3a7d7b9ebd..b8ba023f74 100644
--- a/packages/frontend/src/pages/user/followers.vue
+++ b/packages/frontend/src/pages/user/followers.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs">
<MkSpacer :contentMax="1000">
<Transition name="fade" mode="out-in">
<div v-if="user">
@@ -15,15 +14,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-else/>
</Transition>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import XFollowList from './follow-list.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
@@ -52,7 +51,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.user,
icon: 'ti ti-user',
...user.value ? {
diff --git a/packages/frontend/src/pages/user/following.vue b/packages/frontend/src/pages/user/following.vue
index 045afd030a..1fe64c3042 100644
--- a/packages/frontend/src/pages/user/following.vue
+++ b/packages/frontend/src/pages/user/following.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
+<PageWithHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs">
<MkSpacer :contentMax="1000">
<Transition name="fade" mode="out-in">
<div v-if="user">
@@ -15,15 +14,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkLoading v-else/>
</Transition>
</MkSpacer>
-</MkStickyContainer>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import XFollowList from './follow-list.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
@@ -52,7 +51,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.user,
icon: 'ti ti-user',
...user.value ? {
diff --git a/packages/frontend/src/pages/user/home.stories.impl.ts b/packages/frontend/src/pages/user/home.stories.impl.ts
index c623ef9ee4..66d3579041 100644
--- a/packages/frontend/src/pages/user/home.stories.impl.ts
+++ b/packages/frontend/src/pages/user/home.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../../.storybook/fakes.js';
import { commonHandlers } from '../../../.storybook/mocks.js';
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index fb067e9a65..802de9778c 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -12,7 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="profile _gaps">
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
- <MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
+ <MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!"/>
+ <MkInfo v-if="user.host == null && user.username.includes('.')">{{ i18n.ts.isSystemAccount }}</MkInfo>
<div :key="user.id" class="main _panel">
<div class="banner-container" :class="{ [$style.bannerContainerTall]: useTallBanner }">
@@ -58,7 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
<div v-if="user.followedMessage != null" class="followedMessage">
- <MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin shadow>
+ <MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin>
<div class="messageHeader">{{ i18n.ts.messageToFollower }}</div>
<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user"/></MkSparkle></div>
</MkFukidashi>
@@ -205,21 +206,21 @@ 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 { getUserMenu } from '@/scripts/get-user-menu.js';
+import { getUserMenu } from '@/utility/get-user-menu.js';
import number from '@/filters/number.js';
import { userPage } from '@/filters/user.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
-import { $i, iAmModerator } from '@/account.js';
+import { $i, iAmModerator } from '@/i.js';
import { dateString } from '@/filters/date.js';
-import { confetti } from '@/scripts/confetti.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
-import { useRouter } from '@/router/supplier.js';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
+import { confetti } from '@/utility/confetti.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/utility/isFfVisibleForMe.js';
+import { useRouter } from '@/router.js';
+import { getStaticImageUrl } from '@/utility/media-proxy.js';
import { infoImageUrl } from '@/instance.js';
import MkSparkle from '@/components/MkSparkle.vue';
+import { prefer } from '@/preferences.js';
const MkNote = defineAsyncComponent(() =>
defaultStore.state.noteDesign === 'sharkey'
@@ -331,7 +332,7 @@ const AllPagination = {
const style = computed(() => {
if (props.user.bannerUrl == null) return {};
- if (defaultStore.state.disableShowingAnimatedImages) {
+ if (prefer.s.disableShowingAnimatedImages) {
return {
backgroundImage: `url(${ getStaticImageUrl(props.user.bannerUrl) })`,
};
diff --git a/packages/frontend/src/pages/user/index.files.vue b/packages/frontend/src/pages/user/index.files.vue
index 44e35e3479..804be43493 100644
--- a/packages/frontend/src/pages/user/index.files.vue
+++ b/packages/frontend/src/pages/user/index.files.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
import { i18n } from '@/i18n.js';
import MkNoteMediaGrid from '@/components/MkNoteMediaGrid.vue';
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index ba02559d68..9d8eb0da2b 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -4,41 +4,38 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkStickyContainer>
- <template #header><MkPageHeader v-model:tab="tab" :displayBackButton="true" :actions="headerActions" :tabs="headerTabs"/></template>
- <div>
- <div v-if="user">
- <MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <XHome v-if="tab === 'home'" key="home" :user="user" @unfoldFiles="() => { tab = 'files'; }"/>
- <MkSpacer v-else-if="tab === 'notes'" key="notes" :contentMax="800" style="padding-top: 0">
- <XTimeline :user="user"/>
- </MkSpacer>
- <XFiles v-else-if="tab === 'files'" :user="user"/>
- <XActivity v-else-if="tab === 'activity'" key="activity" :user="user"/>
- <XAchievements v-else-if="tab === 'achievements'" key="achievements" :user="user"/>
- <XReactions v-else-if="tab === 'reactions'" key="reactions" :user="user"/>
- <XClips v-else-if="tab === 'clips'" key="clips" :user="user"/>
- <XLists v-else-if="tab === 'lists'" key="lists" :user="user"/>
- <XPages v-else-if="tab === 'pages'" key="pages" :user="user"/>
- <XFlashs v-else-if="tab === 'flashs'" key="flashs" :user="user"/>
- <XGallery v-else-if="tab === 'gallery'" key="gallery" :user="user"/>
- <XRaw v-else-if="tab === 'raw'" key="raw" :user="user"/>
- </MkHorizontalSwipe>
- </div>
- <MkError v-else-if="error" @retry="fetchUser()"/>
- <MkLoading v-else/>
+<PageWithHeader v-model:tab="tab" :displayBackButton="true" :tabs="headerTabs" :actions="headerActions">
+ <div v-if="user">
+ <MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+ <XHome v-if="tab === 'home'" :user="user" @unfoldFiles="() => { tab = 'files'; }"/>
+ <MkSpacer v-else-if="tab === 'notes'" :contentMax="800" style="padding-top: 0">
+ <XTimeline :user="user"/>
+ </MkSpacer>
+ <XFiles v-else-if="tab === 'files'" :user="user"/>
+ <XActivity v-else-if="tab === 'activity'" :user="user"/>
+ <XAchievements v-else-if="tab === 'achievements'" :user="user"/>
+ <XReactions v-else-if="tab === 'reactions'" :user="user"/>
+ <XClips v-else-if="tab === 'clips'" :user="user"/>
+ <XLists v-else-if="tab === 'lists'" :user="user"/>
+ <XPages v-else-if="tab === 'pages'" :user="user"/>
+ <XFlashs v-else-if="tab === 'flashs'" :user="user"/>
+ <XGallery v-else-if="tab === 'gallery'" :user="user"/>
+ <XRaw v-else-if="tab === 'raw'" :user="user"/>
+ </MkHorizontalSwipe>
</div>
-</MkStickyContainer>
+ <MkError v-else-if="error" @retry="fetchUser()"/>
+ <MkLoading v-else/>
+</PageWithHeader>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { acct as getAcct } from '@/filters/user.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { serverContext, assertServerContext } from '@/server-context.js';
@@ -147,7 +144,7 @@ const headerTabs = computed(() => user.value ? [{
icon: 'ti ti-code',
}] : []);
-definePageMetadata(() => ({
+definePage(() => ({
title: i18n.ts.user,
icon: 'ti ti-user',
...user.value ? {
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index c5731bd2a9..b95f79f42d 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -4,31 +4,24 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div v-if="meta" class="rsqzvsbo">
- <MkFeaturedPhotos class="bg"/>
- <XTimeline class="tl"/>
- <div class="shape1"></div>
- <div class="shape2"></div>
- <div class="logo-wrapper">
- <div class="powered-by">Powered by</div>
- <img :src="misskeysvg" class="misskey"/>
+<div v-if="meta" :class="$style.root">
+ <MkFeaturedPhotos :class="$style.bg"/>
+ <XTimeline :class="$style.tl"/>
+ <div :class="$style.shape1"></div>
+ <div :class="$style.shape2"></div>
+ <div :class="$style.logoWrapper">
+ <div :class="$style.poweredBy">Powered by</div>
+ <img :src="misskeysvg" :class="$style.misskey"/>
</div>
- <div class="emojis">
- <MkEmoji :normal="true" :noStyle="true" emoji="👍"/>
- <MkEmoji :normal="true" :noStyle="true" emoji="❤"/>
- <MkEmoji :normal="true" :noStyle="true" emoji="😆"/>
- <MkEmoji :normal="true" :noStyle="true" emoji="🎉"/>
- <MkEmoji :normal="true" :noStyle="true" emoji="🍮"/>
- </div>
- <div class="contents">
+ <div :class="$style.contents">
<MkVisitorDashboard/>
</div>
- <div v-if="instances && instances.length > 0" class="federation">
+ <div v-if="instances && instances.length > 0" :class="$style.federation">
<MarqueeText :duration="40">
<MkA v-for="instance in instances" :key="instance.id" :class="$style.federationInstance" :to="`/instance-info/${instance.host}`" behavior="window">
<!--<MkInstanceCardMini :instance="instance"/>-->
- <img v-if="instance.iconUrl" class="icon" :src="getInstanceIcon(instance)" alt=""/>
- <span class="name _monospace">{{ instance.host }}</span>
+ <img v-if="instance.iconUrl" :class="$style.federationInstanceIcon" :src="getInstanceIcon(instance)" alt=""/>
+ <span class="_monospace">{{ instance.host }}</span>
</MkA>
</MarqueeText>
</div>
@@ -42,9 +35,9 @@ import XTimeline from './welcome.timeline.vue';
import MarqueeText from '@/components/MkMarquee.vue';
import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
import misskeysvg from '/client-assets/sharkey.svg';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
-import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
+import { getProxiedImageUrl } from '@/utility/media-proxy.js';
import { instance as meta } from '@/instance.js';
const instances = ref<Misskey.entities.FederationInstance[]>();
@@ -66,122 +59,111 @@ misskeyApiGet('federation/instances', {
});
</script>
-<style lang="scss" scoped>
-.rsqzvsbo {
- > .bg {
- position: fixed;
- top: 0;
- right: 0;
- width: 80vw; // 100%からshapeの幅を引いている
- height: 100vh;
- }
+<style lang="scss" module>
+.root {
+ height: 100cqh;
+ overflow: auto;
+ overscroll-behavior: contain;
+}
- > .tl {
- position: fixed;
- top: 0;
- bottom: 0;
- right: 64px;
- margin: auto;
- padding: 128px 0;
- width: 500px;
- height: calc(100% - 256px);
- overflow: hidden;
- -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
- mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
+.bg {
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 80vw; // 100%からshapeの幅を引いている
+ height: 100vh;
+}
- @media (max-width: 1200px) {
- display: none;
- }
- }
+.tl {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ right: 64px;
+ margin: auto;
+ padding: 128px 0;
+ width: 500px;
+ height: calc(100% - 256px);
+ overflow: hidden;
+ -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
+ mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
- > .shape1 {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background: var(--MI_THEME-accent);
- clip-path: polygon(0% 0%, 45% 0%, 20% 100%, 0% 100%);
+ @media (max-width: 1200px) {
+ display: none;
}
- > .shape2 {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background: var(--MI_THEME-accent);
- clip-path: polygon(0% 0%, 25% 0%, 35% 100%, 0% 100%);
- opacity: 0.5;
- }
-
- > .logo-wrapper {
- position: fixed;
- top: 36px;
- left: 36px;
- flex: auto;
- color: #fff;
- user-select: none;
- pointer-events: none;
+}
- > .powered-by {
- margin-bottom: 2px;
- }
+.shape1 {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background: var(--MI_THEME-accent);
+ clip-path: polygon(0% 0%, 45% 0%, 20% 100%, 0% 100%);
+}
+.shape2 {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background: var(--MI_THEME-accent);
+ clip-path: polygon(0% 0%, 25% 0%, 35% 100%, 0% 100%);
+ opacity: 0.5;
+}
- > .misskey {
- width: 140px;
- @media (max-width: 450px) {
- width: 130px;
- }
- }
- }
+.logoWrapper {
+ position: fixed;
+ top: 36px;
+ left: 36px;
+ flex: auto;
+ color: #fff;
+ user-select: none;
+ pointer-events: none;
+}
- > .emojis {
- position: fixed;
- bottom: 32px;
- left: 35px;
+.poweredBy {
+ margin-bottom: 2px;
+}
- > * {
- margin-right: 8px;
- }
+.misskey {
+ width: 120px;
- @media (max-width: 1200px) {
- display: none;
- }
+ @media (max-width: 450px) {
+ width: 100px;
}
+}
- > .contents {
- position: relative;
- width: min(430px, calc(100% - 32px));
- margin-left: 128px;
- padding: 100px 0 100px 0;
+.contents {
+ position: relative;
+ width: min(430px, calc(100% - 32px));
+ margin-left: 128px;
+ padding: 100px 0 100px 0;
- @media (max-width: 1200px) {
- margin: auto;
- }
+ @media (max-width: 1200px) {
+ margin: auto;
}
+}
- > .federation {
- position: fixed;
- bottom: 16px;
- left: 0;
- right: 0;
- margin: auto;
- 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;
+.federation {
+ position: fixed;
+ bottom: 16px;
+ left: 0;
+ right: 0;
+ margin: auto;
+ 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;
- @media (max-width: 900px) {
- display: none;
- }
+ @media (max-width: 900px) {
+ display: none;
}
}
-</style>
-<style lang="scss" module>
.federationInstance {
display: inline-flex;
align-items: center;
@@ -190,13 +172,13 @@ misskeyApiGet('federation/instances', {
margin: 0 10px 0 0;
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(--MI-radius-ellipse);
- }
+.federationInstanceIcon {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ margin-right: 5px;
+ 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 58e815d89c..da14f180d5 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div>
- <MkAnimBg style="position: fixed; top: 0;"/>
+<PageWithAnimBg>
<div :class="$style.formContainer">
<form :class="$style.form" class="_panel" @submit.prevent="submit()">
<div :class="$style.title">
@@ -35,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</form>
</div>
-</div>
+</PageWithAnimBg>
</template>
<script lang="ts" setup>
@@ -44,10 +43,9 @@ import { host, version } from '@@/js/config.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { login } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import MkAnimBg from '@/components/MkAnimBg.vue';
+import { login } from '@/accounts.js';
const username = ref('');
const password = ref('');
diff --git a/packages/frontend/src/pages/welcome.timeline.note.vue b/packages/frontend/src/pages/welcome.timeline.note.vue
index 460a225f23..5ca487a70b 100644
--- a/packages/frontend/src/pages/welcome.timeline.note.vue
+++ b/packages/frontend/src/pages/welcome.timeline.note.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, onUpdated, onMounted } from 'vue';
+import { ref, useTemplateRef, onUpdated, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
import MkMediaList from '@/components/MkMediaList.vue';
@@ -45,7 +45,7 @@ defineProps<{
note: Misskey.entities.Note;
}>();
-const noteTextEl = shallowRef<HTMLDivElement>();
+const noteTextEl = useTemplateRef('noteTextEl');
const shouldCollapse = ref(false);
const showContent = ref(false);
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index 2964b15164..058be6c099 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -21,15 +21,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { onUpdated, ref, shallowRef } from 'vue';
-import XNote from '@/pages/welcome.timeline.note.vue';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import { onUpdated, ref, useTemplateRef } from 'vue';
import { getScrollContainer } from '@@/js/scroll.js';
+import XNote from '@/pages/welcome.timeline.note.vue';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
const notes = ref<Misskey.entities.Note[]>([]);
const isScrolling = ref(false);
const scrollState = ref<null | 'intro' | 'loop'>(null);
-const notesMainContainerEl = shallowRef<HTMLElement>();
+const notesMainContainerEl = useTemplateRef('notesMainContainerEl');
misskeyApiGet('notes/featured').then(_notes => {
notes.value = _notes.filter(n => n.cw == null);
diff --git a/packages/frontend/src/pages/welcome.vue b/packages/frontend/src/pages/welcome.vue
index 38d257506c..d3e571c053 100644
--- a/packages/frontend/src/pages/welcome.vue
+++ b/packages/frontend/src/pages/welcome.vue
@@ -16,7 +16,7 @@ import * as Misskey from 'misskey-js';
import XSetup from './welcome.setup.vue';
import XEntrance from './welcome.entrance.a.vue';
import { instanceName } from '@@/js/config.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { definePage } from '@/page.js';
import { fetchInstance } from '@/instance.js';
const instance = ref<Misskey.entities.MetaDetailed | null>(null);
@@ -29,7 +29,7 @@ const headerActions = computed(() => []);
const headerTabs = computed(() => []);
-definePageMetadata(() => ({
+definePage(() => ({
title: instanceName,
icon: null,
}));
diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts
index 27bb34da36..581ab35341 100644
--- a/packages/frontend/src/plugin.ts
+++ b/packages/frontend/src/plugin.ts
@@ -3,179 +3,431 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { ref } from 'vue';
+import { ref, defineAsyncComponent } from 'vue';
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
-import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
-import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js';
-import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
+import { compareVersions } from 'compare-versions';
+import { v4 as uuid } from 'uuid';
+import * as Misskey from 'misskey-js';
+import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
+import { store } from '@/store.js';
+import * as os from '@/os.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { i18n } from '@/i18n.js';
+import { prefer } from '@/preferences.js';
+import { warningExternalWebsite } from '@/utility/warning-external-website.js';
+
+export type Plugin = {
+ installId: string;
+ name: string;
+ active: boolean;
+ config?: Record<string, { default: any }>;
+ configData: Record<string, any>;
+ src: string | null;
+ version: string;
+ author?: string;
+ description?: string;
+ permissions?: string[];
+};
+
+export type AiScriptPluginMeta = {
+ name: string;
+ version: string;
+ author: string;
+ description?: string;
+ permissions?: string[];
+ config?: Record<string, any>;
+};
const parser = new Parser();
-const pluginContexts = new Map<string, Interpreter>();
-export const pluginLogs = ref(new Map<string, string[]>());
-export async function install(plugin: Plugin): Promise<void> {
+export function isSupportedAiScriptVersion(version: string): boolean {
+ try {
+ return (compareVersions(version, '0.12.0') >= 0);
+ } catch (err) {
+ return false;
+ }
+}
+
+export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta> {
+ if (!code) {
+ throw new Error('code is required');
+ }
+
+ const lv = utils.getLangVersion(code);
+ if (lv == null) {
+ throw new Error('No language version annotation found');
+ } else if (!isSupportedAiScriptVersion(lv)) {
+ throw new Error(`Aiscript version '${lv}' is not supported`);
+ }
+
+ let ast;
+ try {
+ ast = parser.parse(code);
+ } catch (err) {
+ throw new Error('Aiscript syntax error');
+ }
+
+ const meta = Interpreter.collectMetadata(ast);
+ if (meta == null) {
+ throw new Error('Meta block not found');
+ }
+
+ const metadata = meta.get(null);
+ if (metadata == null) {
+ throw new Error('Metadata not found');
+ }
+
+ const { name, version, author, description, permissions, config } = metadata;
+ if (name == null || version == null || author == null) {
+ throw new Error('Required property not found');
+ }
+
+ return {
+ name,
+ version,
+ author,
+ description,
+ permissions,
+ config,
+ };
+}
+
+export async function authorizePlugin(plugin: Plugin) {
+ if (plugin.permissions == null || plugin.permissions.length === 0) return;
+ if (Object.hasOwn(store.s.pluginTokens, plugin.installId)) return;
+
+ const token = await new Promise<string>((res, rej) => {
+ const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {
+ title: i18n.ts.tokenRequested,
+ information: i18n.ts.pluginTokenRequestedDescription,
+ initialName: plugin.name,
+ initialPermissions: plugin.permissions,
+ }, {
+ done: async result => {
+ const { name, permissions } = result;
+ const { token } = await misskeyApi('miauth/gen-token', {
+ session: null,
+ name: name,
+ permission: permissions,
+ });
+ res(token);
+ },
+ closed: () => dispose(),
+ });
+ });
+
+ store.set('pluginTokens', {
+ ...store.s.pluginTokens,
+ [plugin.installId]: token,
+ });
+}
+
+export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
+ if (!code) return;
+
+ let realMeta: AiScriptPluginMeta;
+ if (!meta) {
+ realMeta = await parsePluginMeta(code);
+ } else {
+ realMeta = meta;
+ }
+
+ if (prefer.s.plugins.some(x => x.name === realMeta.name)) {
+ throw new Error('Plugin already installed');
+ }
+
+ const installId = uuid();
+
+ const plugin = {
+ ...realMeta,
+ installId,
+ active: true,
+ configData: {},
+ src: code,
+ };
+
+ prefer.commit('plugins', prefer.s.plugins.concat(plugin));
+
+ await authorizePlugin(plugin);
+
+ await launchPlugin(installId);
+}
+
+export async function uninstallPlugin(plugin: Plugin) {
+ abortPlugin(plugin);
+ prefer.commit('plugins', prefer.s.plugins.filter(x => x.installId !== plugin.installId));
+ if (Object.hasOwn(store.s.pluginTokens, plugin.installId)) {
+ await os.apiWithDialog('i/revoke-token', {
+ token: store.s.pluginTokens[plugin.installId],
+ });
+ const pluginTokens = { ...store.s.pluginTokens };
+ delete pluginTokens[plugin.installId];
+ store.set('pluginTokens', pluginTokens);
+ }
+}
+
+const pluginContexts = new Map<Plugin['installId'], Interpreter>();
+
+export const pluginLogs = ref(new Map<Plugin['installId'], {
+ at: number;
+ message: string;
+ isSystem?: boolean;
+ isError?: boolean;
+}[]>());
+
+type HandlerDef = {
+ post_form_action: {
+ title: string,
+ handler: <T>(form: T, update: (key: unknown, value: unknown) => void) => void;
+ };
+ user_action: {
+ title: string,
+ handler: (user: Misskey.entities.UserDetailed) => void;
+ };
+ note_action: {
+ title: string,
+ handler: (note: Misskey.entities.Note) => void;
+ };
+ note_view_interruptor: {
+ handler: (note: Misskey.entities.Note) => unknown;
+ };
+ note_post_interruptor: {
+ handler: (note: FIXME) => unknown;
+ };
+ page_view_interruptor: {
+ handler: (page: Misskey.entities.Page) => unknown;
+ };
+};
+
+type PluginHandler<K extends keyof HandlerDef> = {
+ pluginInstallId: string;
+ type: K;
+ ctx: HandlerDef[K];
+};
+
+let pluginHandlers: PluginHandler<keyof HandlerDef>[] = [];
+
+function addPluginHandler<K extends keyof HandlerDef>(installId: Plugin['installId'], type: K, ctx: PluginHandler<K>['ctx']) {
+ pluginLogs.value.get(installId)!.push({
+ at: Date.now(),
+ isSystem: true,
+ message: `Handler registered: ${type}`,
+ });
+ pluginHandlers.push({ pluginInstallId: installId, type, ctx });
+}
+
+export function launchPlugins() {
+ for (const plugin of prefer.s.plugins) {
+ if (plugin.active) {
+ launchPlugin(plugin.installId);
+ }
+ }
+}
+
+async function launchPlugin(id: Plugin['installId']): Promise<void> {
+ const plugin = prefer.s.plugins.find(x => x.installId === id);
+ if (!plugin) return;
+
// 後方互換性のため
if (plugin.src == null) return;
+ pluginLogs.value.set(plugin.installId, []);
+
+ function systemLog(message: string, isError = false): void {
+ pluginLogs.value.get(plugin.installId)?.push({
+ at: Date.now(),
+ isSystem: true,
+ message,
+ isError,
+ });
+ }
+
+ systemLog('Starting plugin...');
+
+ await authorizePlugin(plugin);
+
const aiscript = new Interpreter(createPluginEnv({
plugin: plugin,
- storageKey: 'plugins:' + plugin.id,
+ storageKey: 'plugins:' + plugin.installId,
}), {
in: aiScriptReadline,
out: (value): void => {
- console.log(value);
- pluginLogs.value.get(plugin.id).push(utils.reprValue(value));
+ pluginLogs.value.get(plugin.installId)!.push({
+ at: Date.now(),
+ message: utils.reprValue(value),
+ });
},
log: (): void => {
},
err: (err): void => {
- pluginLogs.value.get(plugin.id).push(`${err}`);
+ pluginLogs.value.get(plugin.installId)!.push({
+ at: Date.now(),
+ message: `${err}`,
+ isError: true,
+ });
throw err; // install時のtry-catchに反応させる
},
});
- initPlugin({ plugin, aiscript });
+ pluginContexts.set(plugin.installId, aiscript);
aiscript.exec(parser.parse(plugin.src)).then(
() => {
console.info('Plugin installed:', plugin.name, 'v' + plugin.version);
+ systemLog('Plugin started');
},
(err) => {
console.error('Plugin install failed:', plugin.name, 'v' + plugin.version);
+ systemLog(`${err}`, true);
throw err;
},
);
}
+export function abortPlugin(plugin: Plugin): void {
+ const pluginContext = pluginContexts.get(plugin.installId);
+ if (!pluginContext) return;
+
+ pluginContext.abort();
+ pluginContexts.delete(plugin.installId);
+ pluginLogs.value.delete(plugin.installId);
+ pluginHandlers = pluginHandlers.filter(x => x.pluginInstallId !== plugin.installId);
+}
+
+export function reloadPlugin(plugin: Plugin): void {
+ abortPlugin(plugin);
+ launchPlugin(plugin.installId);
+}
+
+export async function configPlugin(plugin: Plugin) {
+ if (plugin.config == null) {
+ throw new Error('This plugin does not have a config');
+ }
+
+ const config = plugin.config;
+ for (const key in plugin.configData) {
+ config[key].default = plugin.configData[key];
+ }
+
+ const { canceled, result } = await os.form(plugin.name, config);
+ if (canceled) return;
+
+ prefer.commit('plugins', prefer.s.plugins.map(x => x.installId === plugin.installId ? { ...x, configData: result } : x));
+
+ reloadPlugin(plugin);
+}
+
+export function changePluginActive(plugin: Plugin, active: boolean) {
+ prefer.commit('plugins', prefer.s.plugins.map(x => x.installId === plugin.installId ? { ...x, active } : x));
+
+ if (active) {
+ launchPlugin(plugin.installId);
+ } else {
+ abortPlugin(plugin);
+ }
+}
+
function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Record<string, values.Value> {
+ const id = opts.plugin.installId;
+
const config = new Map<string, values.Value>();
for (const [k, v] of Object.entries(opts.plugin.config ?? {})) {
config.set(k, utils.jsToVal(typeof opts.plugin.configData[k] !== 'undefined' ? opts.plugin.configData[k] : v.default));
}
- return {
- ...createAiScriptEnv({ ...opts, token: opts.plugin.token }),
- //#region Deprecated
- 'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => {
- utils.assertString(title);
- registerPostFormAction({ pluginId: opts.plugin.id, title: title.value, handler });
- }),
- 'Mk:register_user_action': values.FN_NATIVE(([title, handler]) => {
- utils.assertString(title);
- registerUserAction({ pluginId: opts.plugin.id, title: title.value, handler });
- }),
- 'Mk:register_note_action': values.FN_NATIVE(([title, handler]) => {
- utils.assertString(title);
- registerNoteAction({ pluginId: opts.plugin.id, title: title.value, handler });
- }),
- //#endregion
- 'Plugin:register_post_form_action': values.FN_NATIVE(([title, handler]) => {
+ function withContext<T>(fn: (ctx: Interpreter) => T): T {
+ const ctx = pluginContexts.get(id);
+ if (!ctx) throw new Error('Plugin context not found');
+ return fn(ctx);
+ }
+
+ const env: Record<string, values.Value> = {
+ ...createAiScriptEnv({ ...opts, token: store.s.pluginTokens[id] }),
+
+ 'Plugin:register:post_form_action': values.FN_NATIVE(([title, handler]) => {
utils.assertString(title);
- registerPostFormAction({ pluginId: opts.plugin.id, title: title.value, handler });
+ utils.assertFunction(handler);
+ addPluginHandler(id, 'post_form_action', {
+ title: title.value,
+ handler: withContext(ctx => (form, update) => {
+ ctx.execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => {
+ if (!key || !value) {
+ return;
+ }
+ update(utils.valToJs(key), utils.valToJs(value));
+ })]);
+ }),
+ });
}),
- 'Plugin:register_user_action': values.FN_NATIVE(([title, handler]) => {
+
+ 'Plugin:register:user_action': values.FN_NATIVE(([title, handler]) => {
utils.assertString(title);
- registerUserAction({ pluginId: opts.plugin.id, title: title.value, handler });
+ utils.assertFunction(handler);
+ addPluginHandler(id, 'user_action', {
+ title: title.value,
+ handler: withContext(ctx => (user) => {
+ ctx.execFn(handler, [utils.jsToVal(user)]);
+ }),
+ });
}),
- 'Plugin:register_note_action': values.FN_NATIVE(([title, handler]) => {
+
+ 'Plugin:register:note_action': values.FN_NATIVE(([title, handler]) => {
utils.assertString(title);
- registerNoteAction({ pluginId: opts.plugin.id, title: title.value, handler });
+ utils.assertFunction(handler);
+ addPluginHandler(id, 'note_action', {
+ title: title.value,
+ handler: withContext(ctx => (note) => {
+ ctx.execFn(handler, [utils.jsToVal(note)]);
+ }),
+ });
}),
- 'Plugin:register_note_view_interruptor': values.FN_NATIVE(([handler]) => {
- registerNoteViewInterruptor({ pluginId: opts.plugin.id, handler });
+
+ 'Plugin:register:note_view_interruptor': values.FN_NATIVE(([handler]) => {
+ utils.assertFunction(handler);
+ addPluginHandler(id, 'note_view_interruptor', {
+ handler: withContext(ctx => async (note) => {
+ return utils.valToJs(await ctx.execFn(handler, [utils.jsToVal(note)]));
+ }),
+ });
}),
- 'Plugin:register_note_post_interruptor': values.FN_NATIVE(([handler]) => {
- registerNotePostInterruptor({ pluginId: opts.plugin.id, handler });
+
+ 'Plugin:register:note_post_interruptor': values.FN_NATIVE(([handler]) => {
+ utils.assertFunction(handler);
+ addPluginHandler(id, 'note_post_interruptor', {
+ handler: withContext(ctx => async (note) => {
+ return utils.valToJs(await ctx.execFn(handler, [utils.jsToVal(note)]));
+ }),
+ });
}),
- 'Plugin:register_page_view_interruptor': values.FN_NATIVE(([handler]) => {
- registerPageViewInterruptor({ pluginId: opts.plugin.id, handler });
+
+ 'Plugin:register:page_view_interruptor': values.FN_NATIVE(([handler]) => {
+ utils.assertFunction(handler);
+ addPluginHandler(id, 'page_view_interruptor', {
+ handler: withContext(ctx => async (page) => {
+ return utils.valToJs(await ctx.execFn(handler, [utils.jsToVal(page)]));
+ }),
+ });
}),
+
'Plugin:open_url': values.FN_NATIVE(([url]) => {
utils.assertString(url);
warningExternalWebsite(url.value);
}),
+
'Plugin:config': values.OBJ(config),
};
-}
-
-function initPlugin({ plugin, aiscript }): void {
- pluginContexts.set(plugin.id, aiscript);
- pluginLogs.value.set(plugin.id, []);
-}
-
-function registerPostFormAction({ pluginId, title, handler }): void {
- postFormActions.push({
- title, handler: (form, update) => {
- const pluginContext = pluginContexts.get(pluginId);
- if (!pluginContext) {
- return;
- }
- pluginContext.execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => {
- if (!key || !value) {
- return;
- }
- update(utils.valToJs(key), utils.valToJs(value));
- })]);
- },
- });
-}
-
-function registerUserAction({ pluginId, title, handler }): void {
- userActions.push({
- title, handler: (user) => {
- const pluginContext = pluginContexts.get(pluginId);
- if (!pluginContext) {
- return;
- }
- pluginContext.execFn(handler, [utils.jsToVal(user)]);
- },
- });
-}
-
-function registerNoteAction({ pluginId, title, handler }): void {
- noteActions.push({
- title, handler: (note) => {
- const pluginContext = pluginContexts.get(pluginId);
- if (!pluginContext) {
- return;
- }
- pluginContext.execFn(handler, [utils.jsToVal(note)]);
- },
- });
-}
-function registerNoteViewInterruptor({ pluginId, handler }): void {
- noteViewInterruptors.push({
- handler: async (note) => {
- const pluginContext = pluginContexts.get(pluginId);
- if (!pluginContext) {
- return;
- }
- return utils.valToJs(await pluginContext.execFn(handler, [utils.jsToVal(note)]));
- },
- });
-}
+ // 後方互換性のため
+ env['Plugin:register_post_form_action'] = env['Plugin:register:post_form_action'];
+ env['Plugin:register_user_action'] = env['Plugin:register:user_action'];
+ env['Plugin:register_note_action'] = env['Plugin:register:note_action'];
+ env['Plugin:register_note_view_interruptor'] = env['Plugin:register:note_view_interruptor'];
+ env['Plugin:register_note_post_interruptor'] = env['Plugin:register:note_post_interruptor'];
+ env['Plugin:register_page_view_interruptor'] = env['Plugin:register:page_view_interruptor'];
-function registerNotePostInterruptor({ pluginId, handler }): void {
- notePostInterruptors.push({
- handler: async (note) => {
- const pluginContext = pluginContexts.get(pluginId);
- if (!pluginContext) {
- return;
- }
- return utils.valToJs(await pluginContext.execFn(handler, [utils.jsToVal(note)]));
- },
- });
+ return env;
}
-function registerPageViewInterruptor({ pluginId, handler }): void {
- pageViewInterruptors.push({
- handler: async (page) => {
- const pluginContext = pluginContexts.get(pluginId);
- if (!pluginContext) {
- return;
- }
- return utils.valToJs(await pluginContext.execFn(handler, [utils.jsToVal(page)]));
- },
- });
+export function getPluginHandlers<K extends keyof HandlerDef>(type: K): HandlerDef[K][] {
+ return pluginHandlers.filter((x): x is PluginHandler<K> => x.type === type).map(x => x.ctx);
}
diff --git a/packages/frontend/src/pref-migrate.ts b/packages/frontend/src/pref-migrate.ts
new file mode 100644
index 0000000000..beeaac8383
--- /dev/null
+++ b/packages/frontend/src/pref-migrate.ts
@@ -0,0 +1,142 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { v4 as uuid } from 'uuid';
+import type { DeckProfile } from '@/deck.js';
+import { ColdDeviceStorage, store } from '@/store.js';
+import { prefer } from '@/preferences.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { deckStore } from '@/ui/deck/deck-store.js';
+import { unisonReload } from '@/utility/unison-reload.js';
+
+// TODO: そのうち消す
+export function migrateOldSettings() {
+ store.loaded.then(async () => {
+ const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []);
+ if (themes.length > 0) {
+ prefer.commit('themes', themes);
+ }
+
+ const plugins = ColdDeviceStorage.get('plugins');
+ prefer.commit('plugins', plugins.map(p => ({
+ ...p,
+ installId: (p as any).id,
+ id: undefined,
+ })));
+
+ prefer.commit('deck.profile', deckStore.s.profile);
+ misskeyApi('i/registry/keys', {
+ scope: ['client', 'deck', 'profiles'],
+ }).then(async keys => {
+ const profiles: DeckProfile[] = [];
+ for (const key of keys) {
+ const deck = await misskeyApi('i/registry/get', {
+ scope: ['client', 'deck', 'profiles'],
+ key: key,
+ });
+ profiles.push({
+ id: uuid(),
+ name: key,
+ columns: deck.columns,
+ layout: deck.layout,
+ });
+ }
+ prefer.commit('deck.profiles', profiles);
+ });
+
+ prefer.commit('lightTheme', ColdDeviceStorage.get('lightTheme'));
+ prefer.commit('darkTheme', ColdDeviceStorage.get('darkTheme'));
+ prefer.commit('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode'));
+ prefer.commit('emojiPalettes', [{
+ id: 'reactions',
+ name: '',
+ emojis: store.s.reactions,
+ }, {
+ id: 'pinnedEmojis',
+ name: '',
+ emojis: store.s.pinnedEmojis,
+ }]);
+ prefer.commit('emojiPaletteForMain', 'pinnedEmojis');
+ prefer.commit('emojiPaletteForReaction', 'reactions');
+ prefer.commit('overridedDeviceKind', store.s.overridedDeviceKind);
+ prefer.commit('widgets', store.s.widgets);
+ prefer.commit('keepCw', store.s.keepCw);
+ prefer.commit('collapseRenotes', store.s.collapseRenotes);
+ prefer.commit('rememberNoteVisibility', store.s.rememberNoteVisibility);
+ prefer.commit('uploadFolder', store.s.uploadFolder);
+ prefer.commit('keepOriginalUploading', store.s.keepOriginalUploading);
+ prefer.commit('menu', store.s.menu);
+ prefer.commit('statusbars', store.s.statusbars);
+ prefer.commit('pinnedUserLists', store.s.pinnedUserLists);
+ prefer.commit('serverDisconnectedBehavior', store.s.serverDisconnectedBehavior);
+ prefer.commit('nsfw', store.s.nsfw);
+ prefer.commit('highlightSensitiveMedia', store.s.highlightSensitiveMedia);
+ prefer.commit('animation', store.s.animation);
+ prefer.commit('animatedMfm', store.s.animatedMfm);
+ prefer.commit('advancedMfm', store.s.advancedMfm);
+ prefer.commit('showReactionsCount', store.s.showReactionsCount);
+ prefer.commit('enableQuickAddMfmFunction', store.s.enableQuickAddMfmFunction);
+ prefer.commit('loadRawImages', store.s.loadRawImages);
+ prefer.commit('imageNewTab', store.s.imageNewTab);
+ prefer.commit('disableShowingAnimatedImages', store.s.disableShowingAnimatedImages);
+ prefer.commit('emojiStyle', store.s.emojiStyle);
+ prefer.commit('menuStyle', store.s.menuStyle);
+ prefer.commit('useBlurEffectForModal', store.s.useBlurEffectForModal);
+ prefer.commit('useBlurEffect', store.s.useBlurEffect);
+ prefer.commit('showFixedPostForm', store.s.showFixedPostForm);
+ prefer.commit('showFixedPostFormInChannel', store.s.showFixedPostFormInChannel);
+ prefer.commit('enableInfiniteScroll', store.s.enableInfiniteScroll);
+ prefer.commit('useReactionPickerForContextMenu', store.s.useReactionPickerForContextMenu);
+ prefer.commit('showGapBetweenNotesInTimeline', store.s.showGapBetweenNotesInTimeline);
+ prefer.commit('instanceTicker', store.s.instanceTicker);
+ prefer.commit('emojiPickerScale', store.s.emojiPickerScale);
+ prefer.commit('emojiPickerWidth', store.s.emojiPickerWidth);
+ prefer.commit('emojiPickerHeight', store.s.emojiPickerHeight);
+ prefer.commit('emojiPickerStyle', store.s.emojiPickerStyle);
+ prefer.commit('reportError', store.s.reportError);
+ prefer.commit('squareAvatars', store.s.squareAvatars);
+ prefer.commit('showAvatarDecorations', store.s.showAvatarDecorations);
+ prefer.commit('numberOfPageCache', store.s.numberOfPageCache);
+ prefer.commit('showNoteActionsOnlyHover', store.s.showNoteActionsOnlyHover);
+ prefer.commit('showClipButtonInNoteFooter', store.s.showClipButtonInNoteFooter);
+ prefer.commit('reactionsDisplaySize', store.s.reactionsDisplaySize);
+ prefer.commit('limitWidthOfReaction', store.s.limitWidthOfReaction);
+ prefer.commit('forceShowAds', store.s.forceShowAds);
+ prefer.commit('aiChanMode', store.s.aiChanMode);
+ prefer.commit('devMode', store.s.devMode);
+ prefer.commit('mediaListWithOneImageAppearance', store.s.mediaListWithOneImageAppearance);
+ prefer.commit('notificationPosition', store.s.notificationPosition);
+ prefer.commit('notificationStackAxis', store.s.notificationStackAxis);
+ prefer.commit('enableCondensedLine', store.s.enableCondensedLine);
+ prefer.commit('keepScreenOn', store.s.keepScreenOn);
+ prefer.commit('disableStreamingTimeline', store.s.disableStreamingTimeline);
+ prefer.commit('useGroupedNotifications', store.s.useGroupedNotifications);
+ prefer.commit('dataSaver', store.s.dataSaver);
+ prefer.commit('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect);
+ prefer.commit('enableHorizontalSwipe', store.s.enableHorizontalSwipe);
+ prefer.commit('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer);
+ prefer.commit('keepOriginalFilename', store.s.keepOriginalFilename);
+ prefer.commit('alwaysConfirmFollow', store.s.alwaysConfirmFollow);
+ prefer.commit('confirmWhenRevealingSensitiveMedia', store.s.confirmWhenRevealingSensitiveMedia);
+ prefer.commit('contextMenu', store.s.contextMenu);
+ prefer.commit('skipNoteRender', store.s.skipNoteRender);
+ prefer.commit('showSoftWordMutedWord', store.s.showSoftWordMutedWord);
+ prefer.commit('confirmOnReact', store.s.confirmOnReact);
+ prefer.commit('defaultFollowWithReplies', store.s.defaultWithReplies);
+ prefer.commit('sound.masterVolume', store.s.sound_masterVolume);
+ prefer.commit('sound.notUseSound', store.s.sound_notUseSound);
+ prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive);
+ prefer.commit('sound.on.note', store.s.sound_note as any);
+ prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any);
+ prefer.commit('sound.on.notification', store.s.sound_notification as any);
+ prefer.commit('sound.on.reaction', store.s.sound_reaction as any);
+ prefer.commit('defaultNoteVisibility', store.s.defaultNoteVisibility);
+ prefer.commit('defaultNoteLocalOnly', store.s.defaultNoteLocalOnly);
+
+ window.setTimeout(() => {
+ unisonReload();
+ }, 5000);
+ });
+}
diff --git a/packages/frontend/src/preferences.ts b/packages/frontend/src/preferences.ts
new file mode 100644
index 0000000000..73c89e23af
--- /dev/null
+++ b/packages/frontend/src/preferences.ts
@@ -0,0 +1,150 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { PreferencesProfile, StorageProvider } from '@/preferences/manager.js';
+import { cloudBackup } from '@/preferences/utility.js';
+import { miLocalStorage } from '@/local-storage.js';
+import { isSameScope, PreferencesManager } from '@/preferences/manager.js';
+import { store } from '@/store.js';
+import { $i } from '@/i.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { TAB_ID } from '@/tab-id.js';
+
+function createPrefManager(storageProvider: StorageProvider) {
+ let profile: PreferencesProfile;
+
+ const savedProfileRaw = miLocalStorage.getItem('preferences');
+ if (savedProfileRaw == null) {
+ profile = PreferencesManager.newProfile();
+ miLocalStorage.setItem('preferences', JSON.stringify(profile));
+ } else {
+ profile = PreferencesManager.normalizeProfile(JSON.parse(savedProfileRaw));
+ }
+
+ return new PreferencesManager(profile, storageProvider);
+}
+
+const syncGroup = 'default';
+
+const storageProvider: StorageProvider = {
+ save: (ctx) => {
+ miLocalStorage.setItem('preferences', JSON.stringify(ctx.profile));
+ miLocalStorage.setItem('latestPreferencesUpdate', `${TAB_ID}/${Date.now()}`);
+ },
+
+ cloudGet: async (ctx) => {
+ // TODO: この取得方法だとアカウントが変わると保存場所も変わってしまうので改修する
+ // 例えば複数アカウントある場合でも設定値を保存するための「プライマリアカウント」を設定できるようにするとか
+ try {
+ const cloudData = await misskeyApi('i/registry/get', {
+ scope: ['client', 'preferences', 'sync'],
+ key: syncGroup + ':' + ctx.key,
+ }) as [any, any][];
+ const target = cloudData.find(([scope]) => isSameScope(scope, ctx.scope));
+ if (target == null) return null;
+ return {
+ value: target[1],
+ };
+ } catch (err: any) {
+ if (err.code === 'NO_SUCH_KEY') { // TODO: いちいちエラーキャッチするのは面倒なのでキーが無くてもエラーにならない maybe-get のようなエンドポイントをバックエンドに実装する
+ return null;
+ } else {
+ throw err;
+ }
+ }
+ },
+
+ cloudSet: async (ctx) => {
+ let cloudData: [any, any][] = [];
+ try {
+ cloudData = await misskeyApi('i/registry/get', {
+ scope: ['client', 'preferences', 'sync'],
+ key: syncGroup + ':' + ctx.key,
+ }) as [any, any][];
+ } catch (err: any) {
+ if (err.code === 'NO_SUCH_KEY') { // TODO: いちいちエラーキャッチするのは面倒なのでキーが無くてもエラーにならない maybe-get のようなエンドポイントをバックエンドに実装する
+ cloudData = [];
+ } else {
+ throw err;
+ }
+ }
+
+ const i = cloudData.findIndex(([scope]) => isSameScope(scope, ctx.scope));
+
+ if (i === -1) {
+ cloudData.push([ctx.scope, ctx.value]);
+ } else {
+ cloudData[i] = [ctx.scope, ctx.value];
+ }
+
+ await misskeyApi('i/registry/set', {
+ scope: ['client', 'preferences', 'sync'],
+ key: syncGroup + ':' + ctx.key,
+ value: cloudData,
+ });
+ },
+
+ cloudGets: async (ctx) => {
+ // TODO: 値の取得を1つのリクエストで済ませたい(バックエンド側でAPIの新設が必要)
+ const fetchings = ctx.needs.map(need => storageProvider.cloudGet(need).then(res => [need.key, res] as const));
+ const cloudDatas = await Promise.all(fetchings);
+
+ const res = {} as Partial<Record<string, any>>;
+ for (const cloudData of cloudDatas) {
+ if (cloudData[1] != null) {
+ res[cloudData[0]] = cloudData[1].value;
+ }
+ }
+
+ return res;
+ },
+};
+
+export const prefer = createPrefManager(storageProvider);
+
+let latestSyncedAt = Date.now();
+
+function syncBetweenTabs() {
+ const latest = miLocalStorage.getItem('latestPreferencesUpdate');
+ if (latest == null) return;
+
+ const latestTab = latest.split('/')[0];
+ const latestAt = parseInt(latest.split('/')[1]);
+
+ if (latestTab === TAB_ID) return;
+ if (latestAt <= latestSyncedAt) return;
+
+ prefer.rewriteProfile(PreferencesManager.normalizeProfile(JSON.parse(miLocalStorage.getItem('preferences')!)));
+
+ latestSyncedAt = Date.now();
+
+ if (_DEV_) console.log('prefer:synced');
+}
+
+window.setInterval(syncBetweenTabs, 5000);
+
+window.document.addEventListener('visibilitychange', () => {
+ if (window.document.visibilityState === 'visible') {
+ syncBetweenTabs();
+ }
+});
+
+let latestBackupAt = 0;
+
+window.setInterval(() => {
+ if ($i == null) return;
+ if (!store.s.enablePreferencesAutoCloudBackup) return;
+ if (window.document.visibilityState !== 'visible') return; // 同期されていない古い値がバックアップされるのを防ぐ
+ if (prefer.profile.modifiedAt <= latestBackupAt) return;
+
+ cloudBackup().then(() => {
+ latestBackupAt = Date.now();
+ });
+}, 1000 * 60 * 3);
+
+if (_DEV_) {
+ (window as any).prefer = prefer;
+ (window as any).cloudBackup = cloudBackup;
+}
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
new file mode 100644
index 0000000000..127ebeef0c
--- /dev/null
+++ b/packages/frontend/src/preferences/def.ts
@@ -0,0 +1,384 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import { hemisphere } from '@@/js/intl-const.js';
+import type { Theme } from '@/theme.js';
+import type { SoundType } from '@/utility/sound.js';
+import type { Plugin } from '@/plugin.js';
+import type { DeviceKind } from '@/utility/device-kind.js';
+import type { DeckProfile } from '@/deck.js';
+import type { PreferencesDefinition } from './manager.js';
+import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
+
+/** サウンド設定 */
+export type SoundStore = {
+ type: Exclude<SoundType, '_driveFile_'>;
+ volume: number;
+} | {
+ type: '_driveFile_';
+
+ /** ドライブのファイルID */
+ fileId: string;
+
+ /** ファイルURL(こちらが優先される) */
+ fileUrl: string;
+
+ volume: number;
+};
+
+// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
+
+export const PREF_DEF = {
+ // TODO: 持つのはホストやユーザーID、ユーザー名など最低限にしといて、その他のプロフィール情報はpreferences外で管理した方が綺麗そう
+ // 現状だと、updateCurrentAccount/updateCurrentAccountPartialが呼ばれるたびに「設定」へのcommitが行われて不自然(明らかに設定の更新とは捉えにくい)だし
+ accounts: {
+ default: [] as [host: string, user: Misskey.entities.User][],
+ },
+
+ pinnedUserLists: {
+ accountDependent: true,
+ default: [] as Misskey.entities.UserList[],
+ },
+ uploadFolder: {
+ accountDependent: true,
+ default: null as string | null,
+ },
+ widgets: {
+ accountDependent: true,
+ default: [{
+ name: 'calendar',
+ id: 'a', place: 'right', data: {},
+ }, {
+ name: 'notifications',
+ id: 'b', place: 'right', data: {},
+ }, {
+ name: 'trends',
+ id: 'c', place: 'right', data: {},
+ }] as {
+ name: string;
+ id: string;
+ place: string | null;
+ data: Record<string, any>;
+ }[],
+ },
+ 'deck.profile': {
+ accountDependent: true,
+ default: null as string | null,
+ },
+ 'deck.profiles': {
+ accountDependent: true,
+ default: [] as DeckProfile[],
+ },
+
+ emojiPalettes: {
+ serverDependent: true,
+ default: [{
+ id: 'a',
+ name: '',
+ emojis: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
+ }] as {
+ id: string;
+ name: string;
+ emojis: string[];
+ }[],
+ },
+ emojiPaletteForReaction: {
+ serverDependent: true,
+ default: null as string | null,
+ },
+ emojiPaletteForMain: {
+ serverDependent: true,
+ default: null as string | null,
+ },
+
+ overridedDeviceKind: {
+ default: null as DeviceKind | null,
+ },
+ themes: {
+ default: [] as Theme[],
+ },
+ lightTheme: {
+ default: null as Theme | null,
+ },
+ darkTheme: {
+ default: null as Theme | null,
+ },
+ syncDeviceDarkMode: {
+ default: true,
+ },
+ defaultNoteVisibility: {
+ default: 'public' as (typeof Misskey.noteVisibilities)[number],
+ },
+ defaultNoteLocalOnly: {
+ default: false,
+ },
+ keepCw: {
+ default: true,
+ },
+ keepOriginalUploading: {
+ default: false,
+ },
+ rememberNoteVisibility: {
+ default: false,
+ },
+ reportError: {
+ default: false,
+ },
+ collapseRenotes: {
+ default: true,
+ },
+ menu: {
+ default: [
+ 'notifications',
+ 'clips',
+ 'drive',
+ 'followRequests',
+ 'chat',
+ '-',
+ 'explore',
+ 'announcements',
+ 'channels',
+ 'search',
+ '-',
+ 'ui',
+ ],
+ },
+ statusbars: {
+ default: [] as {
+ name: string;
+ id: string;
+ type: string;
+ size: 'verySmall' | 'small' | 'medium' | 'large' | 'veryLarge';
+ black: boolean;
+ props: Record<string, any>;
+ }[],
+ },
+ serverDisconnectedBehavior: {
+ default: 'quiet' as 'quiet' | 'reload' | 'dialog',
+ },
+ nsfw: {
+ default: 'respect' as 'respect' | 'force' | 'ignore',
+ },
+ highlightSensitiveMedia: {
+ default: false,
+ },
+ animation: {
+ default: !window.matchMedia('(prefers-reduced-motion)').matches,
+ },
+ animatedMfm: {
+ default: !window.matchMedia('(prefers-reduced-motion)').matches,
+ },
+ advancedMfm: {
+ default: true,
+ },
+ showReactionsCount: {
+ default: false,
+ },
+ enableQuickAddMfmFunction: {
+ default: false,
+ },
+ loadRawImages: {
+ default: false,
+ },
+ imageNewTab: {
+ default: false,
+ },
+ disableShowingAnimatedImages: {
+ default: window.matchMedia('(prefers-reduced-motion)').matches,
+ },
+ emojiStyle: {
+ default: 'twemoji', // twemoji / fluentEmoji / native
+ },
+ menuStyle: {
+ default: 'auto' as 'auto' | 'popup' | 'drawer',
+ },
+ useBlurEffectForModal: {
+ default: DEFAULT_DEVICE_KIND === 'desktop',
+ },
+ useBlurEffect: {
+ default: DEFAULT_DEVICE_KIND === 'desktop',
+ },
+ showFixedPostForm: {
+ default: false,
+ },
+ showFixedPostFormInChannel: {
+ default: false,
+ },
+ enableInfiniteScroll: {
+ default: true,
+ },
+ useReactionPickerForContextMenu: {
+ default: false,
+ },
+ showGapBetweenNotesInTimeline: {
+ default: false,
+ },
+ instanceTicker: {
+ default: 'remote' as 'none' | 'remote' | 'always',
+ },
+ emojiPickerScale: {
+ default: 2,
+ },
+ emojiPickerWidth: {
+ default: 2,
+ },
+ emojiPickerHeight: {
+ default: 3,
+ },
+ emojiPickerStyle: {
+ default: 'auto' as 'auto' | 'popup' | 'drawer',
+ },
+ squareAvatars: {
+ default: false,
+ },
+ showAvatarDecorations: {
+ default: true,
+ },
+ numberOfPageCache: {
+ default: 3,
+ },
+ showNoteActionsOnlyHover: {
+ default: false,
+ },
+ showClipButtonInNoteFooter: {
+ default: false,
+ },
+ reactionsDisplaySize: {
+ default: 'medium' as 'small' | 'medium' | 'large',
+ },
+ limitWidthOfReaction: {
+ default: true,
+ },
+ forceShowAds: {
+ default: false,
+ },
+ aiChanMode: {
+ default: false,
+ },
+ devMode: {
+ default: false,
+ },
+ mediaListWithOneImageAppearance: {
+ default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3',
+ },
+ notificationPosition: {
+ default: 'rightBottom' as 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom',
+ },
+ notificationStackAxis: {
+ default: 'horizontal' as 'vertical' | 'horizontal',
+ },
+ enableCondensedLine: {
+ default: true,
+ },
+ keepScreenOn: {
+ default: false,
+ },
+ disableStreamingTimeline: {
+ default: false,
+ },
+ useGroupedNotifications: {
+ default: true,
+ },
+ dataSaver: {
+ default: {
+ media: false,
+ avatar: false,
+ urlPreview: false,
+ code: false,
+ } as Record<string, boolean>,
+ },
+ hemisphere: {
+ default: hemisphere as 'N' | 'S',
+ },
+ enableSeasonalScreenEffect: {
+ default: false,
+ },
+ enableHorizontalSwipe: {
+ default: true,
+ },
+ useNativeUiForVideoAudioPlayer: {
+ default: false,
+ },
+ keepOriginalFilename: {
+ default: true,
+ },
+ alwaysConfirmFollow: {
+ default: true,
+ },
+ confirmWhenRevealingSensitiveMedia: {
+ default: false,
+ },
+ contextMenu: {
+ default: 'app' as 'app' | 'appWithShift' | 'native',
+ },
+ skipNoteRender: {
+ default: true,
+ },
+ showSoftWordMutedWord: {
+ default: false,
+ },
+ confirmOnReact: {
+ default: false,
+ },
+ defaultFollowWithReplies: {
+ default: false,
+ },
+ makeEveryTextElementsSelectable: {
+ default: DEFAULT_DEVICE_KIND === 'desktop',
+ },
+ plugins: {
+ default: [] as Plugin[],
+ },
+
+ 'sound.masterVolume': {
+ default: 0.3,
+ },
+ 'sound.notUseSound': {
+ default: false,
+ },
+ 'sound.useSoundOnlyWhenActive': {
+ default: false,
+ },
+ 'sound.on.note': {
+ default: { type: 'syuilo/n-aec', volume: 1 } as SoundStore,
+ },
+ 'sound.on.noteMy': {
+ default: { type: 'syuilo/n-cea-4va', volume: 1 } as SoundStore,
+ },
+ 'sound.on.notification': {
+ default: { type: 'syuilo/n-ea', volume: 1 } as SoundStore,
+ },
+ 'sound.on.reaction': {
+ default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
+ },
+ 'sound.on.chatMessage': {
+ default: { type: 'syuilo/waon', volume: 1 } as SoundStore,
+ },
+
+ 'deck.alwaysShowMainColumn': {
+ default: true,
+ },
+ 'deck.navWindow': {
+ default: true,
+ },
+ 'deck.useSimpleUiForNonRootPages': {
+ default: true,
+ },
+ 'deck.columnAlign': {
+ default: 'left' as 'left' | 'right' | 'center',
+ },
+
+ 'game.dropAndFusion': {
+ default: {
+ bgmVolume: 0.25,
+ sfxVolume: 1,
+ },
+ },
+
+ 'experimental.stackingRouterView': {
+ default: false,
+ },
+} satisfies PreferencesDefinition;
diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts
new file mode 100644
index 0000000000..f96aa2f368
--- /dev/null
+++ b/packages/frontend/src/preferences/manager.ts
@@ -0,0 +1,476 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { computed, onUnmounted, ref, watch } from 'vue';
+import { v4 as uuid } from 'uuid';
+import { host, version } from '@@/js/config.js';
+import { PREF_DEF } from './def.js';
+import type { Ref, WritableComputedRef } from 'vue';
+import type { MenuItem } from '@/types/menu.js';
+import { $i } from '@/i.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+import { deepEqual } from '@/utility/deep-equal.js';
+
+// NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない
+
+//type DottedToNested<T extends Record<string, any>> = {
+// [K in keyof T as K extends string ? K extends `${infer A}.${infer B}` ? A : K : K]: K extends `${infer A}.${infer B}` ? DottedToNested<{ [key in B]: T[K] }> : T[K];
+//};
+
+type PREF = typeof PREF_DEF;
+type ValueOf<K extends keyof PREF> = PREF[K]['default'];
+
+type Scope = Partial<{
+ server: string | null; // host
+ account: string | null; // userId
+ device: string | null; // 将来のため
+}>;
+
+type ValueMeta = Partial<{
+ sync: boolean;
+}>;
+
+type PrefRecord<K extends keyof PREF> = [scope: Scope, value: ValueOf<K>, meta: ValueMeta];
+
+function parseScope(scope: Scope): {
+ server: string | null;
+ account: string | null;
+ device: string | null;
+} {
+ return {
+ server: scope.server ?? null,
+ account: scope.account ?? null,
+ device: scope.device ?? null,
+ };
+}
+
+function makeScope(scope: Partial<{
+ server: string | null;
+ account: string | null;
+ device: string | null;
+}>): Scope {
+ const c = {} as Scope;
+ if (scope.server != null) c.server = scope.server;
+ if (scope.account != null) c.account = scope.account;
+ if (scope.device != null) c.device = scope.device;
+ return c;
+}
+
+export function isSameScope(a: Scope, b: Scope): boolean {
+ // null と undefined (キー無し) は区別したくないので == で比較
+ // eslint-disable-next-line eqeqeq
+ return a.server == b.server && a.account == b.account && a.device == b.device;
+}
+
+export type PreferencesProfile = {
+ id: string;
+ version: string;
+ type: 'main';
+ modifiedAt: number;
+ name: string;
+ preferences: {
+ [K in keyof PREF]: PrefRecord<K>[];
+ };
+};
+
+export type StorageProvider = {
+ save: (ctx: { profile: PreferencesProfile; }) => void;
+ cloudGets: <K extends keyof PREF>(ctx: { needs: { key: K; scope: Scope; }[] }) => Promise<Partial<Record<K, ValueOf<K>>>>;
+ cloudGet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; }) => Promise<{ value: ValueOf<K>; } | null>;
+ cloudSet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; value: ValueOf<K>; }) => Promise<void>;
+};
+
+export type PreferencesDefinition = Record<string, {
+ default: any;
+ accountDependent?: boolean;
+ serverDependent?: boolean;
+}>;
+
+export class PreferencesManager {
+ private storageProvider: StorageProvider;
+ public profile: PreferencesProfile;
+ public cloudReady: Promise<void>;
+
+ /**
+ * static / state の略 (static が予約語のため)
+ */
+ public s = {} as {
+ [K in keyof PREF]: ValueOf<K>;
+ };
+
+ /**
+ * reactive の略
+ */
+ public r = {} as {
+ [K in keyof PREF]: Ref<ValueOf<K>>;
+ };
+
+ constructor(profile: PreferencesProfile, storageProvider: StorageProvider) {
+ this.profile = profile;
+ this.storageProvider = storageProvider;
+
+ const states = this.genStates();
+
+ for (const key in states) {
+ this.s[key] = states[key];
+ this.r[key] = ref(this.s[key]);
+ }
+
+ this.cloudReady = this.fetchCloudValues();
+
+ // TODO: 定期的にクラウドの値をフェッチ
+ }
+
+ private isAccountDependentKey<K extends keyof PREF>(key: K): boolean {
+ return (PREF_DEF as PreferencesDefinition)[key].accountDependent === true;
+ }
+
+ private isServerDependentKey<K extends keyof PREF>(key: K): boolean {
+ return (PREF_DEF as PreferencesDefinition)[key].serverDependent === true;
+ }
+
+ private rewriteRawState<K extends keyof PREF>(key: K, value: ValueOf<K>) {
+ const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除
+ this.r[key].value = this.s[key] = v;
+ }
+
+ public commit<K extends keyof PREF>(key: K, value: ValueOf<K>) {
+ const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除
+
+ if (deepEqual(this.s[key], v)) {
+ if (_DEV_) console.log('(skip) prefer:commit', key, v);
+ return;
+ }
+
+ if (_DEV_) console.log('prefer:commit', key, v);
+
+ this.rewriteRawState(key, v);
+
+ const record = this.getMatchedRecordOf(key);
+
+ if (parseScope(record[0]).account == null && this.isAccountDependentKey(key)) {
+ this.profile.preferences[key].push([makeScope({
+ server: host,
+ account: $i!.id,
+ }), v, {}]);
+ this.save();
+ return;
+ }
+
+ if (parseScope(record[0]).server == null && this.isServerDependentKey(key)) {
+ this.profile.preferences[key].push([makeScope({
+ server: host,
+ }), v, {}]);
+ this.save();
+ return;
+ }
+
+ record[1] = v;
+ this.save();
+
+ if (record[2].sync) {
+ // awaitの必要なし
+ // TODO: リクエストを間引く
+ this.storageProvider.cloudSet({ key, scope: record[0], value: record[1] });
+ }
+ }
+
+ /**
+ * 特定のキーの、簡易的なcomputed refを作ります
+ * 主にvue上で設定コントロールのmodelとして使う用
+ */
+ public model<K extends keyof PREF, V extends ValueOf<K> = ValueOf<K>>(
+ key: K,
+ getter?: (v: ValueOf<K>) => V,
+ setter?: (v: V) => ValueOf<K>,
+ ): WritableComputedRef<V> {
+ const valueRef = ref(this.s[key]);
+
+ const stop = watch(this.r[key], val => {
+ valueRef.value = val;
+ });
+
+ // NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする
+ onUnmounted(() => {
+ stop();
+ });
+
+ // TODO: VueのcustomRef使うと良い感じになるかも
+ return computed({
+ get: () => {
+ if (getter) {
+ return getter(valueRef.value);
+ } else {
+ return valueRef.value;
+ }
+ },
+ set: (value) => {
+ const val = setter ? setter(value) : value;
+ this.commit(key, val);
+ valueRef.value = val;
+ },
+ });
+ }
+
+ private genStates() {
+ const states = {} as { [K in keyof PREF]: ValueOf<K> };
+ for (const _key in PREF_DEF) {
+ const key = _key as keyof PREF;
+ const record = this.getMatchedRecordOf(key);
+ (states[key] as any) = record[1];
+ }
+
+ return states;
+ }
+
+ private async fetchCloudValues() {
+ const needs = [] as { key: keyof PREF; scope: Scope; }[];
+ for (const _key in PREF_DEF) {
+ const key = _key as keyof PREF;
+ const record = this.getMatchedRecordOf(key);
+ if (record[2].sync) {
+ needs.push({
+ key,
+ scope: record[0],
+ });
+ }
+ }
+
+ const cloudValues = await this.storageProvider.cloudGets({ needs });
+
+ for (const _key in PREF_DEF) {
+ const key = _key as keyof PREF;
+ const record = this.getMatchedRecordOf(key);
+ if (record[2].sync && Object.hasOwn(cloudValues, key) && cloudValues[key] !== undefined) {
+ const cloudValue = cloudValues[key];
+ if (!deepEqual(cloudValue, record[1])) {
+ this.rewriteRawState(key, cloudValue);
+ record[1] = cloudValue;
+ if (_DEV_) console.log('cloud fetched', key, cloudValue);
+ }
+ }
+ }
+
+ this.save();
+ if (_DEV_) console.log('cloud fetch completed');
+ }
+
+ public static newProfile(): PreferencesProfile {
+ const data = {} as PreferencesProfile['preferences'];
+ for (const key in PREF_DEF) {
+ data[key] = [[makeScope({}), PREF_DEF[key].default, {}]];
+ }
+ return {
+ id: uuid(),
+ version: version,
+ type: 'main',
+ modifiedAt: Date.now(),
+ name: '',
+ preferences: data,
+ };
+ }
+
+ public static normalizeProfile(profileLike: any): PreferencesProfile {
+ const data = {} as PreferencesProfile['preferences'];
+ for (const key in PREF_DEF) {
+ const records = profileLike.preferences[key];
+ if (records == null || records.length === 0) {
+ data[key] = [[makeScope({}), PREF_DEF[key].default, {}]];
+ continue;
+ } else {
+ data[key] = records;
+
+ // alpha段階ではmetaが無かったのでマイグレート
+ // TODO: そのうち消す
+ for (const record of data[key] as any[][]) {
+ if (record.length === 2) {
+ record.push({});
+ }
+ }
+ }
+ }
+
+ return {
+ ...profileLike,
+ preferences: data,
+ };
+ }
+
+ public save() {
+ this.profile.modifiedAt = Date.now();
+ this.profile.version = version;
+ this.storageProvider.save({ profile: this.profile });
+ }
+
+ public getMatchedRecordOf<K extends keyof PREF>(key: K): PrefRecord<K> {
+ const records = this.profile.preferences[key];
+
+ if ($i == null) return records.find(([scope, v]) => parseScope(scope).account == null)!;
+
+ const accountOverrideRecord = records.find(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === $i!.id);
+ if (accountOverrideRecord) return accountOverrideRecord;
+
+ const serverOverrideRecord = records.find(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account == null);
+ if (serverOverrideRecord) return serverOverrideRecord;
+
+ const record = records.find(([scope, v]) => parseScope(scope).account == null);
+ return record!;
+ }
+
+ public isAccountOverrided<K extends keyof PREF>(key: K): boolean {
+ if ($i == null) return false;
+ return this.profile.preferences[key].some(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === $i!.id) ?? false;
+ }
+
+ public setAccountOverride<K extends keyof PREF>(key: K) {
+ if ($i == null) return;
+ if (this.isAccountDependentKey(key)) throw new Error('already account-dependent');
+ if (this.isAccountOverrided(key)) return;
+
+ const records = this.profile.preferences[key];
+ records.push([makeScope({
+ server: host,
+ account: $i!.id,
+ }), this.s[key], {}]);
+
+ this.save();
+ }
+
+ public clearAccountOverride<K extends keyof PREF>(key: K) {
+ if ($i == null) return;
+ if (this.isAccountDependentKey(key)) throw new Error('cannot clear override for this account-dependent property');
+
+ const records = this.profile.preferences[key];
+
+ const index = records.findIndex(([scope, v]) => parseScope(scope).server === host && parseScope(scope).account === $i!.id);
+ if (index === -1) return;
+
+ records.splice(index, 1);
+
+ this.rewriteRawState(key, this.getMatchedRecordOf(key)[1]);
+
+ this.save();
+ }
+
+ public isSyncEnabled<K extends keyof PREF>(key: K): boolean {
+ return this.getMatchedRecordOf(key)[2].sync ?? false;
+ }
+
+ public async enableSync<K extends keyof PREF>(key: K): Promise<{ enabled: boolean; } | null> {
+ if (this.isSyncEnabled(key)) return Promise.resolve(null);
+
+ const record = this.getMatchedRecordOf(key);
+
+ const existing = await this.storageProvider.cloudGet({ key, scope: record[0] });
+ if (existing != null && !deepEqual(existing.value, record[1])) {
+ const { canceled, result } = await os.select({
+ title: i18n.ts.preferenceSyncConflictTitle,
+ text: i18n.ts.preferenceSyncConflictText,
+ items: [{
+ text: i18n.ts.preferenceSyncConflictChoiceServer,
+ value: 'remote',
+ }, {
+ text: i18n.ts.preferenceSyncConflictChoiceDevice,
+ value: 'local',
+ }, {
+ text: i18n.ts.preferenceSyncConflictChoiceCancel,
+ value: null,
+ }],
+ default: 'remote',
+ });
+ if (canceled || result == null) return { enabled: false };
+
+ if (result === 'remote') {
+ this.commit(key, existing.value);
+ } else if (result === 'local') {
+ // nop
+ }
+ }
+
+ record[2].sync = true;
+ this.save();
+
+ // awaitの必要性は無い
+ this.storageProvider.cloudSet({ key, scope: record[0], value: this.s[key] });
+
+ return { enabled: true };
+ }
+
+ public disableSync<K extends keyof PREF>(key: K) {
+ if (!this.isSyncEnabled(key)) return;
+
+ const record = this.getMatchedRecordOf(key);
+ delete record[2].sync;
+ this.save();
+ }
+
+ public renameProfile(name: string) {
+ this.profile.name = name;
+ this.save();
+ }
+
+ public rewriteProfile(profile: PreferencesProfile) {
+ this.profile = profile;
+ const states = this.genStates();
+ for (const _key in states) {
+ const key = _key as keyof PREF;
+ this.rewriteRawState(key, states[key]);
+ }
+
+ this.fetchCloudValues();
+ }
+
+ public getPerPrefMenu<K extends keyof PREF>(key: K): MenuItem[] {
+ const overrideByAccount = ref(this.isAccountOverrided(key));
+ watch(overrideByAccount, () => {
+ if (overrideByAccount.value) {
+ this.setAccountOverride(key);
+ } else {
+ this.clearAccountOverride(key);
+ }
+ });
+
+ const sync = ref(this.isSyncEnabled(key));
+ watch(sync, () => {
+ if (sync.value) {
+ this.enableSync(key).then((res) => {
+ if (res == null) return;
+ if (!res.enabled) sync.value = false;
+ });
+ } else {
+ this.disableSync(key);
+ }
+ });
+
+ return [{
+ icon: 'ti ti-copy',
+ text: i18n.ts.copyPreferenceId,
+ action: () => {
+ copyToClipboard(key);
+ },
+ }, {
+ icon: 'ti ti-refresh',
+ text: i18n.ts.resetToDefaultValue,
+ danger: true,
+ action: () => {
+ this.commit(key, PREF_DEF[key].default);
+ },
+ }, {
+ type: 'divider',
+ }, {
+ type: 'switch',
+ icon: 'ti ti-user-cog',
+ text: i18n.ts.overrideByAccount,
+ ref: overrideByAccount,
+ }, {
+ type: 'switch',
+ icon: 'ti ti-cloud-cog',
+ text: i18n.ts.syncBetweenDevices,
+ ref: sync,
+ }];
+ }
+}
diff --git a/packages/frontend/src/preferences/utility.ts b/packages/frontend/src/preferences/utility.ts
new file mode 100644
index 0000000000..adba908c3c
--- /dev/null
+++ b/packages/frontend/src/preferences/utility.ts
@@ -0,0 +1,226 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { ref, watch } from 'vue';
+import type { PreferencesProfile } from './manager.js';
+import type { MenuItem } from '@/types/menu.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { i18n } from '@/i18n.js';
+import { miLocalStorage } from '@/local-storage.js';
+import { prefer } from '@/preferences.js';
+import * as os from '@/os.js';
+import { store } from '@/store.js';
+import { $i } from '@/i.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { unisonReload } from '@/utility/unison-reload.js';
+
+function canAutoBackup() {
+ return prefer.profile.name != null && prefer.profile.name.trim() !== '';
+}
+
+export function getPreferencesProfileMenu(): MenuItem[] {
+ const autoBackupEnabled = ref(store.s.enablePreferencesAutoCloudBackup);
+
+ watch(autoBackupEnabled, () => {
+ if (autoBackupEnabled.value) {
+ if (!canAutoBackup()) {
+ autoBackupEnabled.value = false;
+ os.alert({
+ type: 'warning',
+ title: i18n.ts._preferencesBackup.youNeedToNameYourProfileToEnableAutoBackup,
+ });
+ return;
+ }
+
+ store.set('enablePreferencesAutoCloudBackup', true);
+ } else {
+ store.set('enablePreferencesAutoCloudBackup', false);
+ }
+ });
+
+ const menu: MenuItem[] = [{
+ type: 'label',
+ text: prefer.profile.name || `(${i18n.ts.noName})`,
+ }, {
+ text: i18n.ts.rename,
+ icon: 'ti ti-pencil',
+ action: () => {
+ renameProfile();
+ },
+ }, {
+ type: 'switch',
+ icon: 'ti ti-cloud-up',
+ text: i18n.ts._preferencesBackup.autoBackup,
+ ref: autoBackupEnabled,
+ }, {
+ text: i18n.ts.export,
+ icon: 'ti ti-download',
+ action: () => {
+ exportCurrentProfile();
+ },
+ }, {
+ type: 'divider',
+ }, {
+ text: i18n.ts._preferencesBackup.restoreFromBackup,
+ icon: 'ti ti-cloud-down',
+ action: () => {
+ restoreFromCloudBackup();
+ },
+ }, {
+ text: i18n.ts.import,
+ icon: 'ti ti-upload',
+ action: () => {
+ importProfile();
+ },
+ }];
+
+ if (prefer.s.devMode) {
+ menu.push({
+ type: 'divider',
+ }, {
+ text: 'Copy profile as text',
+ icon: 'ti ti-clipboard',
+ action: () => {
+ copyToClipboard(JSON.stringify(prefer.profile, null, '\t'));
+ },
+ });
+ }
+
+ return menu;
+}
+
+async function renameProfile() {
+ const { canceled, result: name } = await os.inputText({
+ title: i18n.ts._preferencesProfile.profileName,
+ text: i18n.ts._preferencesProfile.profileNameDescription + '\n' + i18n.ts._preferencesProfile.profileNameDescription2,
+ placeholder: prefer.profile.name || null,
+ default: prefer.profile.name || null,
+ });
+ if (canceled || name == null || name.trim() === '') return;
+
+ prefer.renameProfile(name);
+}
+
+function exportCurrentProfile() {
+ const p = prefer.profile;
+ const txtBlob = new Blob([JSON.stringify(p)], { type: 'text/plain' });
+ const dummya = window.document.createElement('a');
+ dummya.href = URL.createObjectURL(txtBlob);
+ dummya.download = `${p.name || p.id}.misskeypreferences`;
+ dummya.click();
+}
+
+function importProfile() {
+ const input = window.document.createElement('input');
+ input.type = 'file';
+ input.accept = '.misskeypreferences';
+ input.onchange = async () => {
+ if (input.files == null || input.files.length === 0) return;
+
+ const file = input.files[0];
+ const txt = await file.text();
+ const profile = JSON.parse(txt) as PreferencesProfile;
+
+ miLocalStorage.setItem('preferences', JSON.stringify(profile));
+ miLocalStorage.setItem('hidePreferencesRestoreSuggestion', 'true');
+ shouldSuggestRestoreBackup.value = false;
+ unisonReload();
+ };
+
+ input.click();
+}
+
+export async function cloudBackup() {
+ if ($i == null) return;
+ if (!canAutoBackup()) {
+ throw new Error('Profile name is not set');
+ }
+
+ await misskeyApi('i/registry/set', {
+ scope: ['client', 'preferences', 'backups'],
+ key: prefer.profile.name,
+ value: prefer.profile,
+ });
+}
+
+export async function restoreFromCloudBackup() {
+ if ($i == null) return;
+
+ // TODO: 更新日時でソートして取得したい
+ const keys = await misskeyApi('i/registry/keys', {
+ scope: ['client', 'preferences', 'backups'],
+ });
+
+ if (_DEV_) console.log(keys);
+
+ if (keys.length === 0) {
+ os.alert({
+ type: 'warning',
+ title: i18n.ts._preferencesBackup.noBackupsFoundTitle,
+ text: i18n.ts._preferencesBackup.noBackupsFoundDescription,
+ });
+ return;
+ }
+
+ const select = await os.select({
+ title: i18n.ts._preferencesBackup.selectBackupToRestore,
+ items: keys.map(k => ({
+ text: k,
+ value: k,
+ })),
+ });
+ if (select.canceled) return;
+ if (select.result == null) return;
+
+ const profile = await misskeyApi('i/registry/get', {
+ scope: ['client', 'preferences', 'backups'],
+ key: select.result,
+ });
+
+ if (_DEV_) console.log(profile);
+
+ miLocalStorage.setItem('preferences', JSON.stringify(profile));
+ miLocalStorage.setItem('hidePreferencesRestoreSuggestion', 'true');
+ store.set('enablePreferencesAutoCloudBackup', true);
+ shouldSuggestRestoreBackup.value = false;
+ unisonReload();
+}
+
+export async function enableAutoBackup() {
+ if (!canAutoBackup()) {
+ await renameProfile();
+ }
+
+ if (!canAutoBackup()) {
+ return;
+ }
+
+ store.set('enablePreferencesAutoCloudBackup', true);
+}
+
+export const shouldSuggestRestoreBackup = ref(false);
+
+if ($i != null) {
+ if (new Date($i.createdAt).getTime() > (Date.now() - 1000 * 60 * 30)) { // アカウント作成直後は意味ないので除外
+ miLocalStorage.setItem('hidePreferencesRestoreSuggestion', 'true');
+ } else {
+ if (miLocalStorage.getItem('hidePreferencesRestoreSuggestion') !== 'true') {
+ misskeyApi('i/registry/keys', {
+ scope: ['client', 'preferences', 'backups'],
+ }).then(keys => {
+ if (keys.length === 0) {
+ miLocalStorage.setItem('hidePreferencesRestoreSuggestion', 'true');
+ } else {
+ shouldSuggestRestoreBackup.value = true;
+ }
+ });
+ }
+ }
+}
+
+export function hideRestoreBackupSuggestion() {
+ miLocalStorage.setItem('hidePreferencesRestoreSuggestion', 'true');
+ shouldSuggestRestoreBackup.value = false;
+}
diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router.definition.ts
index 2d50a27dbf..725b844ad2 100644
--- a/packages/frontend/src/router/definition.ts
+++ b/packages/frontend/src/router.definition.ts
@@ -3,10 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { AsyncComponentLoader, defineAsyncComponent } from 'vue';
-import type { IRouter, RouteDef } from '@/nirax.js';
-import { Router } from '@/nirax.js';
-import { $i, iAmModerator } from '@/account.js';
+import { defineAsyncComponent } from 'vue';
+import type { AsyncComponentLoader } from 'vue';
+import type { RouteDef } from '@/lib/nirax.js';
+import { $i, iAmModerator } from '@/i.js';
import MkLoading from '@/pages/_loading_.vue';
import MkError from '@/pages/_error_.vue';
@@ -16,7 +16,7 @@ export const page = (loader: AsyncComponentLoader) => defineAsyncComponent({
errorComponent: MkError,
});
-const routes: RouteDef[] = [{
+export const ROUTE_DEF = [{
path: '/@:username/pages/:pageName(*)',
component: page(() => import('@/pages/page.vue')),
}, {
@@ -41,6 +41,22 @@ const routes: RouteDef[] = [{
path: '/clips/:clipId',
component: page(() => import('@/pages/clip.vue')),
}, {
+ path: '/chat',
+ component: page(() => import('@/pages/chat/home.vue')),
+ loginRequired: true,
+}, {
+ path: '/chat/user/:userId',
+ component: page(() => import('@/pages/chat/room.vue')),
+ loginRequired: true,
+}, {
+ path: '/chat/room/:roomId',
+ component: page(() => import('@/pages/chat/room.vue')),
+ loginRequired: true,
+}, {
+ path: '/chat/messages/:messageId',
+ component: page(() => import('@/pages/chat/message.vue')),
+ loginRequired: true,
+}, {
path: '/instance-info/:host',
component: page(() => import('@/pages/instance-info.vue')),
}, {
@@ -57,17 +73,13 @@ const routes: RouteDef[] = [{
name: 'avatarDecoration',
component: page(() => import('@/pages/settings/avatar-decoration.vue')),
}, {
- path: '/roles',
- name: 'roles',
- component: page(() => import('@/pages/settings/roles.vue')),
- }, {
path: '/privacy',
name: 'privacy',
component: page(() => import('@/pages/settings/privacy.vue')),
}, {
- path: '/emoji-picker',
- name: 'emojiPicker',
- component: page(() => import('@/pages/settings/emoji-picker.vue')),
+ path: '/emoji-palette',
+ name: 'emoji-palette',
+ component: page(() => import('@/pages/settings/emoji-palette.vue')),
}, {
path: '/drive',
name: 'drive',
@@ -89,9 +101,9 @@ const routes: RouteDef[] = [{
name: 'security',
component: page(() => import('@/pages/settings/security.vue')),
}, {
- path: '/general',
- name: 'general',
- component: page(() => import('@/pages/settings/general.vue')),
+ path: '/preferences',
+ name: 'preferences',
+ component: page(() => import('@/pages/settings/preferences.vue')),
}, {
path: '/theme/install',
name: 'theme',
@@ -117,6 +129,10 @@ const routes: RouteDef[] = [{
name: 'sounds',
component: page(() => import('@/pages/settings/sounds.vue')),
}, {
+ path: '/accessibility',
+ name: 'accessibility',
+ component: page(() => import('@/pages/settings/accessibility.vue')),
+ }, {
path: '/plugin/install',
name: 'plugin',
component: page(() => import('@/pages/settings/plugin.install.vue')),
@@ -125,48 +141,36 @@ const routes: RouteDef[] = [{
name: 'plugin',
component: page(() => import('@/pages/settings/plugin.vue')),
}, {
- path: '/import-export',
- name: 'import-export',
- component: page(() => import('@/pages/settings/import-export.vue')),
+ path: '/account-data',
+ name: 'account-data',
+ component: page(() => import('@/pages/settings/account-data.vue')),
}, {
path: '/mute-block',
name: 'mute-block',
component: page(() => import('@/pages/settings/mute-block.vue')),
}, {
- path: '/api',
- name: 'api',
- component: page(() => import('@/pages/settings/api.vue')),
+ path: '/connect',
+ name: 'connect',
+ component: page(() => import('@/pages/settings/connect.vue')),
}, {
path: '/apps',
- name: 'api',
+ name: 'connect',
component: page(() => import('@/pages/settings/apps.vue')),
}, {
path: '/webhook/edit/:webhookId',
- name: 'webhook',
+ name: 'connect',
component: page(() => import('@/pages/settings/webhook.edit.vue')),
}, {
path: '/webhook/new',
- name: 'webhook',
+ name: 'connect',
component: page(() => import('@/pages/settings/webhook.new.vue')),
}, {
- path: '/webhook',
- name: 'webhook',
- component: page(() => import('@/pages/settings/webhook.vue')),
- }, {
path: '/deck',
name: 'deck',
component: page(() => import('@/pages/settings/deck.vue')),
}, {
- path: '/preferences-backups',
- name: 'preferences-backups',
- component: page(() => import('@/pages/settings/preferences-backups.vue')),
- }, {
- path: '/migration',
- name: 'migration',
- component: page(() => import('@/pages/settings/migration.vue')),
- }, {
path: '/custom-css',
- name: 'general',
+ name: 'preferences',
component: page(() => import('@/pages/settings/custom-css.vue')),
}, {
path: '/accounts',
@@ -590,7 +594,6 @@ const routes: RouteDef[] = [{
name: 'index',
path: '/',
component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')),
- globalCacheKey: 'index',
}, {
// テスト用リダイレクト設定。ログイン中ユーザのプロフィールにリダイレクトする
path: '/redirect-test',
@@ -599,8 +602,4 @@ const routes: RouteDef[] = [{
}, {
path: '/:(*)',
component: page(() => import('@/pages/not-found.vue')),
-}];
-
-export function createMainRouter(path: string): IRouter {
- return new Router(routes, path, !!$i, page(() => import('@/pages/not-found.vue')));
-}
+}] satisfies RouteDef[];
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
new file mode 100644
index 0000000000..d702da80fa
--- /dev/null
+++ b/packages/frontend/src/router.ts
@@ -0,0 +1,46 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { inject } from 'vue';
+import { page } from '@/router.definition.js';
+import { $i } from '@/i.js';
+import { Nirax } from '@/lib/nirax.js';
+import { ROUTE_DEF } from '@/router.definition.js';
+import { analytics } from '@/analytics.js';
+import { DI } from '@/di.js';
+
+export type Router = Nirax<typeof ROUTE_DEF>;
+
+export function createRouter(fullPath: string): Router {
+ return new Nirax(ROUTE_DEF, fullPath, !!$i, page(() => import('@/pages/not-found.vue')));
+}
+
+export const mainRouter = createRouter(window.location.pathname + window.location.search + window.location.hash);
+
+window.addEventListener('popstate', (event) => {
+ mainRouter.replace(window.location.pathname + window.location.search + window.location.hash);
+});
+
+mainRouter.addListener('push', ctx => {
+ window.history.pushState({ }, '', ctx.fullPath);
+});
+
+mainRouter.addListener('replace', ctx => {
+ window.history.replaceState({ }, '', ctx.fullPath);
+});
+
+mainRouter.addListener('change', ctx => {
+ if (_DEV_) console.log('mainRouter: change', ctx.fullPath);
+ analytics.page({
+ path: ctx.fullPath,
+ title: ctx.fullPath,
+ });
+});
+
+mainRouter.init();
+
+export function useRouter(): Router {
+ return inject(DI.router) ?? mainRouter;
+}
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
deleted file mode 100644
index 8307df1150..0000000000
--- a/packages/frontend/src/router/main.ts
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { EventEmitter } from 'eventemitter3';
-import { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js';
-
-import type { App, ShallowRef } from 'vue';
-
-/**
- * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。
- * また、{@link Router}のインスタンスを作成するためのファクトリも{@link provide}経由で公開する(`routerFactory`というキーで取得可能)
- */
-export function setupRouter(app: App, routerFactory: ((path: string) => IRouter)): void {
- app.provide('routerFactory', routerFactory);
-
- const mainRouter = routerFactory(location.pathname + location.search + location.hash);
-
- window.addEventListener('popstate', (event) => {
- mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
- });
-
- mainRouter.addListener('push', ctx => {
- window.history.pushState({ key: ctx.key }, '', ctx.path);
- });
-
- mainRouter.addListener('same', () => {
- window.scroll({ top: 0, behavior: 'smooth' });
- });
-
- mainRouter.addListener('replace', ctx => {
- window.history.replaceState({ key: ctx.key }, '', ctx.path);
- });
-
- mainRouter.init();
-
- setMainRouter(mainRouter);
-}
-
-function getMainRouter(): IRouter {
- const router = mainRouterHolder;
- if (!router) {
- throw new Error('mainRouter is not found.');
- }
-
- return router;
-}
-
-/**
- * メインルータを設定する。一度設定すると、それ以降は変更できない。
- * {@link setupRouter}から呼び出されることのみを想定している。
- */
-export function setMainRouter(router: IRouter) {
- if (mainRouterHolder) {
- throw new Error('mainRouter is already exists.');
- }
-
- mainRouterHolder = router;
-}
-
-/**
- * {@link mainRouter}用のプロキシ実装。
- * {@link mainRouter}は起動シーケンスの一部にて初期化されるため、僅かにundefinedになる期間がある。
- * その僅かな期間のためだけに型をundefined込みにしたくないのでこのクラスを緩衝材として使用する。
- */
-class MainRouterProxy implements IRouter {
- private supplier: () => IRouter;
-
- constructor(supplier: () => IRouter) {
- this.supplier = supplier;
- }
-
- get current(): Resolved {
- return this.supplier().current;
- }
-
- get currentRef(): ShallowRef<Resolved> {
- return this.supplier().currentRef;
- }
-
- get currentRoute(): ShallowRef<RouteDef> {
- return this.supplier().currentRoute;
- }
-
- get navHook(): ((path: string, flag?: RouterFlag) => boolean) | null {
- return this.supplier().navHook;
- }
-
- set navHook(value) {
- this.supplier().navHook = value;
- }
-
- getCurrentKey(): string {
- return this.supplier().getCurrentKey();
- }
-
- getCurrentPath(): string {
- return this.supplier().getCurrentPath();
- }
-
- push(path: string, flag?: RouterFlag): void {
- this.supplier().push(path, flag);
- }
-
- replace(path: string, key?: string | null): void {
- this.supplier().replace(path, key);
- }
-
- resolve(path: string): Resolved | null {
- return this.supplier().resolve(path);
- }
-
- init(): void {
- this.supplier().init();
- }
-
- eventNames(): Array<EventEmitter.EventNames<RouterEvent>> {
- return this.supplier().eventNames();
- }
-
- listeners<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- ): Array<EventEmitter.EventListener<RouterEvent, T>> {
- return this.supplier().listeners(event);
- }
-
- listenerCount(
- event: EventEmitter.EventNames<RouterEvent>,
- ): number {
- return this.supplier().listenerCount(event);
- }
-
- emit<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- ...args: EventEmitter.EventArgs<RouterEvent, T>
- ): boolean {
- return this.supplier().emit(event, ...args);
- }
-
- on<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- ): this {
- this.supplier().on(event, fn, context);
- return this;
- }
-
- addListener<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- ): this {
- this.supplier().addListener(event, fn, context);
- return this;
- }
-
- once<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- ): this {
- this.supplier().once(event, fn, context);
- return this;
- }
-
- removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn?: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- once?: boolean,
- ): this {
- this.supplier().removeListener(event, fn, context, once);
- return this;
- }
-
- off<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn?: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- once?: boolean,
- ): this {
- this.supplier().off(event, fn, context, once);
- return this;
- }
-
- removeAllListeners(
- event?: EventEmitter.EventNames<RouterEvent>,
- ): this {
- this.supplier().removeAllListeners(event);
- return this;
- }
-}
-
-let mainRouterHolder: IRouter | null = null;
-
-export const mainRouter: IRouter = new MainRouterProxy(getMainRouter);
diff --git a/packages/frontend/src/router/supplier.ts b/packages/frontend/src/router/supplier.ts
deleted file mode 100644
index 7da236f4e7..0000000000
--- a/packages/frontend/src/router/supplier.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { inject } from 'vue';
-import { IRouter, Router } from '@/nirax.js';
-import { mainRouter } from '@/router/main.js';
-
-/**
- * メインの{@link Router}を取得する。
- * あらかじめ{@link setupRouter}を実行しておく必要がある({@link provide}により{@link IRouter}のインスタンスを注入可能であるならばこの限りではない)
- */
-export function useRouter(): IRouter {
- return inject<Router | null>('router', null) ?? mainRouter;
-}
-
-/**
- * 任意の{@link Router}を取得するためのファクトリを取得する。
- * あらかじめ{@link setupRouter}を実行しておく必要がある。
- */
-export function useRouterFactory(): (path: string) => IRouter {
- const factory = inject<(path: string) => IRouter>('routerFactory');
- if (!factory) {
- console.error('routerFactory is not defined.');
- throw new Error('routerFactory is not defined.');
- }
-
- return factory;
-}
diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts
deleted file mode 100644
index a85ee01e26..0000000000
--- a/packages/frontend/src/scripts/gen-search-query.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import * as Misskey from 'misskey-js';
-import { host as localHost } from '@@/js/config.js';
-
-export async function genSearchQuery(v: any, q: string) {
- let host: string;
- let userId: string;
- if (q.split(' ').some(x => x.startsWith('@'))) {
- for (const at of q.split(' ').filter(x => x.startsWith('@')).map(x => x.substring(1))) {
- if (at.includes('.')) {
- if (at === localHost || at === '.') {
- host = null;
- } else {
- host = at;
- }
- } else {
- const user = await v.api('users/show', Misskey.acct.parse(at)).catch(x => null);
- if (user) {
- userId = user.id;
- } else {
- // todo: show error
- }
- }
- }
- }
- return {
- query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '),
- host: host,
- userId: userId,
- };
-}
diff --git a/packages/frontend/src/scripts/install-plugin.ts b/packages/frontend/src/scripts/install-plugin.ts
deleted file mode 100644
index 72ff8bd5ff..0000000000
--- a/packages/frontend/src/scripts/install-plugin.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { defineAsyncComponent } from 'vue';
-import { compareVersions } from 'compare-versions';
-import { v4 as uuid } from 'uuid';
-import { Interpreter, Parser, utils } from '@syuilo/aiscript';
-import type { Plugin } from '@/store.js';
-import { ColdDeviceStorage } from '@/store.js';
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { i18n } from '@/i18n.js';
-
-export type AiScriptPluginMeta = {
- name: string;
- version: string;
- author: string;
- description?: string;
- permissions?: string[];
- config?: Record<string, any>;
-};
-
-const parser = new Parser();
-
-export function savePlugin({ id, meta, src, token }: {
- id: string;
- meta: AiScriptPluginMeta;
- src: string;
- token: string;
-}) {
- ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({
- ...meta,
- id,
- active: true,
- configData: {},
- token: token,
- src: src,
- } as Plugin));
-}
-
-export function isSupportedAiScriptVersion(version: string): boolean {
- try {
- return (compareVersions(version, '0.12.0') >= 0);
- } catch (err) {
- return false;
- }
-}
-
-export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta> {
- if (!code) {
- throw new Error('code is required');
- }
-
- const lv = utils.getLangVersion(code);
- if (lv == null) {
- throw new Error('No language version annotation found');
- } else if (!isSupportedAiScriptVersion(lv)) {
- throw new Error(`Aiscript version '${lv}' is not supported`);
- }
-
- let ast;
- try {
- ast = parser.parse(code);
- } catch (err) {
- if (err instanceof Error) {
- throw new Error(`Aiscript syntax error\n${(err as Error).message}`);
- } else {
- throw new Error('Aiscript syntax error');
- }
- }
-
- const meta = Interpreter.collectMetadata(ast);
- if (meta == null) {
- throw new Error('Meta block not found');
- }
-
- const metadata = meta.get(null);
- if (metadata == null) {
- throw new Error('Metadata not found');
- }
-
- const { name, version, author, description, permissions, config } = metadata;
- if (name == null || version == null || author == null) {
- throw new Error('Required property not found');
- }
-
- return {
- name,
- version,
- author,
- description,
- permissions,
- config,
- };
-}
-
-export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
- if (!code) return;
-
- let realMeta: AiScriptPluginMeta;
- if (!meta) {
- realMeta = await parsePluginMeta(code);
- } else {
- realMeta = meta;
- }
-
- const token = realMeta.permissions == null || realMeta.permissions.length === 0 ? null : await new Promise((res, rej) => {
- const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {
- title: i18n.ts.tokenRequested,
- information: i18n.ts.pluginTokenRequestedDescription,
- initialName: realMeta.name,
- initialPermissions: realMeta.permissions,
- }, {
- done: async result => {
- const { name, permissions } = result;
- const { token } = await misskeyApi('miauth/gen-token', {
- session: null,
- name: name,
- permission: permissions,
- });
- res(token);
- },
- closed: () => dispose(),
- });
- });
-
- savePlugin({
- id: uuid(),
- meta: realMeta,
- token,
- src: code,
- });
-}
diff --git a/packages/frontend/src/scripts/install-theme.ts b/packages/frontend/src/scripts/install-theme.ts
deleted file mode 100644
index 866f1225bf..0000000000
--- a/packages/frontend/src/scripts/install-theme.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import JSON5 from 'json5';
-import { addTheme, getThemes } from '@/theme-store.js';
-import { Theme, applyTheme, validateTheme } from '@/scripts/theme.js';
-
-export function parseThemeCode(code: string): Theme {
- let theme;
-
- try {
- theme = JSON5.parse(code);
- } catch (err) {
- throw new Error('Failed to parse theme json');
- }
- if (!validateTheme(theme)) {
- throw new Error('This theme is invaild');
- }
- if (getThemes().some(t => t.id === theme.id)) {
- throw new Error('This theme is already installed');
- }
-
- return theme;
-}
-
-export function previewTheme(code: string): void {
- const theme = parseThemeCode(code);
- if (theme) applyTheme(theme, false);
-}
-
-export async function installTheme(code: string): Promise<void> {
- const theme = parseThemeCode(code);
- if (!theme) return;
- await addTheme(theme);
-}
diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts
deleted file mode 100644
index 54ec2ce39b..0000000000
--- a/packages/frontend/src/scripts/lookup.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { i18n } from '@/i18n.js';
-import { Router } from '@/nirax.js';
-import { mainRouter } from '@/router/main.js';
-
-export async function lookup(router?: Router) {
- const _router = router ?? mainRouter;
-
- const { canceled, result: temp } = await os.inputText({
- title: i18n.ts.lookup,
- });
- const query = temp ? temp.trim() : '';
- if (canceled || query.length <= 1) return;
-
- if (query.startsWith('@') && !query.includes(' ')) {
- _router.push(`/${query}`);
- return;
- }
-
- if (query.startsWith('#')) {
- _router.push(`/tags/${encodeURIComponent(query.substring(1))}`);
- return;
- }
-
- if (query.startsWith('http://') || query.startsWith('https://')) {
- const promise = misskeyApi('ap/show', {
- uri: query,
- });
-
- os.promiseDialog(promise, null, (err) => {
- let title = i18n.ts.somethingHappened;
- let text = err.message + '\n' + err.id;
-
- switch (err.id) {
- case '974b799e-1a29-4889-b706-18d4dd93e266':
- title = i18n.ts._remoteLookupErrors._federationNotAllowed.title;
- text = i18n.ts._remoteLookupErrors._federationNotAllowed.description;
- break;
- case '1a5eab56-e47b-48c2-8d5e-217b897d70db':
- title = i18n.ts._remoteLookupErrors._uriInvalid.title;
- text = i18n.ts._remoteLookupErrors._uriInvalid.description;
- break;
- case '81b539cf-4f57-4b29-bc98-032c33c0792e':
- title = i18n.ts._remoteLookupErrors._requestFailed.title;
- text = i18n.ts._remoteLookupErrors._requestFailed.description;
- break;
- case '70193c39-54f3-4813-82f0-70a680f7495b':
- title = i18n.ts._remoteLookupErrors._responseInvalid.title;
- text = i18n.ts._remoteLookupErrors._responseInvalid.description;
- break;
- case 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a':
- title = i18n.ts._remoteLookupErrors._responseInvalid.title;
- text = i18n.ts._remoteLookupErrors._responseInvalidIdHostNotMatch.description;
- break;
- case 'dc94d745-1262-4e63-a17d-fecaa57efc82':
- title = i18n.ts._remoteLookupErrors._noSuchObject.title;
- text = i18n.ts._remoteLookupErrors._noSuchObject.description;
- break;
- }
-
- os.alert({
- type: 'error',
- title,
- text,
- });
- }, i18n.ts.fetchingAsApObject);
-
- const res = await promise;
-
- if (res.type === 'User') {
- _router.push(`/@${res.object.username}@${res.object.host}`);
- } else if (res.type === 'Note') {
- _router.push(`/notes/${res.object.id}`);
- }
-
- return;
- }
-}
diff --git a/packages/frontend/src/server-context.ts b/packages/frontend/src/server-context.ts
index e79d3fa314..744bfa4b7b 100644
--- a/packages/frontend/src/server-context.ts
+++ b/packages/frontend/src/server-context.ts
@@ -5,7 +5,7 @@
import * as Misskey from 'misskey-js';
-const providedContextEl = document.getElementById('misskey_clientCtx');
+const providedContextEl = window.document.getElementById('misskey_clientCtx');
export type ServerContext = {
clip?: Misskey.entities.Clip;
diff --git a/packages/frontend/src/signout.ts b/packages/frontend/src/signout.ts
new file mode 100644
index 0000000000..e7d7cdfd22
--- /dev/null
+++ b/packages/frontend/src/signout.ts
@@ -0,0 +1,55 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { apiUrl } from '@@/js/config.js';
+import { defaultMemoryStorage } from '@/memory-storage';
+import { waiting } from '@/os.js';
+import { unisonReload, reloadChannel } from '@/utility/unison-reload.js';
+import { $i } from '@/i.js';
+
+export async function signout() {
+ if (!$i) return;
+
+ // TODO: preferの自動バックアップがオンの場合、いろいろ消す前に強制バックアップ
+
+ waiting();
+
+ localStorage.clear();
+ defaultMemoryStorage.clear();
+
+ const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => {
+ indexedDB.deleteDatabase(name);
+ }));
+
+ await Promise.all(idbPromises);
+
+ //#region Remove service worker registration
+ try {
+ if (navigator.serviceWorker.controller) {
+ const registration = await navigator.serviceWorker.ready;
+ const push = await registration.pushManager.getSubscription();
+ if (push) {
+ await window.fetch(`${apiUrl}/sw/unregister`, {
+ method: 'POST',
+ body: JSON.stringify({
+ i: $i.token,
+ endpoint: push.endpoint,
+ }),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ }
+ }
+
+ await navigator.serviceWorker.getRegistrations()
+ .then(registrations => {
+ return Promise.all(registrations.map(registration => registration.unregister()));
+ });
+ } catch (err) {}
+ //#endregion
+
+ unisonReload('/');
+}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 69fcef32c2..0ba1a17969 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -5,70 +5,21 @@
import { markRaw, ref } from 'vue';
import * as Misskey from 'misskey-js';
+import { searchEngineMap } from '@/utility/search-engine-map.js';
+import lightTheme from '@@/themes/l-light.json5';
+import darkTheme from '@@/themes/d-green-lime.json5';
import { hemisphere } from '@@/js/intl-const.js';
-import lightTheme from '@@/themes/l-cherry.json5';
-import darkTheme from '@@/themes/d-ice.json5';
-import { searchEngineMap } from './scripts/search-engine-map.js';
-import type { SoundType } from '@/scripts/sound.js';
-import type { Ast } from '@syuilo/aiscript';
-import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js';
+import type { DeviceKind } from '@/utility/device-kind.js';
+import type { Plugin } from '@/plugin.js';
import { miLocalStorage } from '@/local-storage.js';
-import { defaultFollowingFeedState } from '@/scripts/following-feed-utils.js';
-import { Storage } from '@/pizzax.js';
+import { Pizzax } from '@/lib/pizzax.js';
+import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
+import { defaultFollowingFeedState } from '@/utility/following-feed-utils.js';
-interface PostFormAction {
- title: string,
- handler: <T>(form: T, update: (key: unknown, value: unknown) => void) => void;
-}
-
-interface UserAction {
- title: string,
- handler: (user: Misskey.entities.UserDetailed) => void;
-}
-
-interface NoteAction {
- title: string,
- handler: (note: Misskey.entities.Note) => void;
-}
-
-interface NoteViewInterruptor {
- handler: (note: Misskey.entities.Note) => unknown;
-}
-
-interface NotePostInterruptor {
- handler: (note: FIXME) => unknown;
-}
-
-interface PageViewInterruptor {
- handler: (page: Misskey.entities.Page) => unknown;
-}
-
-/** サウンド設定 */
-export type SoundStore = {
- type: Exclude<SoundType, '_driveFile_'>;
- volume: number;
-} | {
- type: '_driveFile_';
-
- /** ドライブのファイルID */
- fileId: string;
-
- /** ファイルURL(こちらが優先される) */
- fileUrl: string;
-
- volume: number;
-}
-
-export const postFormActions: PostFormAction[] = [];
-export const userActions: UserAction[] = [];
-export const noteActions: NoteAction[] = [];
-export const noteViewInterruptors: NoteViewInterruptor[] = [];
-export const notePostInterruptors: NotePostInterruptor[] = [];
-export const pageViewInterruptors: PageViewInterruptor[] = [];
-
-// TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう)
-// あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない
-export const defaultStore = markRaw(new Storage('base', {
+/**
+ * 「状態」を管理するストア(not「設定」)
+ */
+export const store = markRaw(new Pizzax('base', {
accountSetupWizard: {
where: 'account',
default: 0,
@@ -86,18 +37,103 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: false,
},
- keepCw: {
+ memo: {
where: 'account',
- default: true,
+ default: null,
},
- showFullAcct: {
+ reactionAcceptance: {
+ where: 'account',
+ default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
+ },
+ mutedAds: {
where: 'account',
+ default: [] as string[],
+ },
+ visibility: {
+ where: 'deviceAccount',
+ default: 'public' as (typeof Misskey.noteVisibilities)[number],
+ },
+ localOnly: {
+ where: 'deviceAccount',
default: false,
},
- collapseRenotes: {
+ showPreview: {
+ where: 'device',
+ default: false,
+ },
+ tl: {
+ where: 'deviceAccount',
+ default: {
+ src: 'home' as 'home' | 'local' | 'social' | 'global' | 'bubble' | `list:${string}`,
+ userList: null as Misskey.entities.UserList | null,
+ filter: {
+ withReplies: true,
+ withRenotes: true,
+ withSensitive: true,
+ onlyFiles: false,
+ withBots: true,
+ },
+ },
+ },
+ darkMode: {
+ where: 'device',
+ default: false,
+ },
+ recentlyUsedEmojis: {
+ where: 'device',
+ default: [] as string[],
+ },
+ recentlyUsedUsers: {
+ where: 'device',
+ default: [] as string[],
+ },
+ menuDisplay: {
+ where: 'device',
+ default: 'sideFull' as 'sideFull' | 'sideIcon' | 'top',
+ },
+ postFormWithHashtags: {
+ where: 'device',
+ default: false,
+ },
+ postFormHashtags: {
+ where: 'device',
+ default: '',
+ },
+ additionalUnicodeEmojiIndexes: {
+ where: 'device',
+ default: {} as Record<string, Record<string, string[]>>,
+ },
+ pluginTokens: {
+ where: 'deviceAccount',
+ default: {} as Record<string, string>, // plugin id, token
+ },
+ accountTokens: {
+ where: 'device',
+ default: {} as Record<string, string>, // host/userId, token
+ },
+
+ enablePreferencesAutoCloudBackup: {
+ where: 'device',
+ default: false,
+ },
+ showPreferencesAutoCloudBackupSuggestion: {
+ where: 'device',
+ default: true,
+ },
+
+ //#region TODO: そのうち消す (preferに移行済み)
+ defaultWithReplies: {
where: 'account',
default: false,
},
+ reactions: {
+ where: 'account',
+ default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
+ },
+ pinnedEmojis: {
+ where: 'account',
+ default: [],
+ },
collapseNotesRepliedTo: {
where: 'account',
default: false,
@@ -114,8 +150,21 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
- rememberNoteVisibility: {
+ widgets: {
where: 'account',
+ default: [] as {
+ name: string;
+ id: string;
+ place: string | null;
+ data: Record<string, any>;
+ }[],
+ },
+ overridedDeviceKind: {
+ where: 'device',
+ default: null as DeviceKind | null,
+ },
+ defaultSideView: {
+ where: 'device',
default: false,
},
defaultNoteVisibility: {
@@ -126,42 +175,30 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: false,
},
- uploadFolder: {
+ keepCw: {
where: 'account',
- default: null as string | null,
+ default: true,
},
- pastedFileName: {
+ collapseRenotes: {
where: 'account',
- default: 'yyyy-MM-dd HH-mm-ss [{{number}}]',
+ default: true,
},
- keepOriginalUploading: {
+ rememberNoteVisibility: {
where: 'account',
default: false,
},
- memo: {
- where: 'account',
- default: null,
- },
- reactions: {
- where: 'account',
- default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
- },
- pinnedEmojis: {
+ uploadFolder: {
where: 'account',
- default: [],
+ default: null as string | null,
},
- reactionAcceptance: {
+ keepOriginalUploading: {
where: 'account',
- default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
+ default: false,
},
like: {
where: 'account',
default: null as string | null,
},
- mutedAds: {
- where: 'account',
- default: [] as string[],
- },
autoloadConversation: {
where: 'account',
default: true,
@@ -182,7 +219,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: true,
},
-
menu: {
where: 'deviceAccount',
default: [
@@ -198,18 +234,6 @@ export const defaultStore = markRaw(new Storage('base', {
'achievements',
],
},
- visibility: {
- where: 'deviceAccount',
- default: 'public' as (typeof Misskey.noteVisibilities)[number],
- },
- localOnly: {
- where: 'deviceAccount',
- default: false,
- },
- showPreview: {
- where: 'device',
- default: false,
- },
statusbars: {
where: 'deviceAccount',
default: [] as {
@@ -221,29 +245,6 @@ export const defaultStore = markRaw(new Storage('base', {
props: Record<string, any>;
}[],
},
- widgets: {
- where: 'account',
- default: [] as {
- name: string;
- id: string;
- place: string | null;
- data: Record<string, any>;
- }[],
- },
- tl: {
- where: 'deviceAccount',
- default: {
- src: 'home' as 'home' | 'local' | 'social' | 'global' | 'bubble' | `list:${string}`,
- userList: null as Misskey.entities.UserList | null,
- filter: {
- withReplies: true,
- withRenotes: true,
- withBots: true,
- withSensitive: true,
- onlyFiles: false,
- },
- },
- },
pinnedUserLists: {
where: 'deviceAccount',
default: [] as Misskey.entities.UserList[],
@@ -252,11 +253,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: defaultFollowingFeedState,
},
-
- overridedDeviceKind: {
- where: 'device',
- default: null as DeviceKind | null,
- },
serverDisconnectedBehavior: {
where: 'device',
default: 'disabled' as 'quiet' | 'dialog' | 'disabled',
@@ -361,10 +357,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
- darkMode: {
- where: 'device',
- default: false,
- },
instanceTicker: {
where: 'device',
default: 'remote' as 'none' | 'remote' | 'always',
@@ -385,22 +377,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: 'auto' as 'auto' | 'popup' | 'drawer',
},
- recentlyUsedEmojis: {
- where: 'device',
- default: [] as string[],
- },
- recentlyUsedUsers: {
- where: 'device',
- default: [] as string[],
- },
- defaultSideView: {
- where: 'device',
- default: false,
- },
- menuDisplay: {
- where: 'device',
- default: 'sideFull' as 'sideFull' | 'sideIcon' | 'top',
- },
reportError: {
where: 'device',
default: false,
@@ -413,18 +389,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: true,
},
- postFormWithHashtags: {
- where: 'device',
- default: false,
- },
- postFormHashtags: {
- where: 'device',
- default: '',
- },
- themeInitial: {
- where: 'device',
- default: true,
- },
numberOfPageCache: {
where: 'device',
default: 3,
@@ -489,18 +453,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: true,
},
- additionalUnicodeEmojiIndexes: {
- where: 'device',
- default: {} as Record<string, Record<string, string[]>>,
- },
keepScreenOn: {
where: 'device',
default: false,
},
- defaultWithReplies: {
- where: 'account',
- default: false,
- },
disableStreamingTimeline: {
where: 'device',
default: false,
@@ -522,17 +478,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
- dropAndFusion: {
- where: 'device',
- default: {
- bgmVolume: 0.25,
- sfxVolume: 1,
- },
- },
- hemisphere: {
- where: 'device',
- default: hemisphere as 'N' | 'S',
- },
enableHorizontalSwipe: {
where: 'device',
default: true,
@@ -565,7 +510,14 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
-
+ confirmOnReact: {
+ where: 'device',
+ default: false,
+ },
+ hemisphere: {
+ where: 'device',
+ default: hemisphere as 'N' | 'S',
+ },
sound_masterVolume: {
where: 'device',
default: 0.3,
@@ -580,56 +532,49 @@ export const defaultStore = markRaw(new Storage('base', {
},
sound_note: {
where: 'device',
- default: { type: 'syuilo/n-aec', volume: 0 } as SoundStore,
+ default: { type: 'syuilo/n-aec', volume: 1 },
},
sound_noteMy: {
where: 'device',
- default: { type: 'syuilo/n-cea-4va', volume: 1 } as SoundStore,
+ default: { type: 'syuilo/n-cea-4va', volume: 1 },
},
sound_notification: {
where: 'device',
- default: { type: 'syuilo/n-ea', volume: 1 } as SoundStore,
+ default: { type: 'syuilo/n-ea', volume: 1 },
},
sound_reaction: {
where: 'device',
- default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
+ default: { type: 'syuilo/bubble2', volume: 1 },
+ },
+ dropAndFusion: {
+ where: 'device',
+ default: {
+ bgmVolume: 0.25,
+ sfxVolume: 1,
+ },
},
+ //#endregion
}));
// TODO: 他のタブと永続化されたstateを同期
const PREFIX = 'miux:' as const;
-export type Plugin = {
- id: string;
- name: string;
- active: boolean;
- config?: Record<string, { default: any }>;
- configData: Record<string, any>;
- token: string;
- src: string | null;
- version: string;
- ast: Ast.Node[];
- author?: string;
- description?: string;
- permissions?: string[];
-};
-
interface Watcher {
key: string;
callback: (value: unknown) => void;
}
+// TODO: 消す(preferに移行済みのため)
/**
* 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ)
*/
-
export class ColdDeviceStorage {
public static default = {
- lightTheme,
- darkTheme,
- syncDeviceDarkMode: true,
- plugins: [] as Plugin[],
+ lightTheme, // TODO: 消す(preferに移行済みのため)
+ darkTheme, // TODO: 消す(preferに移行済みのため)
+ syncDeviceDarkMode: true, // TODO: 消す(preferに移行済みのため)
+ plugins: [] as Plugin[], // TODO: 消す(preferに移行済みのため)
};
public static watchers: Watcher[] = [];
diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts
index e63dac951c..25544d9d88 100644
--- a/packages/frontend/src/stream.ts
+++ b/packages/frontend/src/stream.ts
@@ -5,10 +5,10 @@
import * as Misskey from 'misskey-js';
import { markRaw } from 'vue';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { wsOrigin } from '@@/js/config.js';
// TODO: No WebsocketモードでStreamMockが使えそう
-//import { StreamMock } from '@/scripts/stream-mock.js';
+//import { StreamMock } from '@/utility/stream-mock.js';
// heart beat interval in ms
const HEART_BEAT_INTERVAL = 1000 * 60;
@@ -29,10 +29,10 @@ export function useStream(): Misskey.IStream {
timeoutHeartBeat = window.setTimeout(heartbeat, HEART_BEAT_INTERVAL);
// send heartbeat right now when last send time is over HEART_BEAT_INTERVAL
- document.addEventListener('visibilitychange', () => {
+ window.document.addEventListener('visibilitychange', () => {
if (
!stream
- || document.visibilityState !== 'visible'
+ || window.document.visibilityState !== 'visible'
|| Date.now() - lastHeartbeatCall < HEART_BEAT_INTERVAL
) return;
heartbeat();
@@ -42,7 +42,7 @@ export function useStream(): Misskey.IStream {
}
function heartbeat(): void {
- if (stream != null && document.visibilityState === 'visible') {
+ if (stream != null && window.document.visibilityState === 'visible') {
stream.heartbeat();
}
lastHeartbeatCall = Date.now();
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index 35443cdb8a..6568c738c5 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -111,6 +111,11 @@ html {
&.useSystemFont {
font-family: system-ui;
}
+
+ &:not(.forceSelectableAll) {
+ user-select: none;
+ -webkit-user-select: none;
+ }
}
html._themeChanging_ {
@@ -138,10 +143,6 @@ a {
outline-offset: 2px;
}
- &:hover {
- text-decoration: underline;
- }
-
&[target="_blank"] {
-webkit-touch-callout: default;
}
@@ -150,6 +151,8 @@ a {
textarea, input {
tap-highlight-color: transparent;
-webkit-tap-highlight-color: transparent;
+ user-select: text;
+ -webkit-user-select: text;
}
optgroup, option {
@@ -158,8 +161,9 @@ optgroup, option {
}
hr {
- margin: var(--MI-margin) 0 var(--MI-margin) 0;
+ margin: 0;
border: none;
+ border-radius: 999px;
height: 1px;
background: var(--MI_THEME-divider);
}
@@ -192,6 +196,29 @@ rt {
text-align: center;
}
+._pageContainer {
+ container-type: size;
+ contain: strict;
+ overflow: auto;
+ overscroll-behavior: contain;
+}
+
+._pageScrollable {
+ height: 100%;
+ overflow: clip;
+ overflow-y: scroll;
+ overscroll-behavior: contain;
+}
+
+._pageScrollableReversed {
+ height: 100%;
+ overflow: clip;
+ overflow-y: scroll;
+ overscroll-behavior: contain;
+ display: flex;
+ flex-direction: column-reverse;
+}
+
._indicatorCircle {
display: inline-block;
width: 1em;
@@ -213,6 +240,16 @@ rt {
padding: 0.3em 0.5em;
}
+._selectable {
+ user-select: text;
+ -webkit-user-select: text;
+}
+
+._selectableAtomic {
+ user-select: all;
+ -webkit-user-select: all;
+}
+
._noSelect {
user-select: none;
-webkit-user-select: none;
@@ -226,11 +263,6 @@ rt {
text-overflow: ellipsis;
}
-._ghost {
- @extend ._noSelect;
- pointer-events: none;
-}
-
._modalBg {
position: fixed;
top: 0;
@@ -333,13 +365,13 @@ rt {
._gaps_m {
display: flex;
flex-direction: column;
- gap: 1.5em;
+ gap: 21px;
}
._gaps_s {
display: flex;
flex-direction: column;
- gap: 0.75em;
+ gap: 10px;
}
._gaps {
@@ -457,6 +489,10 @@ rt {
color: var(--MI_THEME-link);
}
+._love {
+ color: var(--MI_THEME-love);
+}
+
._caption {
font-size: 0.8em;
opacity: 0.7;
diff --git a/packages/frontend/src/tab-id.ts b/packages/frontend/src/tab-id.ts
new file mode 100644
index 0000000000..49b69f72d2
--- /dev/null
+++ b/packages/frontend/src/tab-id.ts
@@ -0,0 +1,11 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { v4 as uuid } from 'uuid';
+
+// HMR有効時にバグか知らんけど複数回実行されるのでその対策
+export const TAB_ID = window.sessionStorage.getItem('TAB_ID') ?? uuid();
+window.sessionStorage.setItem('TAB_ID', TAB_ID);
+if (_DEV_) console.log('TAB_ID', TAB_ID);
diff --git a/packages/frontend/src/theme-store.ts b/packages/frontend/src/theme-store.ts
index c41cc17652..2ae5d8730e 100644
--- a/packages/frontend/src/theme-store.ts
+++ b/packages/frontend/src/theme-store.ts
@@ -3,28 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Theme, getBuiltinThemes } from '@/scripts/theme.js';
-import { miLocalStorage } from '@/local-storage.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { $i } from '@/account.js';
-
-const lsCacheKey = $i ? `themes:${$i.id}` as const : null;
+import type { Theme } from '@/theme.js';
+import { getBuiltinThemes } from '@/theme.js';
+import { $i } from '@/i.js';
+import { prefer } from '@/preferences.js';
export function getThemes(): Theme[] {
if ($i == null) return [];
- return JSON.parse(miLocalStorage.getItem(lsCacheKey!) ?? '[]');
-}
-
-export async function fetchThemes(): Promise<void> {
- if ($i == null) return;
-
- try {
- const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' });
- miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
- } catch (err) {
- if (err.code === 'NO_SUCH_KEY') return;
- throw err;
- }
+ return prefer.s.themes;
}
export async function addTheme(theme: Theme): Promise<void> {
@@ -33,15 +19,15 @@ export async function addTheme(theme: Theme): Promise<void> {
if (builtinThemes.some(t => t.id === theme.id)) {
throw new Error('builtin theme');
}
- await fetchThemes();
- const themes = getThemes().concat(theme);
- await misskeyApi('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
- miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
+ const themes = getThemes();
+ if (themes.some(t => t.id === theme.id)) {
+ throw new Error('already exists');
+ }
+ prefer.commit('themes', [...themes, theme]);
}
export async function removeTheme(theme: Theme): Promise<void> {
if ($i == null) return;
const themes = getThemes().filter(t => t.id !== theme.id);
- await misskeyApi('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
- miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
+ prefer.commit('themes', themes);
}
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/theme.ts
index 8242e7d2e4..4f61ab6e0e 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/theme.ts
@@ -7,10 +7,12 @@ import { ref } from 'vue';
import tinycolor from 'tinycolor2';
import lightTheme from '@@/themes/_light.json5';
import darkTheme from '@@/themes/_dark.json5';
-import { deepClone } from './clone.js';
+import JSON5 from 'json5';
import type { BundledTheme } from 'shiki/themes';
+import { deepClone } from '@/utility/clone.js';
import { globalEvents } from '@/events.js';
import { miLocalStorage } from '@/local-storage.js';
+import { addTheme, getThemes } from '@/theme-store.js';
export type Theme = {
id: string;
@@ -70,15 +72,18 @@ let timeout: number | null = null;
export function applyTheme(theme: Theme, persist = true) {
if (timeout) window.clearTimeout(timeout);
- document.documentElement.classList.add('_themeChanging_');
+ window.document.documentElement.classList.add('_themeChanging_');
timeout = window.setTimeout(() => {
- document.documentElement.classList.remove('_themeChanging_');
+ window.document.documentElement.classList.remove('_themeChanging_');
+
+ // 色計算など再度行えるようにクライアント全体に通知
+ globalEvents.emit('themeChanged');
}, 1000);
const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
- document.documentElement.dataset.colorScheme = colorScheme;
+ window.document.documentElement.dataset.colorScheme = colorScheme;
// Deep copy
const _theme = deepClone(theme);
@@ -90,7 +95,7 @@ export function applyTheme(theme: Theme, persist = true) {
const props = compile(_theme);
- for (const tag of document.head.children) {
+ for (const tag of window.document.head.children) {
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
tag.setAttribute('content', props['htmlThemeColor']);
break;
@@ -123,21 +128,22 @@ export function applyTheme(theme: Theme, persist = true) {
for (const [k, v] of Object.entries(props)) {
if (k.startsWith('font')) continue;
- document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
+ window.document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
}
- document.documentElement.style.setProperty('color-scheme', colorScheme);
+ window.document.documentElement.style.setProperty('color-scheme', colorScheme);
if (persist) {
miLocalStorage.setItem('theme', JSON.stringify(props));
+ miLocalStorage.setItem('themeId', theme.id);
miLocalStorage.setItem('colorScheme', colorScheme);
}
// 色計算など再度行えるようにクライアント全体に通知
- globalEvents.emit('themeChanged');
+ globalEvents.emit('themeChanging');
}
-function compile(theme: Theme): Record<string, string> {
+export function compile(theme: Theme): Record<string, string> {
function getColor(val: string): tinycolor.Instance {
if (val[0] === '@') { // ref (prop)
return getColor(theme.props[val.substring(1)]);
@@ -188,3 +194,32 @@ export function validateTheme(theme: Record<string, any>): boolean {
if (theme.props == null || typeof theme.props !== 'object') return false;
return true;
}
+
+export function parseThemeCode(code: string): Theme {
+ let theme;
+
+ try {
+ theme = JSON5.parse(code);
+ } catch (err) {
+ throw new Error('Failed to parse theme json');
+ }
+ if (!validateTheme(theme)) {
+ throw new Error('This theme is invaild');
+ }
+ if (getThemes().some(t => t.id === theme.id)) {
+ throw new Error('This theme is already installed');
+ }
+
+ return theme;
+}
+
+export function previewTheme(code: string): void {
+ const theme = parseThemeCode(code);
+ if (theme) applyTheme(theme, false);
+}
+
+export async function installTheme(code: string): Promise<void> {
+ const theme = parseThemeCode(code);
+ if (!theme) return;
+ await addTheme(theme);
+}
diff --git a/packages/frontend/src/timelines.ts b/packages/frontend/src/timelines.ts
index 5080ef4b96..c8ac726891 100644
--- a/packages/frontend/src/timelines.ts
+++ b/packages/frontend/src/timelines.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { instance } from '@/instance.js';
export const basicTimelineTypes = [
diff --git a/packages/frontend/src/types/menu.ts b/packages/frontend/src/types/menu.ts
index 138eb7dd62..820759ce61 100644
--- a/packages/frontend/src/types/menu.ts
+++ b/packages/frontend/src/types/menu.ts
@@ -4,27 +4,31 @@
*/
import * as Misskey from 'misskey-js';
-import { ComputedRef, Ref } from 'vue';
+import type { Component, ComputedRef, Ref } from 'vue';
+import type { ComponentProps as CP } from 'vue-component-type-helpers';
-interface MenuRadioOptionsDef extends Record<string, any> { }
+type ComponentProps<T extends Component> = { [K in keyof CP<T>]: CP<T>[K] | Ref<CP<T>[K]> };
+
+type MenuRadioOptionsDef = Record<string, any>;
export type MenuAction = (ev: MouseEvent) => void;
export type MenuDivider = { type: 'divider' };
export type MenuNull = undefined;
-export type MenuLabel = { type: 'label', text: string };
-export type MenuLink = { type: 'link', to: string, text: string, icon?: string, indicate?: boolean, avatar?: Misskey.entities.User };
-export type MenuA = { type: 'a', href: string, target?: string, download?: string, text: string, icon?: string, indicate?: boolean };
+export type MenuLabel = { type: 'label', text: string, caption?: string };
+export type MenuLink = { type: 'link', to: string, text: string, caption?: string, icon?: string, indicate?: boolean, avatar?: Misskey.entities.User };
+export type MenuA = { type: 'a', href: string, target?: string, download?: string, text: string, caption?: string, icon?: string, indicate?: boolean };
export type MenuUser = { type: 'user', user: Misskey.entities.User, active?: boolean, indicate?: boolean, action: MenuAction };
-export type MenuSwitch = { type: 'switch', ref: Ref<boolean>, text: string, icon?: string, disabled?: boolean | Ref<boolean> };
-export type MenuButton = { type?: 'button', text: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean | ComputedRef<boolean>, avatar?: Misskey.entities.User; action: MenuAction };
-export type MenuRadio = { type: 'radio', text: string, icon?: string, ref: Ref<MenuRadioOptionsDef[keyof MenuRadioOptionsDef]>, options: MenuRadioOptionsDef, disabled?: boolean | Ref<boolean> };
-export type MenuRadioOption = { type: 'radioOption', text: string, action: MenuAction; active?: boolean | ComputedRef<boolean> };
-export type MenuParent = { type: 'parent', text: string, icon?: string, children: MenuItem[] | (() => Promise<MenuItem[]> | MenuItem[]) };
+export type MenuSwitch = { type: 'switch', ref: Ref<boolean>, text: string, caption?: string, icon?: string, disabled?: boolean | Ref<boolean> };
+export type MenuButton = { type?: 'button', text: string, caption?: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean | ComputedRef<boolean>, avatar?: Misskey.entities.User; action: MenuAction };
+export type MenuRadio = { type: 'radio', text: string, caption?: string, icon?: string, ref: Ref<MenuRadioOptionsDef[keyof MenuRadioOptionsDef]>, options: MenuRadioOptionsDef, disabled?: boolean | Ref<boolean> };
+export type MenuRadioOption = { type: 'radioOption', text: string, caption?: string, action: MenuAction; active?: boolean | ComputedRef<boolean> };
+export type MenuComponent<T extends Component = any> = { type: 'component', component: T, props?: ComponentProps<T> };
+export type MenuParent = { type: 'parent', text: string, caption?: string, icon?: string, children: MenuItem[] | (() => Promise<MenuItem[]> | MenuItem[]) };
export type MenuPending = { type: 'pending' };
-type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuParent;
-type OuterPromiseMenuItem = Promise<MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuParent>;
+type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuComponent | MenuParent;
+type OuterPromiseMenuItem = Promise<MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuComponent | MenuParent>;
export type MenuItem = OuterMenuItem | OuterPromiseMenuItem;
-export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuParent;
+export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuComponent | MenuParent;
diff --git a/packages/frontend/src/ui/_common_/PreferenceRestore.vue b/packages/frontend/src/ui/_common_/PreferenceRestore.vue
new file mode 100644
index 0000000000..5fd9f5e44b
--- /dev/null
+++ b/packages/frontend/src/ui/_common_/PreferenceRestore.vue
@@ -0,0 +1,64 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root">
+ <span :class="$style.icon">
+ <i class="ti ti-info-circle"></i>
+ </span>
+ <span :class="$style.title">{{ i18n.ts._preferencesBackup.backupFound }}</span>
+ <span :class="$style.body"><button class="_textButton" @click="restore">{{ i18n.ts.restore }}</button> | <button class="_textButton" @click="skip">{{ i18n.ts.skip }}</button></span>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { $i } from '@/i.js';
+import { i18n } from '@/i18n.js';
+import { hideRestoreBackupSuggestion, restoreFromCloudBackup } from '@/preferences/utility.js';
+
+function restore() {
+ restoreFromCloudBackup();
+}
+
+function skip() {
+ hideRestoreBackupSuggestion();
+}
+</script>
+
+<style lang="scss" module>
+.root {
+ --height: 24px;
+ font-size: 0.85em;
+ display: flex;
+ vertical-align: bottom;
+ width: 100%;
+ line-height: var(--height);
+ height: var(--height);
+ overflow: clip;
+ contain: strict;
+ background: var(--MI_THEME-panel);
+}
+
+.icon {
+ margin-left: 10px;
+}
+
+.title {
+ padding: 0 10px;
+ font-weight: bold;
+
+ &:empty {
+ display: none;
+ }
+}
+
+.body {
+ min-width: 0;
+ flex: 1;
+ overflow: clip;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+</style>
diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue
index d153dc8726..f9af8e1ee7 100644
--- a/packages/frontend/src/ui/_common_/announcements.vue
+++ b/packages/frontend/src/ui/_common_/announcements.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index 6d36df9874..fcb9f2acfc 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -9,7 +9,7 @@ import * as os from '@/os.js';
import { instance } from '@/instance.js';
import { host } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
function toolsMenuItems(): MenuItem[] {
return [{
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index 227d9bb7e6..a7203b1eb9 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -17,18 +17,18 @@ SPDX-License-Identifier: AGPL-3.0-only
<TransitionGroup
tag="div"
:class="[$style.notifications, {
- [$style.notificationsPosition_leftTop]: defaultStore.state.notificationPosition === 'leftTop',
- [$style.notificationsPosition_leftBottom]: defaultStore.state.notificationPosition === 'leftBottom',
- [$style.notificationsPosition_rightTop]: defaultStore.state.notificationPosition === 'rightTop',
- [$style.notificationsPosition_rightBottom]: defaultStore.state.notificationPosition === 'rightBottom',
- [$style.notificationsStackAxis_vertical]: defaultStore.state.notificationStackAxis === 'vertical',
- [$style.notificationsStackAxis_horizontal]: defaultStore.state.notificationStackAxis === 'horizontal',
+ [$style.notificationsPosition_leftTop]: prefer.s.notificationPosition === 'leftTop',
+ [$style.notificationsPosition_leftBottom]: prefer.s.notificationPosition === 'leftBottom',
+ [$style.notificationsPosition_rightTop]: prefer.s.notificationPosition === 'rightTop',
+ [$style.notificationsPosition_rightBottom]: prefer.s.notificationPosition === 'rightBottom',
+ [$style.notificationsStackAxis_vertical]: prefer.s.notificationStackAxis === 'vertical',
+ [$style.notificationsStackAxis_horizontal]: prefer.s.notificationStackAxis === 'horizontal',
}]"
- :moveClass="defaultStore.state.animation ? $style.transition_notification_move : ''"
- :enterActiveClass="defaultStore.state.animation ? $style.transition_notification_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''"
+ :moveClass="prefer.s.animation ? $style.transition_notification_move : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_notification_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_notification_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_notification_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_notification_leaveTo : ''"
>
<div
v-for="notification in notifications" :key="notification.id" :class="$style.notification" :style="{
@@ -56,13 +56,13 @@ import * as Misskey from 'misskey-js';
import { swInject } from './sw-inject.js';
import XNotification from './notification.vue';
import { popups } from '@/os.js';
-import { pendingApiRequestsCount } from '@/scripts/misskey-api.js';
-import { uploads } from '@/scripts/upload.js';
-import * as sound from '@/scripts/sound.js';
-import { $i } from '@/account.js';
+import { pendingApiRequestsCount } from '@/utility/misskey-api.js';
+import { uploads } from '@/utility/upload.js';
+import * as sound from '@/utility/sound.js';
+import { $i } from '@/i.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
import { globalEvents } from '@/events.js';
const SkOneko = defineAsyncComponent(() => import('@/components/SkOneko.vue'));
@@ -75,7 +75,7 @@ const dev = _DEV_;
const notifications = ref<Misskey.entities.Notification[]>([]);
function onNotification(notification: Misskey.entities.Notification, isClient = false) {
- if (document.visibilityState === 'visible') {
+ if (window.document.visibilityState === 'visible') {
if (!isClient && notification.type !== 'test') {
// サーバーサイドのテスト通知の際は自動で既読をつけない(テストできないので)
useStream().send('readNotification');
diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
index aa72de6089..18d9f2f5f8 100644
--- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
+++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
@@ -53,12 +53,13 @@ import { computed, defineAsyncComponent, toRef } from 'vue';
import { openInstanceMenu } from './common.js';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar.js';
-import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
+import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
+import { $i } from '@/i.js';
-const menu = toRef(defaultStore.state, 'menu');
+const menu = toRef(prefer.s, 'menu');
const otherMenuItemIndicated = computed(() => {
for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue;
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 062a8faf3f..d590455ae5 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -9,12 +9,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.top">
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
- <img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl || '/apple-touch-icon.png'" alt="" :class="instance.sidebarLogoUrl && !iconOnly ? $style.wideInstanceIcon : $style.instanceIcon"/>
+ <img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="instance.sidebarLogoUrl && !iconOnly ? $style.wideInstanceIcon : $style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
</button>
</div>
<div :class="$style.middle">
<MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact>
- <i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span>
+ <i :class="$style.itemIcon" class="ti ti-home ti-fw" style="viewTransitionName: navbar-homeIcon;"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span>
</MkA>
<template v-for="item in menu">
<div v-if="item === '-'" :class="$style.divider"></div>
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
: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>
+ <i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]" :style="{ viewTransitionName: 'navbar-item-' + item }"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span>
<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>
@@ -37,14 +37,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<div :class="$style.divider"></div>
<MkA v-if="$i != null && ($i.isAdmin || $i.isModerator)" v-tooltip.noDelay.right="i18n.ts.controlPanel" :class="$style.item" :activeClass="$style.active" to="/admin">
- <i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span>
+ <i :class="$style.itemIcon" class="ti ti-dashboard ti-fw" style="viewTransitionName: navbar-controlPanel;"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span>
</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>
+ <i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw" style="viewTransitionName: navbar-more;"></i><span :class="$style.itemText">{{ i18n.ts.more }}</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>
+ <i :class="$style.itemIcon" class="ti ti-settings ti-fw" style="viewTransitionName: navbar-settings;"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span>
</MkA>
</div>
<div :class="$style.bottom">
@@ -52,25 +52,39 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
</button>
<button v-if="$i != null" v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="_button" :class="[$style.account]" @click="openAccountMenu">
- <MkAvatar :user="$i" :class="$style.avatar"/><MkAcct class="_nowrap" :class="$style.acct" :user="$i"/>
+ <MkAvatar :user="$i" :class="$style.avatar" style="viewTransitionName: navbar-avatar;"/><MkAcct class="_nowrap" :class="$style.acct" :user="$i"/>
</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>
+
+ <!--
+ <svg viewBox="0 0 16 48" :class="$style.subButtonShape">
+ <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>
+ -->
+
+ <div v-if="!forceIconOnly" :class="$style.subButtons">
+ <div :class="[$style.subButton, $style.menuEditButton]">
+ <svg viewBox="0 0 16 64" :class="$style.subButtonShape">
+ <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>
+ <button class="_button" :class="$style.subButtonClickable" @click="menuEdit"><i :class="$style.subButtonIcon" class="ti ti-settings-2"></i></button>
+ </div>
+ <div :class="$style.subButtonGapFill"></div>
+ <div :class="$style.subButtonGapFillDivider"></div>
+ <div :class="[$style.subButton, $style.toggleButton]">
+ <svg viewBox="0 0 16 64" :class="$style.subButtonShape">
+ <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>
+ <button class="_button" :class="$style.subButtonClickable" @click="toggleIconOnly"><i v-if="iconOnly" class="ti ti-chevron-right" :class="$style.subButtonIcon"></i><i v-else class="ti ti-chevron-left" :class="$style.subButtonIcon"></i></button>
+ </div>
+ </div>
</div>
</template>
@@ -79,18 +93,23 @@ import { computed, defineAsyncComponent, ref, watch } from 'vue';
import { openInstanceMenu } from './common.js';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar.js';
-import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
-import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
+import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
+import { useRouter } from '@/router.js';
+import { prefer } from '@/preferences.js';
+import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
+import { $i } from '@/i.js';
+
+const router = useRouter();
const forceIconOnly = ref(window.innerWidth <= 1279);
const iconOnly = computed(() => {
- return forceIconOnly.value || (defaultStore.reactiveState.menuDisplay.value === 'sideIcon');
+ return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon');
});
-const menu = computed(() => defaultStore.state.menu);
+const menu = computed(() => prefer.s.menu);
const otherMenuItemIndicated = computed(() => {
for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue;
@@ -105,12 +124,18 @@ function calcViewState() {
window.addEventListener('resize', calcViewState);
-watch(defaultStore.reactiveState.menuDisplay, () => {
+watch(store.r.menuDisplay, () => {
calcViewState();
});
function toggleIconOnly() {
- defaultStore.set('menuDisplay', iconOnly.value ? 'sideFull' : 'sideIcon');
+ if (window.document.startViewTransition && prefer.s.animation) {
+ window.document.startViewTransition(() => {
+ store.set('menuDisplay', iconOnly.value ? 'sideFull' : 'sideIcon');
+ });
+ } else {
+ store.set('menuDisplay', iconOnly.value ? 'sideFull' : 'sideIcon');
+ }
}
function openAccountMenu(ev: MouseEvent) {
@@ -128,6 +153,10 @@ function more(ev: MouseEvent) {
closed: () => dispose(),
});
}
+
+function menuEdit() {
+ router.push('/settings/navbar');
+}
</script>
<style lang="scss" module>
@@ -136,6 +165,8 @@ function more(ev: MouseEvent) {
--nav-icon-only-width: 80px;
--nav-bg-transparent: color(from var(--MI_THEME-navBg) srgb r g b / 0.5);
+ --subButtonWidth: 20px;
+
flex: 0 0 var(--nav-width);
width: var(--nav-width);
box-sizing: border-box;
@@ -171,23 +202,80 @@ function more(ev: MouseEvent) {
direction: ltr;
}
-.toggleButton {
+.subButtons {
position: fixed;
- bottom: 20px;
left: var(--nav-width);
+ bottom: 80px;
z-index: 1001;
- width: 16px;
- height: 64px;
box-sizing: border-box;
}
-.toggleButtonShape {
+.subButton {
+ display: block;
+ position: relative;
+ z-index: 1002;
+ width: var(--subButtonWidth);
+ height: 50px;
+ box-sizing: border-box;
+ align-content: center;
+}
+
+.subButtonShape {
position: absolute;
z-index: -1;
top: 0;
+ bottom: 0;
left: 0;
- width: 16px;
+ margin: auto;
+ width: var(--subButtonWidth);
+ height: calc(var(--subButtonWidth) * 4);
+}
+
+.subButtonClickable {
+ position: absolute;
+ display: block;
+ max-width: unset;
+ width: 24px;
+ height: 42px;
+ top: 0;
+ bottom: 0;
+ left: -4px;
+ margin: auto;
+ font-size: 10px;
+
+ &:hover {
+ color: var(--MI_THEME-fgHighlighted);
+
+ .subButtonIcon {
+ opacity: 1;
+ }
+ }
+}
+
+.subButtonIcon {
+ margin-left: -4px;
+ opacity: 0.7;
+}
+
+.subButtonGapFill {
+ position: relative;
+ z-index: 1001;
+ width: var(--subButtonWidth);
height: 64px;
+ margin-top: -32px;
+ margin-bottom: -32px;
+ pointer-events: none;
+ background: var(--MI_THEME-navBg);
+}
+
+.subButtonGapFillDivider {
+ position: relative;
+ z-index: 1010;
+ margin-left: -2px;
+ width: 14px;
+ height: 1px;
+ background: var(--MI_THEME-divider);
+ pointer-events: none;
}
.root:not(.iconOnly) {
@@ -426,7 +514,7 @@ function more(ev: MouseEvent) {
font-size: 0.9em;
}
- .toggleButton {
+ .subButtons {
left: var(--nav-width);
}
}
@@ -630,7 +718,7 @@ function more(ev: MouseEvent) {
}
}
- .toggleButton {
+ .subButtons {
left: var(--nav-icon-only-width);
}
}
diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue
index e234bb3a33..16e72fa227 100644
--- a/packages/frontend/src/ui/_common_/statusbar-federation.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue
@@ -34,9 +34,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import MarqueeText from '@/components/MkMarquee.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
-import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
+import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
const props = defineProps<{
display?: 'marquee' | 'oneByOne';
diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue
index da8fa8bb21..4da89a181e 100644
--- a/packages/frontend/src/ui/_common_/statusbar-rss.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue
@@ -31,7 +31,7 @@ import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import MarqueeText from '@/components/MkMarquee.vue';
import { useInterval } from '@@/js/use-interval.js';
-import { shuffle } from '@/scripts/shuffle.js';
+import { shuffle } from '@/utility/shuffle.js';
const props = defineProps<{
url?: string;
diff --git a/packages/frontend/src/ui/_common_/statusbar-user-list.vue b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
index 078b595dca..c5bee51162 100644
--- a/packages/frontend/src/ui/_common_/statusbar-user-list.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
@@ -34,9 +34,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MarqueeText from '@/components/MkMarquee.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
-import { getNoteSummary } from '@/scripts/get-note-summary.js';
+import { getNoteSummary } from '@/utility/get-note-summary.js';
import { notePage } from '@/filters/note.js';
const props = defineProps<{
diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue
index ed881bef22..a8d87599e6 100644
--- a/packages/frontend/src/ui/_common_/statusbars.vue
+++ b/packages/frontend/src/ui/_common_/statusbars.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
<div
- v-for="x in defaultStore.reactiveState.statusbars.value" :key="x.id" :class="[$style.item, { [$style.black]: x.black,
+ v-for="x in prefer.r.statusbars.value" :key="x.id" :class="[$style.item, { [$style.black]: x.black,
[$style.verySmall]: x.size === 'verySmall',
[$style.small]: x.size === 'small',
[$style.large]: x.size === 'large',
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { instance } from '@/instance.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const XRss = defineAsyncComponent(() => import('./statusbar-rss.vue'));
const XFederation = defineAsyncComponent(() => import('./statusbar-federation.vue'));
const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue'));
diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue
index cc62a28b14..5f7600881f 100644
--- a/packages/frontend/src/ui/_common_/stream-indicator.vue
+++ b/packages/frontend/src/ui/_common_/stream-indicator.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div v-if="hasDisconnected && defaultStore.state.serverDisconnectedBehavior === 'quiet'" :class="$style.root" class="_panel _shadow" @click="resetDisconnected">
+<div v-if="hasDisconnected && prefer.s.serverDisconnectedBehavior === 'quiet'" :class="$style.root" class="_panel _shadow" @click="resetDisconnected">
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.disconnectedFromServer }}</div>
<div :class="$style.command" class="_buttons">
<MkButton small primary @click="reload">{{ i18n.ts.reload }}</MkButton>
@@ -19,7 +19,7 @@ import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const zIndex = os.claimZIndex('high');
@@ -34,7 +34,7 @@ function resetDisconnected() {
}
function reload() {
- location.reload();
+ window.location.reload();
}
useStream().on('_disconnected_', onDisconnected);
diff --git a/packages/frontend/src/ui/_common_/sw-inject.ts b/packages/frontend/src/ui/_common_/sw-inject.ts
index ff851ad99f..1459881ba1 100644
--- a/packages/frontend/src/ui/_common_/sw-inject.ts
+++ b/packages/frontend/src/ui/_common_/sw-inject.ts
@@ -4,11 +4,12 @@
*/
import { post } from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { $i, login } from '@/account.js';
-import { getAccountFromId } from '@/scripts/get-account-from-id.js';
-import { deepClone } from '@/scripts/clone.js';
-import { mainRouter } from '@/router/main.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { $i } from '@/i.js';
+import { getAccountFromId } from '@/utility/get-account-from-id.js';
+import { deepClone } from '@/utility/clone.js';
+import { mainRouter } from '@/router.js';
+import { login } from '@/accounts.js';
export function swInject() {
navigator.serviceWorker.addEventListener('message', async ev => {
diff --git a/packages/frontend/src/ui/_common_/upload.vue b/packages/frontend/src/ui/_common_/upload.vue
index 12de579d90..1135d236fc 100644
--- a/packages/frontend/src/ui/_common_/upload.vue
+++ b/packages/frontend/src/ui/_common_/upload.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
import * as os from '@/os.js';
-import { uploads } from '@/scripts/upload.js';
+import { uploads } from '@/utility/upload.js';
import { i18n } from '@/i18n.js';
const zIndex = os.claimZIndex('high');
diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue
index f4633314ae..7d4235bd4e 100644
--- a/packages/frontend/src/ui/classic.header.vue
+++ b/packages/frontend/src/ui/classic.header.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="body">
<div class="left">
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
- <img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/>
+ <img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/>
</button>
<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact>
<i class="ti ti-home ti-fw"></i>
@@ -51,17 +51,18 @@ import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
import { openInstanceMenu } from './_common_/common.js';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar.js';
-import { openAccountMenu as openAccountMenu_, $i } from '@/account.js';
import MkButton from '@/components/MkButton.vue';
-import { defaultStore } from '@/store.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
+import { prefer } from '@/preferences.js';
+import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
+import { $i } from '@/i.js';
const WINDOW_THRESHOLD = 1400;
const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD);
-const menu = ref(defaultStore.state.menu);
-// const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
+const menu = ref(prefer.s.menu);
+// const menuDisplay = computed(store.makeGetterSetter('menuDisplay'));
const otherNavItemIndicated = computed<boolean>(() => {
for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue;
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index f17027bcde..6e81f72549 100644
--- a/packages/frontend/src/ui/classic.sidebar.vue
+++ b/packages/frontend/src/ui/classic.sidebar.vue
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="divider"></div>
<div class="about">
<button v-click-anime class="item _button" @click="openInstanceMenu">
- <img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" :class="{ wideIcon: instance.sidebarLogoUrl && !iconOnly }" class="_ghost" />
+ <img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" :class="{ wideIcon: instance.sidebarLogoUrl && !iconOnly }" class="_ghost" draggable="false" />
</button>
</div>
<!--<MisskeyLogo class="misskey"/>-->
@@ -49,23 +49,25 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, computed, watch, ref, shallowRef } from 'vue';
+import { defineAsyncComponent, computed, watch, ref, useTemplateRef } from 'vue';
import { openInstanceMenu } from './_common_/common.js';
// import { host } from '@@/js/config.js';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar.js';
-import { openAccountMenu as openAccountMenu_, $i } from '@/account.js';
import MkButton from '@/components/MkButton.vue';
-// import { StickySidebar } from '@/scripts/sticky-sidebar.js';
+// import { StickySidebar } from '@/utility/sticky-sidebar.js';
// import { mainRouter } from '@/router.js';
//import MisskeyLogo from '@assets/client/sharkey.svg';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
+import { prefer } from '@/preferences.js';
+import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
+import { $i } from '@/i.js';
const WINDOW_THRESHOLD = 1400;
-const menu = ref(defaultStore.state.menu);
+const menu = ref(prefer.s.menu);
const otherNavItemIndicated = computed<boolean>(() => {
for (const def in navbarItemDef) {
if (menu.value.includes(def)) continue;
@@ -73,7 +75,7 @@ const otherNavItemIndicated = computed<boolean>(() => {
}
return false;
});
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
// let accounts = $ref([]);
// let connection = $ref(null);
const iconOnly = ref(false);
@@ -100,7 +102,7 @@ function openAccountMenu(ev: MouseEvent) {
}, ev);
}
-watch(defaultStore.reactiveState.menuDisplay, () => {
+watch(store.r.menuDisplay, () => {
calcViewState();
});
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index f8cd5fa8be..c252b03c82 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
- <Transition :name="defaultStore.state.animation ? 'tray-back' : ''">
+ <Transition :name="prefer.s.animation ? 'tray-back' : ''">
<div
v-if="widgetsShowing"
class="tray-back _modalBg"
@@ -35,29 +35,32 @@ SPDX-License-Identifier: AGPL-3.0-only
></div>
</Transition>
- <Transition :name="defaultStore.state.animation ? 'tray' : ''">
+ <Transition :name="prefer.s.animation ? 'tray' : ''">
<XWidgets v-if="widgetsShowing" class="tray"/>
</Transition>
- <iframe v-if="defaultStore.state.aiChanMode" ref="live2d" class="ivnzpscs" src="https://misskey-dev.github.io/mascot-web/?scale=2&y=1.4"></iframe>
+ <iframe v-if="prefer.s.aiChanMode" ref="live2d" class="ivnzpscs" src="https://misskey-dev.github.io/mascot-web/?scale=2&y=1.4"></iframe>
<XCommon/>
</div>
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, onMounted, provide, ref, computed, shallowRef } from 'vue';
+import { defineAsyncComponent, onMounted, provide, ref, computed, useTemplateRef } from 'vue';
+import { instanceName } from '@@/js/config.js';
+import { isLink } from '@@/js/is-link.js';
import XSidebar from './classic.sidebar.vue';
import XCommon from './_common_/common.vue';
-import { instanceName } from '@@/js/config.js';
-import { StickySidebar } from '@/scripts/sticky-sidebar.js';
+import type { PageMetadata } from '@/page.js';
+import { StickySidebar } from '@/utility/sticky-sidebar.js';
import * as os from '@/os.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import { defaultStore } from '@/store.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
+import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
-import { mainRouter } from '@/router/main.js';
-import { isLink } from '@@/js/is-link.js';
+import { mainRouter } from '@/router.js';
+import { prefer } from '@/preferences.js';
+import { DI } from '@/di.js';
const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue'));
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
@@ -73,20 +76,20 @@ const widgetsShowing = ref(false);
const fullView = ref(false);
const globalHeaderHeight = ref(0);
const wallpaper = miLocalStorage.getItem('wallpaper') != null;
-const showMenuOnTop = computed(() => defaultStore.state.menuDisplay === 'top');
-const live2d = shallowRef<HTMLIFrameElement>();
+const showMenuOnTop = computed(() => store.s.menuDisplay === 'top');
+const live2d = useTemplateRef('live2d');
const widgetsLeft = ref<HTMLElement>();
const widgetsRight = ref<HTMLElement>();
-provide('router', mainRouter);
+provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
if (pageMetadata.value) {
if (isRoot.value && pageMetadata.value.title === instanceName) {
- document.title = pageMetadata.value.title;
+ window.document.title = pageMetadata.value.title;
} else {
- document.title = `${pageMetadata.value.title} | ${instanceName}`;
+ window.document.title = `${pageMetadata.value.title} | ${instanceName}`;
}
}
});
@@ -95,7 +98,7 @@ provide('shouldHeaderThin', showMenuOnTop.value);
provide('forceSpacerMin', true);
function attachSticky(el: HTMLElement) {
- const sticky = new StickySidebar(el, 0, defaultStore.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
+ const sticky = new StickySidebar(el, 0, store.s.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
window.addEventListener('scroll', () => {
sticky.calc(window.scrollY);
}, { passive: true });
@@ -109,7 +112,7 @@ function onContextmenu(ev: MouseEvent) {
if (isLink(ev.target)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
if (window.getSelection().toString() !== '') return;
- const path = mainRouter.getCurrentPath();
+ const path = mainRouter.getCurrentFullPath();
os.contextMenu([{
type: 'label',
text: path,
@@ -136,32 +139,17 @@ if (window.innerWidth < 1024) {
const currentUI = miLocalStorage.getItem('ui');
miLocalStorage.setItem('ui_temp', currentUI ?? 'default');
miLocalStorage.setItem('ui', 'default');
- location.reload();
+ window.location.reload();
}
-document.documentElement.style.overflowY = 'scroll';
-
-defaultStore.loaded.then(() => {
- if (defaultStore.state.widgets.length === 0) {
- defaultStore.set('widgets', [{
- name: 'calendar',
- id: 'a', place: null, data: {},
- }, {
- name: 'notifications',
- id: 'b', place: null, data: {},
- }, {
- name: 'trends',
- id: 'c', place: null, data: {},
- }]);
- }
-});
+window.document.documentElement.style.overflowY = 'scroll';
onMounted(() => {
window.addEventListener('resize', () => {
isDesktop.value = (window.innerWidth >= DESKTOP_THRESHOLD);
}, { passive: true });
- if (defaultStore.state.aiChanMode) {
+ if (prefer.s.aiChanMode) {
const iframeRect = live2d.value.getBoundingClientRect();
window.addEventListener('mousemove', ev => {
live2d.value.contentWindow.postMessage({
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 36caca5fc0..18094b4444 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.main">
<XAnnouncements v-if="$i"/>
<XStatusBars/>
- <div ref="columnsEl" :class="[$style.sections, { [$style.center]: deckStore.reactiveState.columnAlign.value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.self="onWheel">
+ <div ref="columnsEl" :class="[$style.sections, { [$style.center]: prefer.r['deck.columnAlign'].value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu" @wheel.self="onWheel">
<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
<section
v-for="ids in layout"
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="$style.sideMenu">
<div :class="$style.sideMenuTop">
- <button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${deckStore.state.profile}`" :class="$style.sideMenuButton" class="_button" @click="changeProfile"><i class="ti ti-caret-down"></i></button>
+ <button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${prefer.s['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="switchProfileMenu"><i class="ti ti-caret-down"></i></button>
<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
</div>
<div :class="$style.sideMenuMiddle">
@@ -67,10 +67,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
>
<div
v-if="drawerMenuShowing"
@@ -82,10 +82,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</Transition>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_menuDrawer_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawer_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_menuDrawer_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''"
>
<div v-if="drawerMenuShowing" :class="$style.menu">
<XDrawerMenu/>
@@ -97,22 +97,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, defineAsyncComponent, ref, watch, shallowRef } from 'vue';
+import { computed, defineAsyncComponent, ref, useTemplateRef } from 'vue';
import { v4 as uuid } from 'uuid';
import XCommon from './_common_/common.vue';
-import { deckStore, columnTypes, addColumn as addColumnToStore, forceSaveDeck, 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';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
-import { deviceKind } from '@/scripts/device-kind.js';
-import { defaultStore } from '@/store.js';
+import { deviceKind } from '@/utility/device-kind.js';
+import { prefer } from '@/preferences.js';
import XMainColumn from '@/ui/deck/main-column.vue';
import XTlColumn from '@/ui/deck/tl-column.vue';
import XAntennaColumn from '@/ui/deck/antenna-column.vue';
@@ -124,7 +120,8 @@ import XMentionsColumn from '@/ui/deck/mentions-column.vue';
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 { mainRouter } from '@/router.js';
+import { columns, layout, columnTypes, switchProfileMenu, addColumn as addColumnToStore, deleteProfile as deleteProfile_ } from '@/deck.js';
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
@@ -144,8 +141,8 @@ const columnComponents = {
mainRouter.navHook = (path, flag): boolean => {
if (flag === 'forcePage') return false;
- const noMainColumn = !deckStore.state.columns.some(x => x.type === 'main');
- if (deckStore.state.navWindow || noMainColumn) {
+ const noMainColumn = !columns.value.some(x => x.type === 'main');
+ if (prefer.s['deck.navWindow'] || noMainColumn) {
os.pageWindow(path);
return true;
}
@@ -167,8 +164,6 @@ watch(route, () => {
});
*/
-const columns = deckStore.reactiveState.columns;
-const layout = deckStore.reactiveState.layout;
const menuIndicated = computed(() => {
if ($i == null) return false;
for (const def in navbarItemDef) {
@@ -181,7 +176,7 @@ function showSettings() {
os.pageWindow('/settings/deck');
}
-const columnsEl = shallowRef<HTMLElement>();
+const columnsEl = useTemplateRef('columnsEl');
const addColumn = async (ev) => {
const { canceled, result: column } = await os.select({
@@ -195,7 +190,7 @@ const addColumn = async (ev) => {
addColumnToStore({
type: column,
id: uuid(),
- name: i18n.ts._deck._columns[column],
+ name: null,
width: 330,
soundSetting: { type: null, volume: 1 },
});
@@ -214,68 +209,23 @@ function onWheel(ev: WheelEvent) {
}
}
-document.documentElement.style.overflowY = 'hidden';
-document.documentElement.style.scrollBehavior = 'auto';
-
-loadDeck();
-
-function changeProfile(ev: MouseEvent) {
- let items: MenuItem[] = [{
- text: deckStore.state.profile,
- active: true,
- action: () => {},
- }];
- getProfiles().then(profiles => {
- items.push(...(profiles.filter(k => k !== deckStore.state.profile).map(k => ({
- text: k,
- action: () => {
- deckStore.set('profile', k);
- unisonReload();
- },
- }))), { type: 'divider' as const }, {
- text: i18n.ts._deck.newProfile,
- icon: 'ti ti-plus',
- action: async () => {
- const { canceled, result: name } = await os.inputText({
- title: i18n.ts._deck.profile,
- minLength: 1,
- });
-
- if (canceled || name == null) return;
-
- os.promiseDialog((async () => {
- await deckStore.set('profile', name);
- await forceSaveDeck();
- })(), () => {
- unisonReload();
- });
- },
- });
- }).then(() => {
- os.popupMenu(items, ev.currentTarget ?? ev.target);
- });
-}
+window.document.documentElement.style.overflowY = 'hidden';
+window.document.documentElement.style.scrollBehavior = 'auto';
async function deleteProfile() {
+ if (prefer.s['deck.profile'] == null) return;
+
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.tsx.deleteAreYouSure({ x: deckStore.state.profile }),
+ text: i18n.tsx.deleteAreYouSure({ x: prefer.s['deck.profile'] }),
});
if (canceled) return;
- os.promiseDialog((async () => {
- if (deckStore.state.profile === 'default') {
- await deckStore.set('columns', []);
- await deckStore.set('layout', []);
- await forceSaveDeck();
- } else {
- await deleteProfile_(deckStore.state.profile);
- }
- await deckStore.set('profile', 'default');
- })(), () => {
- unisonReload();
- });
+ await deleteProfile_(prefer.s['deck.profile']);
+
+ os.success();
}
+
</script>
<style>
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index a41639e71c..194b56c842 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
- <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name }}</span>
+ <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name || antennaName || i18n.ts._deck._columns.antenna }}</span>
</template>
<MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @note="onNote"/>
@@ -14,27 +14,29 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, shallowRef, watch, defineAsyncComponent } from 'vue';
-import type { entities as MisskeyEntities } from 'misskey-js';
+import { onMounted, ref, useTemplateRef, watch, defineAsyncComponent } from 'vue';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import type { entities as MisskeyEntities } from 'misskey-js';
+import type { Column } from '@/deck.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { SoundStore } from '@/preferences/def.js';
+import { updateColumn } from '@/deck.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import type { MenuItem } from '@/types/menu.js';
import { antennasCache } from '@/cache.js';
-import { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
-import * as sound from '@/scripts/sound.js';
+import * as sound from '@/utility/sound.js';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
+const antennaName = ref<string | null>(null);
onMounted(() => {
if (props.column.antennaId == null) {
@@ -42,6 +44,13 @@ onMounted(() => {
}
});
+watch([() => props.column.name, () => props.column.antennaId], () => {
+ if (!props.column.name && props.column.antennaId) {
+ misskeyApi('antennas/show', { antennaId: props.column.antennaId })
+ .then(value => antennaName.value = value.name);
+ }
+});
+
watch(soundSetting, v => {
updateColumn(props.column.id, { soundSetting: v });
});
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index 661d45b110..39376108d4 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
- <i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name }}</span>
+ <i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name || channel?.name || i18n.ts._deck._columns.channel }}</span>
</template>
<template v-if="column.channelId">
@@ -19,27 +19,28 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, watch } from 'vue';
+import { onMounted, ref, shallowRef, watch, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import type { Column } from '@/deck.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { SoundStore } from '@/preferences/def.js';
+import { updateColumn } from '@/deck.js';
import MkTimeline from '@/components/MkTimeline.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { favoritedChannelsCache } from '@/cache.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import type { MenuItem } from '@/types/menu.js';
-import { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
-import * as sound from '@/scripts/sound.js';
+import * as sound from '@/utility/sound.js';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
const channel = shallowRef<Misskey.entities.Channel>();
const withRenotes = ref(props.column.withRenotes ?? true);
const onlyFiles = ref(props.column.onlyFiles ?? false);
@@ -58,9 +59,18 @@ watch(onlyFiles, v => {
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
-if (props.column.channelId == null) {
- setChannel();
-}
+onMounted(() => {
+ if (props.column.channelId == null) {
+ setChannel();
+ }
+});
+
+watch([() => props.column.name, () => props.column.channelId], () => {
+ if (!props.column.name && props.column.channelId) {
+ misskeyApi('channels/show', { channelId: props.column.channelId })
+ .then(value => channel.value = value);
+ }
+});
watch(soundSetting, v => {
updateColumn(props.column.id, { soundSetting: v });
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 2eb232096e..febf8ca6f8 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -42,11 +42,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onBeforeUnmount, onMounted, provide, watch, shallowRef, ref, computed } from 'vue';
-import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store.js';
+import { onBeforeUnmount, onMounted, provide, watch, useTemplateRef, ref, computed } from 'vue';
+import type { Column } from '@/deck.js';
+import type { MenuItem } from '@/types/menu.js';
+import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from '@/deck.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import type { MenuItem } from '@/types/menu.js';
provide('shouldHeaderThin', true);
provide('shouldOmitHeaderTitle', true);
@@ -67,7 +68,7 @@ const emit = defineEmits<{
(ev: 'headerWheel', ctx: WheelEvent): void;
}>();
-const body = shallowRef<HTMLDivElement | null>();
+const body = useTemplateRef('body');
const dragging = ref(false);
watch(dragging, v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'));
@@ -99,7 +100,7 @@ function onOtherDragEnd() {
function toggleActive() {
if (!props.isStacked) return;
updateColumn(props.column.id, {
- active: !props.column.active,
+ active: props.column.active == null ? false : !props.column.active,
});
}
@@ -128,7 +129,8 @@ function getMenu() {
icon: 'ti ti-settings',
text: i18n.ts._deck.configureColumn,
action: async () => {
- const { canceled, result } = await os.form(props.column.name, {
+ const name = props.column.name ?? i18n.ts._deck._columns[props.column.type];
+ const { canceled, result } = await os.form(name, {
name: {
type: 'string',
label: i18n.ts.name,
@@ -143,7 +145,7 @@ function getMenu() {
flexible: {
type: 'boolean',
label: i18n.ts._deck.flexible,
- default: props.column.flexible,
+ default: props.column.flexible ?? null,
},
});
if (canceled) return;
@@ -356,7 +358,6 @@ function onDrop(ev) {
> .body {
background: var(--MI_THEME-bg) !important;
- overflow-y: scroll !important;
scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent;
&::-webkit-scrollbar-track {
diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts
index 8e5b1dd1ac..c58b5d7aad 100644
--- a/packages/frontend/src/ui/deck/deck-store.ts
+++ b/packages/frontend/src/ui/deck/deck-store.ts
@@ -3,59 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { throttle } from 'throttle-debounce';
-import { computed, markRaw, Ref } from 'vue';
-import { notificationTypes } from 'misskey-js';
-import type { BasicTimelineType } from '@/timelines.js';
-import { Storage } from '@/pizzax.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { deepClone } from '@/scripts/clone.js';
-import { SoundStore } from '@/store.js';
+import { markRaw } from 'vue';
+import type { Column } from '@/deck.js';
+import { Pizzax } from '@/lib/pizzax.js';
-type ColumnWidget = {
- name: string;
- id: string;
- data: Record<string, any>;
-};
-
-export const columnTypes = [
- 'main',
- 'widgets',
- 'notifications',
- 'tl',
- 'antenna',
- 'list',
- 'channel',
- 'mentions',
- 'direct',
- 'roleTimeline',
- 'following',
-] as const;
-
-export type ColumnType = typeof columnTypes[number];
-
-export type Column = {
- id: string;
- type: ColumnType;
- name: string | null;
- width: number;
- widgets?: ColumnWidget[];
- active?: boolean;
- flexible?: boolean;
- antennaId?: string;
- listId?: string;
- channelId?: string;
- roleId?: string;
- excludeTypes?: typeof notificationTypes[number][];
- tl?: BasicTimelineType;
- withRenotes?: boolean;
- withReplies?: boolean;
- withSensitive?: boolean;
- onlyFiles?: boolean;
- soundSetting: SoundStore;
-};
-
-export const deckStore = markRaw(new Storage('deck', {
+// TODO: 消す(移行済みのため)
+export const deckStore = markRaw(new Pizzax('deck', {
profile: {
where: 'deviceAccount',
default: 'default',
@@ -68,278 +21,4 @@ export const deckStore = markRaw(new Storage('deck', {
where: 'deviceAccount',
default: [] as Column['id'][][],
},
- columnAlign: {
- where: 'deviceAccount',
- default: 'left' as 'left' | 'right' | 'center',
- },
- alwaysShowMainColumn: {
- where: 'deviceAccount',
- default: true,
- },
- navWindow: {
- where: 'deviceAccount',
- default: true,
- },
- useSimpleUiForNonRootPages: {
- where: 'deviceAccount',
- default: true,
- },
}));
-
-export const loadDeck = async () => {
- let deck;
-
- try {
- deck = await misskeyApi('i/registry/get', {
- scope: ['client', 'deck', 'profiles'],
- key: deckStore.state.profile,
- });
- } catch (err) {
- if (err.code === 'NO_SUCH_KEY') {
- // 後方互換性のため
- if (deckStore.state.profile === 'default') {
- saveDeck();
- return;
- }
-
- deckStore.set('columns', []);
- deckStore.set('layout', []);
- return;
- }
- throw err;
- }
-
- deckStore.set('columns', deck.columns);
- deckStore.set('layout', deck.layout);
-};
-
-export async function forceSaveDeck() {
- await misskeyApi('i/registry/set', {
- scope: ['client', 'deck', 'profiles'],
- key: deckStore.state.profile,
- value: {
- columns: deckStore.reactiveState.columns.value,
- layout: deckStore.reactiveState.layout.value,
- },
- });
-}
-
-// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
-export const saveDeck = throttle(1000, () => {
- forceSaveDeck();
-});
-
-export async function getProfiles(): Promise<string[]> {
- return await misskeyApi('i/registry/keys', {
- scope: ['client', 'deck', 'profiles'],
- });
-}
-
-export async function deleteProfile(key: string): Promise<void> {
- return await misskeyApi('i/registry/remove', {
- scope: ['client', 'deck', 'profiles'],
- key: key,
- });
-}
-
-export function addColumn(column: Column) {
- if (column.name === undefined) column.name = null;
- deckStore.push('columns', column);
- deckStore.push('layout', [column.id]);
- saveDeck();
-}
-
-export function removeColumn(id: Column['id']) {
- deckStore.set('columns', deckStore.state.columns.filter(c => c.id !== id));
- deckStore.set('layout', deckStore.state.layout
- .map(ids => ids.filter(_id => _id !== id))
- .filter(ids => ids.length > 0));
- saveDeck();
-}
-
-export function swapColumn(a: Column['id'], b: Column['id']) {
- const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) !== -1);
- const aY = deckStore.state.layout[aX].findIndex(id => id === a);
- const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1);
- const bY = deckStore.state.layout[bX].findIndex(id => id === b);
- const layout = deepClone(deckStore.state.layout);
- layout[aX][aY] = b;
- layout[bX][bY] = a;
- deckStore.set('layout', layout);
- saveDeck();
-}
-
-export function swapLeftColumn(id: Column['id']) {
- const layout = deepClone(deckStore.state.layout);
- deckStore.state.layout.some((ids, i) => {
- if (ids.includes(id)) {
- const left = deckStore.state.layout[i - 1];
- if (left) {
- layout[i - 1] = deckStore.state.layout[i];
- layout[i] = left;
- deckStore.set('layout', layout);
- }
- return true;
- }
- });
- saveDeck();
-}
-
-export function swapRightColumn(id: Column['id']) {
- const layout = deepClone(deckStore.state.layout);
- deckStore.state.layout.some((ids, i) => {
- if (ids.includes(id)) {
- const right = deckStore.state.layout[i + 1];
- if (right) {
- layout[i + 1] = deckStore.state.layout[i];
- layout[i] = right;
- deckStore.set('layout', layout);
- }
- return true;
- }
- });
- saveDeck();
-}
-
-export function swapUpColumn(id: Column['id']) {
- const layout = deepClone(deckStore.state.layout);
- const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
- const ids = deepClone(deckStore.state.layout[idsIndex]);
- ids.some((x, i) => {
- if (x === id) {
- const up = ids[i - 1];
- if (up) {
- ids[i - 1] = id;
- ids[i] = up;
-
- layout[idsIndex] = ids;
- deckStore.set('layout', layout);
- }
- return true;
- }
- });
- saveDeck();
-}
-
-export function swapDownColumn(id: Column['id']) {
- const layout = deepClone(deckStore.state.layout);
- const idsIndex = deckStore.state.layout.findIndex(ids => ids.includes(id));
- const ids = deepClone(deckStore.state.layout[idsIndex]);
- ids.some((x, i) => {
- if (x === id) {
- const down = ids[i + 1];
- if (down) {
- ids[i + 1] = id;
- ids[i] = down;
-
- layout[idsIndex] = ids;
- deckStore.set('layout', layout);
- }
- return true;
- }
- });
- saveDeck();
-}
-
-export function stackLeftColumn(id: Column['id']) {
- let layout = deepClone(deckStore.state.layout);
- const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
- layout = layout.map(ids => ids.filter(_id => _id !== id));
- layout[i - 1].push(id);
- layout = layout.filter(ids => ids.length > 0);
- deckStore.set('layout', layout);
- saveDeck();
-}
-
-export function popRightColumn(id: Column['id']) {
- let layout = deepClone(deckStore.state.layout);
- const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
- const affected = layout[i];
- layout = layout.map(ids => ids.filter(_id => _id !== id));
- layout.splice(i + 1, 0, [id]);
- layout = layout.filter(ids => ids.length > 0);
- deckStore.set('layout', layout);
-
- const columns = deepClone(deckStore.state.columns);
- for (const column of columns) {
- if (affected.includes(column.id)) {
- column.active = true;
- }
- }
- deckStore.set('columns', columns);
-
- saveDeck();
-}
-
-export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
- const columns = deepClone(deckStore.state.columns);
- const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
- const column = deepClone(deckStore.state.columns[columnIndex]);
- if (column == null) return;
- if (column.widgets == null) column.widgets = [];
- column.widgets.unshift(widget);
- columns[columnIndex] = column;
- deckStore.set('columns', columns);
- saveDeck();
-}
-
-export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
- const columns = deepClone(deckStore.state.columns);
- const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
- const column = deepClone(deckStore.state.columns[columnIndex]);
- if (column == null || column.widgets == null) return;
- column.widgets = column.widgets.filter(w => w.id !== widget.id);
- columns[columnIndex] = column;
- deckStore.set('columns', columns);
- saveDeck();
-}
-
-export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
- const columns = deepClone(deckStore.state.columns);
- const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
- const column = deepClone(deckStore.state.columns[columnIndex]);
- if (column == null) return;
- column.widgets = widgets;
- columns[columnIndex] = column;
- deckStore.set('columns', columns);
- saveDeck();
-}
-
-export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
- const columns = deepClone(deckStore.state.columns);
- const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
- const column = deepClone(deckStore.state.columns[columnIndex]);
- if (column == null || column.widgets == null) return;
- column.widgets = column.widgets.map(w => w.id === widgetId ? {
- ...w,
- data: widgetData,
- } : w);
- columns[columnIndex] = column;
- deckStore.set('columns', columns);
- saveDeck();
-}
-
-export async function updateColumn<TColumn>(id: Column['id'], column: Partial<TColumn>) {
- const columns = deepClone(deckStore.state.columns);
- const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
- const currentColumn = deepClone(deckStore.state.columns[columnIndex]);
- if (currentColumn == null) return;
- for (const [k, v] of Object.entries(column)) {
- currentColumn[k] = v;
- }
- columns[columnIndex] = currentColumn;
- await Promise.all([
- deckStore.set('columns', columns),
- saveDeck(),
- ]);
-}
-
-export function getColumn<TColumn extends Column>(id: Column['id']): TColumn {
- return deckStore.state.columns.find(c => c.id === id) as TColumn;
-}
-
-export function getReactiveColumn<TColumn extends Column>(id: Column['id']): Ref<TColumn> {
- return computed(() => {
- return deckStore.reactiveState.columns.value.find(c => c.id === id) as TColumn;
- });
-}
diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue
index d12a18f760..772188d773 100644
--- a/packages/frontend/src/ui/deck/direct-column.vue
+++ b/packages/frontend/src/ui/deck/direct-column.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()">
- <template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name }}</template>
+ <template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.direct }}</template>
<MkNotes ref="tlComponent" :pagination="pagination"/>
</XColumn>
@@ -14,8 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import XColumn from './column.vue';
-import { Column } from './deck-store.js';
+import type { Column } from '@/deck.js';
import MkNotes from '@/components/MkNotes.vue';
+import { i18n } from '@/i18n.js';
defineProps<{
column: Column;
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index 8762fb0cce..77c1ea80a7 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
- <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span>
+ <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ (column.name || listName) ?? i18n.ts._deck._columns.list }}</span>
</template>
<MkTimeline v-if="column.listId" ref="timeline" :key="column.listId + column.withRenotes + column.onlyFiles" src="list" :list="column.listId" :withRenotes="withRenotes" :onlyFiles="onlyFiles" @note="onNote"/>
@@ -14,33 +14,44 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { watch, shallowRef, ref } from 'vue';
-import type { entities as MisskeyEntities } from 'misskey-js';
+import { watch, useTemplateRef, ref, onMounted } from 'vue';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import type { entities as MisskeyEntities } from 'misskey-js';
+import type { Column } from '@/deck.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { SoundStore } from '@/preferences/def.js';
+import { updateColumn } from '@/deck.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import type { MenuItem } from '@/types/menu.js';
-import { SoundStore } from '@/store.js';
import { userListsCache } from '@/cache.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
-import * as sound from '@/scripts/sound.js';
+import * as sound from '@/utility/sound.js';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
const withRenotes = ref(props.column.withRenotes ?? true);
const onlyFiles = ref(props.column.onlyFiles ?? false);
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
+const listName = ref<string | null>(null);
-if (props.column.listId == null) {
- setList();
-}
+onMounted(() => {
+ if (props.column.listId == null) {
+ setList();
+ }
+});
+
+watch([() => props.column.name, () => props.column.listId], () => {
+ if (!props.column.name && props.column.listId) {
+ misskeyApi('users/lists/show', { listId: props.column.listId })
+ .then(value => listName.value = value.name);
+ }
+});
watch(withRenotes, v => {
updateColumn(props.column.id, {
diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index f8c712c371..78454d2e49 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :isStacked="isStacked">
+<XColumn v-if="prefer.s['deck.alwaysShowMainColumn'] || mainRouter.currentRoute.value.name !== 'index'" :column="column" :isStacked="isStacked">
<template #header>
<template v-if="pageMetadata">
<i :class="pageMetadata.icon"></i>
@@ -12,33 +12,34 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</template>
- <div ref="contents">
- <RouterView @contextmenu.stop="onContextmenu"/>
+ <div style="height: 100%;">
+ <StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" @contextmenu.stop="onContextmenu"/>
+ <RouterView v-else @contextmenu.stop="onContextmenu"/>
</div>
</XColumn>
</template>
<script lang="ts" setup>
import { provide, shallowRef, ref } from 'vue';
+import { isLink } from '@@/js/is-link.js';
import XColumn from './column.vue';
-import { deckStore, Column } from '@/ui/deck/deck-store.js';
+import type { Column } from '@/deck.js';
+import type { PageMetadata } from '@/page.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import { useScrollPositionManager } from '@/nirax.js';
-import { getScrollContainer } from '@@/js/scroll.js';
-import { isLink } from '@@/js/is-link.js';
-import { mainRouter } from '@/router/main.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
+import { mainRouter } from '@/router.js';
+import { prefer } from '@/preferences.js';
+import { DI } from '@/di.js';
defineProps<{
column: Column;
isStacked: boolean;
}>();
-const contents = shallowRef<HTMLElement>();
const pageMetadata = ref<null | PageMetadata>(null);
-provide('router', mainRouter);
+provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
@@ -68,6 +69,4 @@ function onContextmenu(ev: MouseEvent) {
},
}], ev);
}
-
-useScrollPositionManager(() => getScrollContainer(contents.value), mainRouter);
</script>
diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue
index 7b25a55ec3..ffd0307940 100644
--- a/packages/frontend/src/ui/deck/mentions-column.vue
+++ b/packages/frontend/src/ui/deck/mentions-column.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()">
- <template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name }}</template>
+ <template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.mentions }}</template>
<MkNotes ref="tlComponent" :pagination="pagination"/>
</XColumn>
@@ -14,8 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import XColumn from './column.vue';
-import { Column } from './deck-store.js';
+import type { Column } from '@/deck.js';
import MkNotes from '@/components/MkNotes.vue';
+import { i18n } from '../../i18n.js';
defineProps<{
column: Column;
diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue
index 19ccfc1f7c..8378dddfef 100644
--- a/packages/frontend/src/ui/deck/notifications-column.vue
+++ b/packages/frontend/src/ui/deck/notifications-column.vue
@@ -5,16 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :column="column" :isStacked="isStacked" :menu="menu" :refresher="async () => { await notificationsComponent?.reload() }">
- <template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name }}</template>
+ <template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.notifications }}</template>
<XNotifications ref="notificationsComponent" :excludeTypes="props.column.excludeTypes"/>
</XColumn>
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, shallowRef } from 'vue';
+import { defineAsyncComponent, useTemplateRef } from 'vue';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import type { Column } from '@/deck.js';
+import { updateColumn } from '@/deck.js';
import XNotifications from '@/components/MkNotifications.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
@@ -24,7 +25,7 @@ const props = defineProps<{
isStacked: boolean;
}>();
-const notificationsComponent = shallowRef<InstanceType<typeof XNotifications>>();
+const notificationsComponent = useTemplateRef('notificationsComponent');
function func() {
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSelectWindow.vue')), {
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index beb4237978..468b3e49e0 100644
--- a/packages/frontend/src/ui/deck/role-timeline-column.vue
+++ b/packages/frontend/src/ui/deck/role-timeline-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
- <i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name }}</span>
+ <i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name || roleName || i18n.ts._deck._columns.roleTimeline }}</span>
</template>
<MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId" @note="onNote"/>
@@ -14,25 +14,27 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, shallowRef, watch } from 'vue';
+import { onMounted, ref, useTemplateRef, watch } from 'vue';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import type { Column } from '@/deck.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { SoundStore } from '@/preferences/def.js';
+import { updateColumn } from '@/deck.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import type { MenuItem } from '@/types/menu.js';
-import { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
-import * as sound from '@/scripts/sound.js';
+import * as sound from '@/utility/sound.js';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
+const roleName = ref<string | null>(null);
onMounted(() => {
if (props.column.roleId == null) {
@@ -40,6 +42,13 @@ onMounted(() => {
}
});
+watch([() => props.column.name, () => props.column.roleId], () => {
+ if (!props.column.name && props.column.roleId) {
+ misskeyApi('roles/show', { roleId: props.column.roleId })
+ .then(value => roleName.value = value.name);
+ }
+});
+
watch(soundSetting, v => {
updateColumn(props.column.id, { soundSetting: v });
});
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index 8f5553ccae..c3e68dadf0 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
<i v-if="column.tl != null" :class="basicTimelineIconClass(column.tl)"/>
- <span style="margin-left: 8px;">{{ column.name }}</span>
+ <span style="margin-left: 8px;">{{ column.name || (column.tl ? i18n.ts._timelines[column.tl] : null) || i18n.ts._deck._columns.tl }}</span>
</template>
<div v-if="!isAvailableBasicTimeline(column.tl)" :class="$style.disabled">
@@ -32,25 +32,25 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, watch, ref, shallowRef, computed } from 'vue';
+import { onMounted, watch, ref, useTemplateRef, computed } from 'vue';
import XColumn from './column.vue';
-import { removeColumn, updateColumn, Column } from './deck-store.js';
+import type { Column } from '@/deck.js';
import type { MenuItem } from '@/types/menu.js';
+import type { SoundStore } from '@/preferences/def.js';
+import { removeColumn, updateColumn } from '@/deck.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { hasWithReplies, isAvailableBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
-import { instance } from '@/instance.js';
-import { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
-import * as sound from '@/scripts/sound.js';
+import * as sound from '@/utility/sound.js';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
const withRenotes = ref(props.column.withRenotes ?? true);
diff --git a/packages/frontend/src/ui/deck/tl-note-notification.ts b/packages/frontend/src/ui/deck/tl-note-notification.ts
index 275ea56ba0..728c0d0d29 100644
--- a/packages/frontend/src/ui/deck/tl-note-notification.ts
+++ b/packages/frontend/src/ui/deck/tl-note-notification.ts
@@ -4,9 +4,10 @@
*/
import * as Misskey from 'misskey-js';
-import { Ref } from 'vue';
-import { SoundStore } from '@/store.js';
-import { getSoundDuration, playMisskeySfxFile, soundsTypes, SoundType } from '@/scripts/sound.js';
+import type { Ref } from 'vue';
+import type { SoundType } from '@/utility/sound.js';
+import type { SoundStore } from '@/preferences/def.js';
+import { getSoundDuration, playMisskeySfxFile, soundsTypes } from '@/utility/sound.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue
index a0e62c8264..4e84ef0ba0 100644
--- a/packages/frontend/src/ui/deck/widgets-column.vue
+++ b/packages/frontend/src/ui/deck/widgets-column.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :naked="true" :column="column" :isStacked="isStacked">
- <template #header><i class="ti ti-apps" style="margin-right: 8px;"></i>{{ column.name }}</template>
+ <template #header><i class="ti ti-apps" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns[props.column.type] }}</template>
<div :class="$style.root">
<div v-if="!(column.widgets && column.widgets.length > 0) && !edit" :class="$style.intro">{{ i18n.ts._deck.widgetsIntroduction }}</div>
@@ -17,7 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import XColumn from './column.vue';
-import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store.js';
+import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from '@/deck.js';
+import type { Column } from '@/deck.js';
import XWidgets from '@/components/MkWidgets.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
index 9e41c48c5b..ec20ac1114 100644
--- a/packages/frontend/src/ui/minimum.vue
+++ b/packages/frontend/src/ui/minimum.vue
@@ -5,9 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
- <div style="container-type: inline-size;">
- <RouterView/>
- </div>
+ <RouterView/>
<XCommon/>
</div>
@@ -15,35 +13,35 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, provide, ref } from 'vue';
-import XCommon from './_common_/common.vue';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
import { instanceName } from '@@/js/config.js';
-import { mainRouter } from '@/router/main.js';
+import XCommon from './_common_/common.vue';
+import type { PageMetadata } from '@/page.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
+import { mainRouter } from '@/router.js';
+import { DI } from '@/di.js';
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
const pageMetadata = ref<null | PageMetadata>(null);
-provide('router', mainRouter);
+provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
if (pageMetadata.value) {
if (isRoot.value && pageMetadata.value.title === instanceName) {
- document.title = pageMetadata.value.title;
+ window.document.title = pageMetadata.value.title;
} else {
- document.title = `${pageMetadata.value.title} | ${instanceName}`;
+ window.document.title = `${pageMetadata.value.title} | ${instanceName}`;
}
}
});
provideReactiveMetadata(pageMetadata);
-
-document.documentElement.style.overflowY = 'scroll';
</script>
<style lang="scss" module>
.root {
- min-height: 100dvh;
- box-sizing: border-box;
+ position: relative;
+ height: 100dvh;
}
</style>
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index e8c71f61cf..6724c6f6c9 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -7,16 +7,29 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root">
<XSidebar v-if="!isMobile" :class="$style.sidebar"/>
- <MkStickyContainer ref="contents" :class="$style.contents" style="container-type: inline-size;" @contextmenu.stop="onContextmenu">
- <template #header>
- <div>
- <XAnnouncements v-if="$i"/>
- <XStatusBars :class="$style.statusbars"/>
- </div>
- </template>
- <RouterView/>
- <div :class="$style.spacer"></div>
- </MkStickyContainer>
+ <div :class="$style.contents" @contextmenu.stop="onContextmenu">
+ <div>
+ <XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
+ <XAnnouncements v-if="$i"/>
+ <XStatusBars :class="$style.statusbars"/>
+ </div>
+ <div :class="$style.content">
+ <StackingRouterView v-if="prefer.s['experimental.stackingRouterView']"/>
+ <RouterView v-else/>
+ </div>
+ <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" 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" class="_blink">
+ <span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span>
+ </span>
+ </button>
+ <button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon" class="ti ti-apps"></i></button>
+ <button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button>
+ </div>
+ </div>
<div v-if="isDesktop && !pageMetadata?.needWideArea" :class="$style.widgets">
<XWidgets/>
@@ -24,24 +37,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" 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" class="_blink">
- <span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span>
- </span>
- </button>
- <button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon" class="ti ti-apps"></i></button>
- <button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button>
- </div>
-
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
>
<div
v-if="drawerMenuShowing"
@@ -53,10 +53,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</Transition>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_menuDrawer_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawer_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_menuDrawer_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''"
>
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
<XDrawerMenu/>
@@ -64,10 +64,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</Transition>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
>
<div
v-if="widgetsShowing"
@@ -79,10 +79,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</Transition>
<Transition
- :enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterActive : ''"
- :leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
- :enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
- :leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
+ :enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
>
<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
@@ -95,28 +95,30 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef, Ref } from 'vue';
+import { defineAsyncComponent, provide, onMounted, computed, ref, watch, useTemplateRef } 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 type { Ref } from 'vue';
+import type { PageMetadata } from '@/page.js';
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
import * as os from '@/os.js';
-import { defaultStore } from '@/store.js';
import { navbarItemDef } from '@/navbar.js';
import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { $i } from '@/i.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
+import { deviceKind } from '@/utility/device-kind.js';
import { miLocalStorage } from '@/local-storage.js';
-import { useScrollPositionManager } from '@/nirax.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
+import { prefer } from '@/preferences.js';
+import { shouldSuggestRestoreBackup } from '@/preferences/utility.js';
+import { DI } from '@/di.js';
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
+const XPreferenceRestore = defineAsyncComponent(() => import('@/ui/_common_/PreferenceRestore.vue'));
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
@@ -132,18 +134,17 @@ window.addEventListener('resize', () => {
const pageMetadata = ref<null | PageMetadata>(null);
const widgetsShowing = ref(false);
-const navFooter = shallowRef<HTMLElement>();
-const contents = shallowRef<InstanceType<typeof MkStickyContainer>>();
+const navFooter = useTemplateRef('navFooter');
-provide('router', mainRouter);
+provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
if (pageMetadata.value) {
if (isRoot.value && pageMetadata.value.title === instanceName) {
- document.title = pageMetadata.value.title;
+ window.document.title = pageMetadata.value.title;
} else {
- document.title = `${pageMetadata.value.title} | ${instanceName}`;
+ window.document.title = `${pageMetadata.value.title} | ${instanceName}`;
}
}
});
@@ -168,25 +169,10 @@ if (window.innerWidth > 1024) {
if (tempUI) {
miLocalStorage.setItem('ui', tempUI);
miLocalStorage.removeItem('ui_temp');
- location.reload();
+ window.location.reload();
}
}
-defaultStore.loaded.then(() => {
- if (defaultStore.state.widgets.length === 0) {
- defaultStore.set('widgets', [{
- name: 'calendar',
- id: 'a', place: 'right', data: {},
- }, {
- name: 'notifications',
- id: 'b', place: 'right', data: {},
- }, {
- name: 'trends',
- id: 'c', place: 'right', data: {},
- }]);
- }
-});
-
onMounted(() => {
if (!isDesktop.value) {
window.addEventListener('resize', () => {
@@ -199,7 +185,7 @@ const onContextmenu = (ev) => {
if (isLink(ev.target)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
if (window.getSelection()?.toString() !== '') return;
- const path = mainRouter.getCurrentPath();
+ const path = mainRouter.getCurrentFullPath();
os.contextMenu([{
type: 'label',
text: path,
@@ -212,31 +198,19 @@ const onContextmenu = (ev) => {
}], ev);
};
-function top() {
- contents.value.rootEl.scrollTo({
- top: 0,
- behavior: 'smooth',
- });
-}
-
const navFooterHeight = ref(0);
-provide<Ref<number>>(CURRENT_STICKY_BOTTOM, navFooterHeight);
watch(navFooter, () => {
if (navFooter.value) {
navFooterHeight.value = navFooter.value.offsetHeight;
- document.body.style.setProperty('--MI-stickyBottom', `${navFooterHeight.value}px`);
- document.body.style.setProperty('--MI-minBottomSpacing', 'var(--MI-minBottomSpacingMobile)');
+ window.document.body.style.setProperty('--MI-minBottomSpacing', 'var(--MI-minBottomSpacingMobile)');
} else {
navFooterHeight.value = 0;
- document.body.style.setProperty('--MI-stickyBottom', '0px');
- document.body.style.setProperty('--MI-minBottomSpacing', '0px');
+ window.document.body.style.setProperty('--MI-minBottomSpacing', '0px');
}
}, {
immediate: true,
});
-
-useScrollPositionManager(() => contents.value.rootEl, mainRouter);
</script>
<style>
@@ -322,87 +296,27 @@ $widgets-hide-threshold: 1090px;
}
.contents {
+ display: flex;
+ flex-direction: column;
flex: 1;
height: 100%;
min-width: 0;
- overflow: auto;
- overflow-y: scroll;
- overscroll-behavior: unset;
background: var(--MI_THEME-bg);
}
-.widgets {
- width: 350px;
- height: 100%;
- box-sizing: border-box;
- overflow: auto;
- 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;
- }
-}
-
-.widgetButton {
- display: block;
- position: fixed;
- z-index: 1000;
- bottom: 32px;
- right: 32px;
- width: 64px;
- height: 64px;
- 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(--MI_THEME-panel);
-}
-
-.widgetsDrawerBg {
- z-index: 1001;
-}
-
-.widgetsDrawer {
- position: fixed;
- top: 0;
- right: 0;
- z-index: 1001;
- width: 310px;
- height: 100dvh;
- 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(--MI_THEME-bg);
-}
-
-.widgetsCloseButton {
- padding: 8px;
- display: block;
- margin: 0 auto;
-}
-
-@media (min-width: 370px) {
- .widgetsCloseButton {
- display: none;
- }
+.content {
+ flex: 1;
+ min-height: 0;
}
.nav {
- position: fixed;
- z-index: 1000;
- bottom: 0;
- left: 0;
padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
grid-gap: 8px;
width: 100%;
box-sizing: border-box;
- -webkit-backdrop-filter: var(--MI-blur, blur(24px));
- backdrop-filter: var(--MI-blur, blur(24px));
- background-color: var(--MI_THEME-header);
+ background: var(--MI_THEME-bg);
border-top: solid 0.5px var(--MI_THEME-divider);
}
@@ -486,7 +400,61 @@ $widgets-hide-threshold: 1090px;
left: 0;
}
-.spacer {
- height: calc(var(--MI-minBottomSpacing));
+.widgets {
+ width: 350px;
+ height: 100%;
+ box-sizing: border-box;
+ overflow: auto;
+ 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;
+ }
+}
+
+.widgetButton {
+ display: block;
+ position: fixed;
+ z-index: 1000;
+ bottom: 32px;
+ right: 32px;
+ width: 64px;
+ height: 64px;
+ border-radius: 100%;
+ 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(--MI_THEME-panel);
+}
+
+.widgetsDrawerBg {
+ z-index: 1001;
+}
+
+.widgetsDrawer {
+ position: fixed;
+ top: 0;
+ right: 0;
+ z-index: 1001;
+ width: 310px;
+ height: 100dvh;
+ 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(--MI_THEME-bg);
+}
+
+.widgetsCloseButton {
+ padding: 8px;
+ display: block;
+ margin: 0 auto;
+}
+
+@media (min-width: 370px) {
+ .widgetsCloseButton {
+ display: none;
+ }
}
</style>
diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue
index fc0a4475d2..1a6d62e19b 100644
--- a/packages/frontend/src/ui/universal.widgets.vue
+++ b/packages/frontend/src/ui/universal.widgets.vue
@@ -19,7 +19,7 @@ const editMode = ref(false);
<script lang="ts" setup>
import XWidgets from '@/components/MkWidgets.vue';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
// null = 全てのウィジェットを表示
@@ -31,24 +31,24 @@ const props = withDefaults(defineProps<{
});
const widgets = computed(() => {
- if (props.place === null) return defaultStore.reactiveState.widgets.value;
- if (props.place === 'left') return defaultStore.reactiveState.widgets.value.filter(w => w.place === 'left');
- return defaultStore.reactiveState.widgets.value.filter(w => w.place !== 'left');
+ if (props.place === null) return prefer.r.widgets.value;
+ if (props.place === 'left') return prefer.r.widgets.value.filter(w => w.place === 'left');
+ return prefer.r.widgets.value.filter(w => w.place !== 'left');
});
function addWidget(widget) {
- defaultStore.set('widgets', [{
+ prefer.commit('widgets', [{
...widget,
place: props.place,
- }, ...defaultStore.state.widgets]);
+ }, ...prefer.s.widgets]);
}
function removeWidget(widget) {
- defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id !== widget.id));
+ prefer.commit('widgets', prefer.s.widgets.filter(w => w.id !== widget.id));
}
function updateWidget({ id, data }) {
- defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? {
+ prefer.commit('widgets', prefer.s.widgets.map(w => w.id === id ? {
...w,
data,
place: props.place,
@@ -57,18 +57,18 @@ function updateWidget({ id, data }) {
function updateWidgets(thisWidgets) {
if (props.place === null) {
- defaultStore.set('widgets', thisWidgets);
+ prefer.commit('widgets', thisWidgets);
return;
}
if (props.place === 'left') {
- defaultStore.set('widgets', [
+ prefer.commit('widgets', [
...thisWidgets.map(w => ({ ...w, place: 'left' })),
- ...defaultStore.state.widgets.filter(w => w.place !== 'left' && !thisWidgets.some(t => w.id === t.id)),
+ ...prefer.s.widgets.filter(w => w.place !== 'left' && !thisWidgets.some(t => w.id === t.id)),
]);
return;
}
- defaultStore.set('widgets', [
- ...defaultStore.state.widgets.filter(w => w.place === 'left' && !thisWidgets.some(t => w.id === t.id)),
+ prefer.commit('widgets', [
+ ...prefer.s.widgets.filter(w => w.place === 'left' && !thisWidgets.some(t => w.id === t.id)),
...thisWidgets.map(w => ({ ...w, place: 'right' })),
]);
}
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index f048ac2124..8fef1fd997 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -4,81 +4,40 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="mk-app">
- <div v-if="!narrow && !isRoot" class="side">
- <div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
- <div class="dashboard">
+<div :class="$style.root">
+ <a v-if="isRoot" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--MI_THEME-panel); color:var(--MI_THEME-fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
+
+ <div v-if="!narrow && !isRoot" :class="$style.side">
+ <div :class="$style.banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
+ <div :class="$style.dashboard">
<MkVisitorDashboard/>
</div>
</div>
- <div class="main">
- <div v-if="!isRoot" class="header">
- <div v-if="narrow === false" class="wide">
- <MkA to="/" class="link" activeClass="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA>
- <MkA v-if="isTimelineAvailable" to="/timeline" class="link" activeClass="active"><i class="ph-chat-text ph-bold ph-lg icon"></i> {{ i18n.ts.timeline }}</MkA>
- <MkA to="/explore" class="link" activeClass="active"><i class="ti ti-hash icon"></i> {{ i18n.ts.explore }}</MkA>
- <MkA to="/channels" class="link" activeClass="active"><i class="ti ti-device-tv icon"></i> {{ i18n.ts.channel }}</MkA>
- </div>
- <div v-else-if="narrow === true" class="narrow">
- <button class="menu _button" @click="showMenu = true">
- <i class="ti ti-menu-2 icon"></i>
- </button>
- </div>
- </div>
- <div class="contents">
- <main v-if="!isRoot" style="container-type: inline-size;">
- <RouterView/>
- </main>
- <main v-else>
- <RouterView/>
- </main>
+ <div :class="$style.main">
+ <button v-if="!isRoot" :class="$style.homeButton" class="_button" @click="goHome">
+ <i class="ti ti-home"></i>
+ </button>
+ <div :class="$style.content">
+ <RouterView/>
</div>
</div>
-
- <Transition :name="'tray-back'">
- <div
- v-if="showMenu"
- class="menu-back _modalBg"
- @click="showMenu = false"
- @touchstart.passive="showMenu = false"
- ></div>
- </Transition>
-
- <Transition :name="'tray'">
- <div v-if="showMenu" class="menu">
- <MkA to="/" class="link" activeClass="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA>
- <MkA v-if="isTimelineAvailable" to="/timeline" class="link" activeClass="active"><i class="ph-chat-text ph-bold ph-lg icon"></i>{{ i18n.ts.timeline }}</MkA>
- <MkA to="/explore" class="link" activeClass="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA>
- <MkA to="/announcements" class="link" activeClass="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA>
- <MkA to="/channels" class="link" activeClass="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA>
- <div class="divider"></div>
- <MkA to="/pages" class="link" activeClass="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA>
- <MkA to="/play" class="link" activeClass="active"><i class="ti ti-player-play icon"></i>Play</MkA>
- <MkA to="/gallery" class="link" activeClass="active"><i class="ph-images-square ph-bold ph-lgs icon"></i>{{ i18n.ts.gallery }}</MkA>
- <div class="action">
- <button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button>
- <button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
- </div>
- </div>
- </Transition>
</div>
<XCommon/>
</template>
<script lang="ts" setup>
import { onMounted, provide, ref, computed } from 'vue';
-import XCommon from './_common_/common.vue';
import { instanceName } from '@@/js/config.js';
+import XCommon from './_common_/common.vue';
+import type { PageMetadata } from '@/page.js';
import * as os from '@/os.js';
import { instance } from '@/instance.js';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import { ColdDeviceStorage, defaultStore } from '@/store.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { i18n } from '@/i18n.js';
import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
+import { DI } from '@/di.js';
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
@@ -86,57 +45,25 @@ const DESKTOP_THRESHOLD = 1100;
const pageMetadata = ref<null | PageMetadata>(null);
-provide('router', mainRouter);
+provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
if (pageMetadata.value) {
if (isRoot.value && pageMetadata.value.title === instanceName) {
- document.title = pageMetadata.value.title;
+ window.document.title = pageMetadata.value.title;
} else {
- document.title = `${pageMetadata.value.title} | ${instanceName}`;
+ window.document.title = `${pageMetadata.value.title} | ${instanceName}`;
}
}
});
provideReactiveMetadata(pageMetadata);
-const announcements = {
- endpoint: 'announcements',
- limit: 10,
-};
-
-const isTimelineAvailable = ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable);
-
-const showMenu = ref(false);
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
const narrow = ref(window.innerWidth < 1280);
-const keymap = computed(() => {
- return {
- 'd': () => {
- if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
- defaultStore.set('darkMode', !defaultStore.state.darkMode);
- },
- 's': () => {
- mainRouter.push('/search');
- },
- };
-});
-
-function signin() {
- const { dispose } = os.popup(XSigninDialog, {
- autoSet: true,
- }, {
- closed: () => dispose(),
- });
-}
-
-function signup() {
- const { dispose } = os.popup(XSignupDialog, {
- autoSet: true,
- }, {
- closed: () => dispose(),
- });
+function goHome() {
+ mainRouter.push('/');
}
onMounted(() => {
@@ -146,152 +73,73 @@ onMounted(() => {
}, { passive: true });
}
});
-
-defineExpose({
- showMenu: showMenu,
-});
</script>
<style>
.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
</style>
-<style lang="scss" scoped>
-.tray-enter-active,
-.tray-leave-active {
- opacity: 1;
- transform: translateX(0);
- transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
-}
-.tray-enter-from,
-.tray-leave-active {
- opacity: 0;
- transform: translateX(-240px);
-}
-
-.tray-back-enter-active,
-.tray-back-leave-active {
- opacity: 1;
- transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
-}
-.tray-back-enter-from,
-.tray-back-leave-active {
- opacity: 0;
+<style lang="scss" module>
+.root {
+ display: flex;
+ height: 100dvh;
+ overflow: clip;
}
-.mk-app {
+.main {
display: flex;
- min-height: 100vh;
-
- > .side {
- position: sticky;
- top: 0;
- left: 0;
- width: 500px;
- height: 100vh;
- background: var(--MI_THEME-accent);
- z-index: 1;
-
- > .banner {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- aspect-ratio: 1.5;
- background-position: center;
- background-size: cover;
- -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
- mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
- }
-
- > .dashboard {
- position: relative;
- padding: 32px;
- box-sizing: border-box;
- max-height: 100%;
- overflow: auto;
- }
- }
-
- > .main {
- flex: 1;
- min-width: 0;
-
- > .header {
- background: var(--MI_THEME-panel);
- position: relative;
- z-index: 1;
-
- > .wide {
- line-height: 50px;
- padding: 0 16px;
-
- > .link {
- padding: 0 16px;
- }
- }
-
- > .narrow {
- > .menu {
- padding: 16px;
- }
- }
- }
- }
-
- > .menu-back {
- position: fixed;
- z-index: 1001;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- }
-
- > .menu {
- position: fixed;
- z-index: 1001;
- top: 0;
- left: 0;
- width: 240px;
- height: 100vh;
- background: var(--MI_THEME-panel);
-
- > .link {
- display: block;
- padding: 16px;
-
- > .icon {
- margin-right: 1em;
- }
- }
+ flex-direction: column;
+ flex: 1;
+ min-width: 0;
+}
- > .divider {
- margin: 8px auto;
- width: calc(100% - 32px);
- border-top: solid 0.5px var(--MI_THEME-divider);
- }
+.homeButton {
+ position: fixed;
+ z-index: 1000;
+ bottom: 16px;
+ right: 16px;
+ width: 60px;
+ height: 60px;
+ background: var(--MI_THEME-panel);
+ border-radius: 999px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
+}
- > .action {
- padding: 16px;
+.side {
+ position: sticky;
+ top: 0;
+ left: 0;
+ width: 500px;
+ height: 100vh;
+ background: var(--MI_THEME-accent);
+ z-index: 1;
+ overflow-y: scroll;
+ background: var(--MI_THEME-accent);
+}
- > button {
- display: block;
- width: 100%;
- padding: 10px;
- box-sizing: border-box;
- text-align: center;
- border-radius: var(--MI-radius-ellipse);
+.banner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ aspect-ratio: 1.5;
+ background-position: center;
+ background-size: cover;
+ -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
+ mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
+}
- &._button {
- background: var(--MI_THEME-panel);
- }
+.dashboard {
+ position: relative;
+ padding: 32px;
+ box-sizing: border-box;
+ max-height: 100%;
+ overflow: auto;
+}
- &:first-child {
- margin-bottom: 16px;
- }
- }
- }
- }
+.content {
+ display: flex;
+ flex-direction: column;
+ height: 100dvh;
}
</style>
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index 757aa6669d..66b4496827 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -4,46 +4,50 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div :class="showBottom ? $style.rootWithBottom : $style.root">
- <div style="container-type: inline-size;">
- <RouterView/>
+<div :class="$style.root">
+ <div :class="$style.contents">
+ <div style="flex: 1; min-height: 0;">
+ <RouterView/>
+ </div>
+
+ <!--
+ デッキUIが設定されている場合はデッキUIに戻れるようにする (ただし?zenが明示された場合は表示しない)
+ See https://github.com/misskey-dev/misskey/issues/10905
+ -->
+ <div v-if="showBottom" :class="$style.bottom">
+ <button v-tooltip="i18n.ts.goToMisskey" :class="['_button', '_shadow', $style.button]" @click="goToMisskey"><i class="ti ti-home"></i></button>
+ </div>
</div>
<XCommon/>
</div>
-
-<!--
- デッキUIが設定されている場合はデッキUIに戻れるようにする (ただし?zenが明示された場合は表示しない)
- See https://github.com/misskey-dev/misskey/issues/10905
--->
-<div v-if="showBottom" :class="$style.bottom">
- <button v-tooltip="i18n.ts.goToMisskey" :class="['_button', '_shadow', $style.button]" @click="goToMisskey"><i class="ti ti-home"></i></button>
-</div>
</template>
<script lang="ts" setup>
import { computed, provide, ref } from 'vue';
-import XCommon from './_common_/common.vue';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
import { instanceName, ui } from '@@/js/config.js';
+import XCommon from './_common_/common.vue';
+import type { PageMetadata } from '@/page.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
+import { DI } from '@/di.js';
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
const pageMetadata = ref<null | PageMetadata>(null);
-const showBottom = !(new URLSearchParams(location.search)).has('zen') && ui === 'deck';
+const showBottom = !(new URLSearchParams(window.location.search)).has('zen') && ui === 'deck';
-provide('router', mainRouter);
+provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
if (pageMetadata.value) {
if (isRoot.value && pageMetadata.value.title === instanceName) {
- document.title = pageMetadata.value.title;
+ window.document.title = pageMetadata.value.title;
} else {
- document.title = `${pageMetadata.value.title} | ${instanceName}`;
+ window.document.title = `${pageMetadata.value.title} | ${instanceName}`;
}
}
});
@@ -52,19 +56,16 @@ provideReactiveMetadata(pageMetadata);
function goToMisskey() {
window.location.href = '/';
}
-
-document.documentElement.style.overflowY = 'scroll';
</script>
<style lang="scss" module>
.root {
- min-height: 100dvh;
- box-sizing: border-box;
}
-.rootWithBottom {
- min-height: calc(100dvh - (60px + (var(--MI-margin) * 2) + env(safe-area-inset-bottom, 0px)));
- box-sizing: border-box;
+.contents {
+ display: flex;
+ flex-direction: column;
+ height: 100dvh;
}
.bottom {
@@ -74,7 +75,6 @@ document.documentElement.style.overflowY = 'scroll';
}
.button {
- position: fixed !important;
padding: 0;
aspect-ratio: 1;
width: 100%;
diff --git a/packages/frontend/src/scripts/use-chart-tooltip.ts b/packages/frontend/src/use/use-chart-tooltip.ts
index bba64fc6ee..bba64fc6ee 100644
--- a/packages/frontend/src/scripts/use-chart-tooltip.ts
+++ b/packages/frontend/src/use/use-chart-tooltip.ts
diff --git a/packages/frontend/src/scripts/use-form.ts b/packages/frontend/src/use/use-form.ts
index 0d505fe466..26cca839c3 100644
--- a/packages/frontend/src/scripts/use-form.ts
+++ b/packages/frontend/src/use/use-form.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { computed, Reactive, reactive, watch } from 'vue';
+import { computed, reactive, watch } from 'vue';
+import type { Reactive } from 'vue';
function copy<T>(v: T): T {
return JSON.parse(JSON.stringify(v));
diff --git a/packages/frontend/src/scripts/use-leave-guard.ts b/packages/frontend/src/use/use-leave-guard.ts
index 5f7e56e8a9..395c12a756 100644
--- a/packages/frontend/src/scripts/use-leave-guard.ts
+++ b/packages/frontend/src/use/use-leave-guard.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Ref } from 'vue';
+import type { Ref } from 'vue';
export function useLeaveGuard(enabled: Ref<boolean>) {
/* TODO
diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/use/use-note-capture.ts
index d15d9043c2..9cc4778edf 100644
--- a/packages/frontend/src/scripts/use-note-capture.ts
+++ b/packages/frontend/src/use/use-note-capture.ts
@@ -3,10 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { onUnmounted, Ref, ShallowRef } from 'vue';
+import { onUnmounted } from 'vue';
+import type { Ref, ShallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import { useStream } from '@/stream.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { misskeyApi } from './misskey-api.js';
export function useNoteCapture(props: {
@@ -123,7 +124,7 @@ export function useNoteCapture(props: {
function capture(withHandler = false): void {
if (connection) {
// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
- connection.send(document.body.contains(props.rootEl.value ?? null as Node | null) ? 'sr' : 's', { id: note.value.id });
+ connection.send(window.document.body.contains(props.rootEl.value ?? null as Node | null) ? 'sr' : 's', { id: note.value.id });
if (pureNote.value.id !== note.value.id) connection.send('s', { id: pureNote.value.id });
if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated);
}
diff --git a/packages/frontend/src/scripts/use-tooltip.ts b/packages/frontend/src/use/use-tooltip.ts
index a26d08cce7..af76a3a1e8 100644
--- a/packages/frontend/src/scripts/use-tooltip.ts
+++ b/packages/frontend/src/use/use-tooltip.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Ref, ref, watch, onUnmounted } from 'vue';
+import { ref, watch, onUnmounted } from 'vue';
+import type { Ref } from 'vue';
export function useTooltip(
elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>,
@@ -28,7 +29,7 @@ export function useTooltip(
if (!isHovering) return;
if (elRef.value == null) return;
const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el;
- if (!document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため
+ if (!window.document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため
const showing = ref(true);
onShow(showing);
@@ -37,7 +38,7 @@ export function useTooltip(
};
autoHidingTimer = window.setInterval(() => {
- if (elRef.value == null || !document.body.contains(elRef.value instanceof Element ? elRef.value : elRef.value.$el)) {
+ if (elRef.value == null || !window.document.body.contains(elRef.value instanceof Element ? elRef.value : elRef.value.$el)) {
if (!isHovering) return;
isHovering = false;
window.clearTimeout(timeoutId);
diff --git a/packages/frontend/src/scripts/achievements.ts b/packages/frontend/src/utility/achievements.ts
index f5d0ab559f..f6ab587ae1 100644
--- a/packages/frontend/src/scripts/achievements.ts
+++ b/packages/frontend/src/utility/achievements.ts
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { $i } from '@/account.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { $i } from '@/i.js';
export const ACHIEVEMENT_TYPES = [
'notes1',
diff --git a/packages/frontend/src/scripts/admin-lookup.ts b/packages/frontend/src/utility/admin-lookup.ts
index 1b57b853c9..7405e229fe 100644
--- a/packages/frontend/src/scripts/admin-lookup.ts
+++ b/packages/frontend/src/utility/admin-lookup.ts
@@ -6,7 +6,7 @@
import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
export async function lookupUser() {
const { canceled, result } = await os.inputText({
diff --git a/packages/frontend/src/scripts/array.ts b/packages/frontend/src/utility/array.ts
index f2feb29dfc..f2feb29dfc 100644
--- a/packages/frontend/src/scripts/array.ts
+++ b/packages/frontend/src/utility/array.ts
diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/utility/autocomplete.ts
index 8a3a6bf6db..0276cc3c20 100644
--- a/packages/frontend/src/scripts/autocomplete.ts
+++ b/packages/frontend/src/utility/autocomplete.ts
@@ -3,9 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { nextTick, Ref, ref, defineAsyncComponent } from 'vue';
+import { nextTick, ref, defineAsyncComponent } from 'vue';
import getCaretCoordinates from 'textarea-caret';
import { toASCII } from 'punycode.js';
+import type { Ref } from 'vue';
import { popup } from '@/os.js';
export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam';
@@ -97,15 +98,21 @@ export class Autocomplete {
const isMention = mentionIndex !== -1;
const isHashtag = hashtagIndex !== -1;
- const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam?.includes(' ');
+ const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam.includes(' ');
const isMfmTag = mfmTagIndex !== -1 && !isMfmParam;
const isEmoji = emojiIndex !== -1 && text.split(/:[\p{Letter}\p{Number}\p{Mark}_+-]+:/u).pop()!.includes(':');
let opened = false;
if (isMention && this.onlyType.includes('user')) {
- const username = text.substring(mentionIndex + 1);
- if (username !== '' && username.match(/^[a-zA-Z0-9_]+$/)) {
+ // ユーザのサジェスト中に@を入力すると、その位置から新たにユーザ名を取りなおそうとしてしまう
+ // この動きはリモートユーザのサジェストを阻害するので、@を検知したらその位置よりも前の@を探し、
+ // ホスト名を含むリモートのユーザ名を全て拾えるようにする
+ const mentionIndexAlt = text.lastIndexOf('@', mentionIndex - 1);
+ const username = mentionIndexAlt === -1
+ ? text.substring(mentionIndex + 1)
+ : text.substring(mentionIndexAlt + 1);
+ if (username !== '' && username.match(/^[a-zA-Z0-9_@.]+$/)) {
this.open('user', username);
opened = true;
} else if (username === '') {
diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts
new file mode 100644
index 0000000000..64fe328478
--- /dev/null
+++ b/packages/frontend/src/utility/autogen/settings-search-index.ts
@@ -0,0 +1,937 @@
+
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+// This file was automatically generated by create-search-index.
+// Do not edit this file.
+
+import { i18n } from '@/i18n.js';
+
+export type SearchIndexItem = {
+ id: string;
+ path?: string;
+ label: string;
+ keywords: string[];
+ icon?: string;
+ children?: SearchIndexItem[];
+};
+
+export const searchIndexes: SearchIndexItem[] = [
+ {
+ id: 'flXd1LC7r',
+ children: [
+ {
+ id: 'hB11H5oul',
+ label: i18n.ts.syncDeviceDarkMode,
+ keywords: ['sync', 'device', 'dark', 'light', 'mode'],
+ },
+ {
+ id: 'fDbLtIKeo',
+ label: i18n.ts.themeForLightMode,
+ keywords: ['light', 'theme'],
+ },
+ {
+ id: 'CsSVILKpX',
+ label: i18n.ts.themeForDarkMode,
+ keywords: ['dark', 'theme'],
+ },
+ {
+ id: '8wcoRp76b',
+ label: i18n.ts.setWallpaper,
+ keywords: ['wallpaper'],
+ },
+ ],
+ label: i18n.ts.theme,
+ keywords: ['theme'],
+ path: '/settings/theme',
+ icon: 'ti ti-palette',
+ },
+ {
+ id: '6fFIRXUww',
+ children: [
+ {
+ id: 'EcwZE7dCl',
+ label: i18n.ts.notUseSound,
+ keywords: ['mute'],
+ },
+ {
+ id: '9MxYVIf7k',
+ label: i18n.ts.useSoundOnlyWhenActive,
+ keywords: ['active', 'mute'],
+ },
+ {
+ id: '94afQxKat',
+ label: i18n.ts.masterVolume,
+ keywords: ['volume', 'master'],
+ },
+ ],
+ label: i18n.ts.sounds,
+ keywords: ['sounds', i18n.ts._settings.soundsBanner],
+ path: '/settings/sounds',
+ icon: 'ti ti-music',
+ },
+ {
+ id: '5BjnxMfYV',
+ children: [
+ {
+ id: '75QPEg57v',
+ children: [
+ {
+ id: 'CiHijRkGG',
+ label: i18n.ts.changePassword,
+ keywords: [],
+ },
+ ],
+ label: i18n.ts.password,
+ keywords: ['password'],
+ },
+ {
+ id: '2fa',
+ children: [
+ {
+ id: 'qCXM0HtJ7',
+ label: i18n.ts.totp,
+ keywords: ['totp', 'app', i18n.ts.totpDescription],
+ },
+ {
+ id: '3g1RePuD9',
+ label: i18n.ts.securityKeyAndPasskey,
+ keywords: ['security', 'key', 'passkey'],
+ },
+ {
+ id: 'pFRud5u8k',
+ label: i18n.ts.passwordLessLogin,
+ keywords: ['password', 'less', 'key', 'passkey', 'login', 'signin', i18n.ts.passwordLessLoginDescription],
+ },
+ ],
+ label: i18n.ts['2fa'],
+ keywords: ['2fa'],
+ },
+ ],
+ label: i18n.ts.security,
+ keywords: ['security', i18n.ts._settings.securityBanner],
+ path: '/settings/security',
+ icon: 'ti ti-lock',
+ },
+ {
+ id: 'w4L6myH61',
+ children: [
+ {
+ id: 'ru8DrOn3J',
+ label: i18n.ts._profile.changeBanner,
+ keywords: ['banner', 'change'],
+ },
+ {
+ id: 'CCnD8Apnu',
+ label: i18n.ts._profile.changeAvatar,
+ keywords: ['avatar', 'icon', 'change'],
+ },
+ {
+ id: 'yFEVCJxFX',
+ label: i18n.ts._profile.name,
+ keywords: ['name'],
+ },
+ {
+ id: '2O1S5reaB',
+ label: i18n.ts._profile.description,
+ keywords: ['description', 'bio'],
+ },
+ {
+ id: 'pWi4OLS8g',
+ label: i18n.ts.location,
+ keywords: ['location', 'locale'],
+ },
+ {
+ id: 'oLO5X6Wtw',
+ label: i18n.ts.birthday,
+ keywords: ['birthday', 'birthdate', 'age'],
+ },
+ {
+ id: 'm2trKwPgq',
+ label: i18n.ts.language,
+ keywords: ['language', 'locale'],
+ },
+ {
+ id: 'kfDZxCDp9',
+ label: i18n.ts._profile.metadataEdit,
+ keywords: ['metadata'],
+ },
+ {
+ id: 'uPt3MFymp',
+ label: i18n.ts._profile.followedMessage,
+ keywords: ['follow', 'message', i18n.ts._profile.followedMessageDescription],
+ },
+ {
+ id: 'wuGg0tBjw',
+ label: i18n.ts.reactionAcceptance,
+ keywords: ['reaction'],
+ },
+ {
+ id: 'EezPpmMnf',
+ children: [
+ {
+ id: 'f2cRLh8ad',
+ label: i18n.ts.flagAsCat,
+ keywords: ['cat'],
+ },
+ {
+ id: 'eVoViiF3h',
+ label: i18n.ts.flagAsBot,
+ keywords: ['bot'],
+ },
+ ],
+ label: i18n.ts.advancedSettings,
+ keywords: [],
+ },
+ ],
+ label: i18n.ts.profile,
+ keywords: ['profile'],
+ path: '/settings/profile',
+ icon: 'ti ti-user',
+ },
+ {
+ id: '2rp9ka5Ht',
+ children: [
+ {
+ id: 'BhAQiHogN',
+ label: i18n.ts.makeFollowManuallyApprove,
+ keywords: ['follow', 'lock', i18n.ts.lockedAccountInfo],
+ },
+ {
+ id: '4DeWGsPaD',
+ label: i18n.ts.autoAcceptFollowed,
+ keywords: ['follow', 'auto', 'accept'],
+ },
+ {
+ id: 'iaM6zUmO9',
+ label: i18n.ts.makeReactionsPublic,
+ keywords: ['reaction', 'public', i18n.ts.makeReactionsPublicDescription],
+ },
+ {
+ id: '5Q6uhghzV',
+ label: i18n.ts.followingVisibility,
+ keywords: ['following', 'visibility'],
+ },
+ {
+ id: 'pZ9q65FX5',
+ label: i18n.ts.followersVisibility,
+ keywords: ['follower', 'visibility'],
+ },
+ {
+ id: 'DMS4yvAGg',
+ label: i18n.ts.hideOnlineStatus,
+ keywords: ['online', 'status', i18n.ts.hideOnlineStatusDescription],
+ },
+ {
+ id: '8rEsGuN8w',
+ label: i18n.ts.noCrawle,
+ keywords: ['crawle', 'index', 'search', i18n.ts.noCrawleDescription],
+ },
+ {
+ id: 's7LdSpiLn',
+ label: i18n.ts.preventAiLearning,
+ keywords: ['crawle', 'ai', i18n.ts.preventAiLearningDescription],
+ },
+ {
+ id: 'l2Wf1s2ad',
+ label: i18n.ts.makeExplorable,
+ keywords: ['explore', i18n.ts.makeExplorableDescription],
+ },
+ {
+ id: 'xEYlOghao',
+ label: i18n.ts._chat.chatAllowedUsers,
+ keywords: ['chat'],
+ },
+ {
+ id: 'BnOtlyaAh',
+ children: [
+ {
+ id: 'BzMIVBpL0',
+ label: i18n.ts._accountSettings.requireSigninToViewContents,
+ keywords: ['login', 'signin'],
+ },
+ {
+ id: 'jJUqPqBAv',
+ label: i18n.ts._accountSettings.makeNotesFollowersOnlyBefore,
+ keywords: ['follower', i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription],
+ },
+ {
+ id: 'ra10txIFV',
+ label: i18n.ts._accountSettings.makeNotesHiddenBefore,
+ keywords: ['hidden', i18n.ts._accountSettings.makeNotesHiddenBeforeDescription],
+ },
+ ],
+ label: i18n.ts.lockdown,
+ keywords: ['lockdown'],
+ },
+ ],
+ label: i18n.ts.privacy,
+ keywords: ['privacy', i18n.ts._settings.privacyBanner],
+ path: '/settings/privacy',
+ icon: 'ti ti-lock-open',
+ },
+ {
+ id: '3yCAv0IsZ',
+ children: [
+ {
+ id: 'AKvDrxSj5',
+ children: [
+ {
+ id: 'cAszhShB0',
+ label: i18n.ts.uiLanguage,
+ keywords: ['language'],
+ },
+ {
+ id: 'apz9AutPm',
+ label: i18n.ts.overridedDeviceKind,
+ keywords: ['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop'],
+ },
+ {
+ id: 'nqRVtw1xw',
+ label: i18n.ts.useBlurEffect,
+ keywords: ['blur'],
+ },
+ {
+ id: 'EO5WHBeG8',
+ label: i18n.ts.useBlurEffectForModal,
+ keywords: ['blur', 'modal'],
+ },
+ {
+ id: 'CWpyT9vLK',
+ label: i18n.ts.showAvatarDecorations,
+ keywords: ['avatar', 'icon', 'decoration', 'show'],
+ },
+ {
+ id: '1wwACqQz1',
+ label: i18n.ts.alwaysConfirmFollow,
+ keywords: ['follow', 'confirm', 'always'],
+ },
+ {
+ id: '1x3JNXj8N',
+ label: i18n.ts.highlightSensitiveMedia,
+ keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'],
+ },
+ {
+ id: 'CfAg0Qekq',
+ label: i18n.ts.confirmWhenRevealingSensitiveMedia,
+ keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'],
+ },
+ {
+ id: 'aefexW9fD',
+ label: i18n.ts.enableAdvancedMfm,
+ keywords: ['mfm', 'enable', 'show', 'advanced'],
+ },
+ {
+ id: 'lu9v5Spqg',
+ label: i18n.ts.enableInfiniteScroll,
+ keywords: ['auto', 'load', 'auto', 'more', 'scroll'],
+ },
+ {
+ id: '6kMj4HVOg',
+ label: i18n.ts.emojiStyle,
+ keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'],
+ },
+ {
+ id: 'DftdlLbNu',
+ label: i18n.ts.pinnedList,
+ keywords: ['pinned', 'list'],
+ },
+ ],
+ label: i18n.ts.general,
+ keywords: ['general'],
+ },
+ {
+ id: 'CQldliCSi',
+ children: [
+ {
+ id: 'kMB2hPyq3',
+ label: i18n.ts.showFixedPostForm,
+ keywords: ['post', 'form', 'timeline'],
+ },
+ {
+ id: 'jC7LtTnmc',
+ label: i18n.ts.showFixedPostFormInChannel,
+ keywords: ['post', 'form', 'timeline', 'channel'],
+ },
+ {
+ id: 'p2wlrnwLo',
+ label: i18n.ts.collapseRenotes,
+ keywords: ['renote', i18n.ts.collapseRenotesDescription],
+ },
+ {
+ id: '6SFn3t8VS',
+ label: i18n.ts.showGapBetweenNotesInTimeline,
+ keywords: ['note', 'timeline', 'gap'],
+ },
+ {
+ id: 'nygexkaUk',
+ label: i18n.ts.disableStreamingTimeline,
+ keywords: ['disable', 'streaming', 'timeline'],
+ },
+ {
+ id: '7vnQgR42v',
+ label: i18n.ts.showNoteActionsOnlyHover,
+ keywords: ['hover', 'show', 'footer', 'action'],
+ },
+ {
+ id: 'x5q4XZ7Kv',
+ label: i18n.ts.showClipButtonInNoteFooter,
+ keywords: ['footer', 'action', 'clip', 'show'],
+ },
+ {
+ id: 'x9irZWjaF',
+ label: i18n.ts.showReactionsCount,
+ keywords: ['reaction', 'count', 'show'],
+ },
+ {
+ id: 'dHPv9mrxi',
+ label: i18n.ts.confirmOnReact,
+ keywords: ['reaction', 'confirm'],
+ },
+ {
+ id: 'bj42W4cvN',
+ label: i18n.ts.loadRawImages,
+ keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'],
+ },
+ {
+ id: 'fzPca1Gk9',
+ label: i18n.ts.useReactionPickerForContextMenu,
+ keywords: ['reaction', 'picker', 'contextmenu', 'open'],
+ },
+ {
+ id: 'mNU5IBln7',
+ label: i18n.ts.reactionsDisplaySize,
+ keywords: ['reaction', 'size', 'scale', 'display'],
+ },
+ {
+ id: 'kYgorbLUy',
+ label: i18n.ts.limitWidthOfReaction,
+ keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'],
+ },
+ {
+ id: 'm75VEWI3S',
+ label: i18n.ts.mediaListWithOneImageAppearance,
+ keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'],
+ },
+ {
+ id: 'CA42sC9Mx',
+ label: i18n.ts.instanceTicker,
+ keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'],
+ },
+ {
+ id: 'knEhibyFp',
+ label: i18n.ts.displayOfSensitiveMedia,
+ keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'],
+ },
+ ],
+ label: i18n.ts._settings.timelineAndNote,
+ keywords: ['timeline', 'note'],
+ },
+ {
+ id: 'yIR4YP0yU',
+ children: [
+ {
+ id: 'cBkUgQNpH',
+ label: i18n.ts.keepCw,
+ keywords: ['remember', 'keep', 'note', 'cw'],
+ },
+ {
+ id: 'Bv4YywaKL',
+ label: i18n.ts.rememberNoteVisibility,
+ keywords: ['remember', 'keep', 'note', 'visibility'],
+ },
+ {
+ id: 'F3kpUNvSQ',
+ label: i18n.ts.enableQuickAddMfmFunction,
+ keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'],
+ },
+ {
+ id: 'BBxwy4F6E',
+ label: i18n.ts.defaultNoteVisibility,
+ keywords: ['default', 'note', 'visibility'],
+ },
+ ],
+ label: i18n.ts.postForm,
+ keywords: ['post', 'form'],
+ },
+ {
+ id: 'e5XnQWk68',
+ children: [
+ {
+ id: 'rOttgccaS',
+ label: i18n.ts.useGroupedNotifications,
+ keywords: ['group'],
+ },
+ {
+ id: 'Ek4Cw3VPq',
+ label: i18n.ts.position,
+ keywords: ['position'],
+ },
+ {
+ id: 'pZLzt3i0s',
+ label: i18n.ts.stackAxis,
+ keywords: ['stack', 'axis', 'direction'],
+ },
+ ],
+ label: i18n.ts.notifications,
+ keywords: ['notification'],
+ },
+ {
+ id: 'c9mbgmHQp',
+ label: i18n.ts.dataSaver,
+ keywords: ['datasaver'],
+ },
+ {
+ id: '5h8vhCX1S',
+ children: [
+ {
+ id: 'bDv03znUy',
+ label: i18n.ts.squareAvatars,
+ keywords: ['avatar', 'icon', 'square'],
+ },
+ {
+ id: 'nkR2LWURW',
+ label: i18n.ts.seasonalScreenEffect,
+ keywords: ['effect', 'show'],
+ },
+ {
+ id: 'sCscGhMmH',
+ label: i18n.ts.openImageInNewTab,
+ keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'],
+ },
+ {
+ id: '4yCgcFElF',
+ label: i18n.ts.withRepliesByDefaultForNewlyFollowed,
+ keywords: ['follow', 'replies'],
+ },
+ {
+ id: '5iMpm5rES',
+ label: i18n.ts.whenServerDisconnected,
+ keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'],
+ },
+ {
+ id: 'dlQjnWBVU',
+ label: i18n.ts.numberOfPageCache,
+ keywords: ['cache', 'page'],
+ },
+ {
+ id: 'qY5xTzl35',
+ label: i18n.ts.forceShowAds,
+ keywords: ['ad', 'show'],
+ },
+ {
+ id: '2VSnj81vC',
+ label: i18n.ts.hemisphere,
+ keywords: [],
+ },
+ {
+ id: 'vuG3aG3IE',
+ label: i18n.ts.additionalEmojiDictionary,
+ keywords: ['emoji', 'dictionary', 'additional', 'extra'],
+ },
+ ],
+ label: i18n.ts.other,
+ keywords: ['other'],
+ },
+ ],
+ label: i18n.ts.preferences,
+ keywords: ['general', 'preferences', i18n.ts._settings.preferencesBanner],
+ path: '/settings/preferences',
+ icon: 'ti ti-adjustments',
+ },
+ {
+ id: 'mwkwtw83Y',
+ label: i18n.ts.plugins,
+ keywords: ['plugin', 'addon', 'extension', i18n.ts._settings.pluginBanner],
+ path: '/settings/plugin',
+ icon: 'ti ti-plug',
+ },
+ {
+ id: 'F1uK9ssiY',
+ children: [
+ {
+ id: 'E0ndmaP6Q',
+ label: i18n.ts._role.policies,
+ keywords: ['account', 'info'],
+ },
+ {
+ id: 'r5SjfwZJc',
+ label: i18n.ts.rolesAssignedToMe,
+ keywords: ['roles'],
+ },
+ {
+ id: 'cm7LrjgaW',
+ label: i18n.ts.accountMigration,
+ keywords: ['account', 'move', 'migration'],
+ },
+ {
+ id: 'ozfqNviP3',
+ label: i18n.ts.closeAccount,
+ keywords: ['account', 'close', 'delete', i18n.ts._accountDelete.requestAccountDelete],
+ },
+ {
+ id: 'tpywgkpxy',
+ label: i18n.ts.experimentalFeatures,
+ keywords: ['experimental', 'feature', 'flags'],
+ },
+ {
+ id: 'zWbGKohZ2',
+ label: i18n.ts.developer,
+ keywords: ['developer', 'mode', 'debug'],
+ },
+ ],
+ label: i18n.ts.other,
+ keywords: ['other'],
+ path: '/settings/other',
+ icon: 'ti ti-dots',
+ },
+ {
+ id: '3icEvyv2D',
+ children: [
+ {
+ id: 'lO3uFTkPN',
+ children: [
+ {
+ id: '5JKaXRqyt',
+ label: i18n.ts.showMutedWord,
+ keywords: ['show'],
+ },
+ ],
+ label: i18n.ts.wordMute,
+ keywords: ['note', 'word', 'soft', 'mute', 'hide'],
+ },
+ {
+ id: 'fMkjL3dK4',
+ label: i18n.ts.hardWordMute,
+ keywords: ['note', 'word', 'hard', 'mute', 'hide'],
+ },
+ {
+ id: 'cimSzQXN0',
+ label: i18n.ts.instanceMute,
+ keywords: ['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide'],
+ },
+ {
+ id: 'gq8rPy3Du',
+ label: `${i18n.ts.mutedUsers} (${ i18n.ts.renote })`,
+ keywords: ['renote', 'mute', 'hide', 'user'],
+ },
+ {
+ id: 'mh2r7EUbF',
+ label: i18n.ts.mutedUsers,
+ keywords: ['note', 'mute', 'hide', 'user'],
+ },
+ {
+ id: 'AUS1OgHrn',
+ label: i18n.ts.blockedUsers,
+ keywords: ['block', 'user'],
+ },
+ ],
+ label: i18n.ts.muteAndBlock,
+ keywords: ['mute', 'block', i18n.ts._settings.muteAndBlockBanner],
+ path: '/settings/mute-block',
+ icon: 'ti ti-ban',
+ },
+ {
+ id: 'yR1OSyLiT',
+ children: [
+ {
+ id: 'yMJzyzOUk',
+ label: i18n.ts._emojiPalette.enableSyncBetweenDevicesForPalettes,
+ keywords: ['sync', 'palettes', 'devices'],
+ },
+ {
+ id: 'wCE09vgZr',
+ label: i18n.ts._emojiPalette.paletteForMain,
+ keywords: ['main', 'palette'],
+ },
+ {
+ id: 'uCzRPrSNx',
+ label: i18n.ts._emojiPalette.paletteForReaction,
+ keywords: ['reaction', 'palette'],
+ },
+ {
+ id: 'hgQr28WUk',
+ children: [
+ {
+ id: 'fY04NIHSQ',
+ label: i18n.ts.size,
+ keywords: ['emoji', 'picker', 'scale', 'size'],
+ },
+ {
+ id: '3j7vlaL7t',
+ label: i18n.ts.numberOfColumn,
+ keywords: ['emoji', 'picker', 'width', 'column', 'size'],
+ },
+ {
+ id: 'zPX8z1Bcy',
+ label: i18n.ts.height,
+ keywords: ['emoji', 'picker', 'height', 'size'],
+ },
+ {
+ id: '2CSkZa4tl',
+ label: i18n.ts.style,
+ keywords: ['emoji', 'picker', 'style'],
+ },
+ ],
+ label: i18n.ts.emojiPickerDisplay,
+ keywords: ['emoji', 'picker', 'display'],
+ },
+ ],
+ label: i18n.ts.emojiPalette,
+ keywords: ['emoji', 'palette'],
+ path: '/settings/emoji-palette',
+ icon: 'ti ti-mood-happy',
+ },
+ {
+ id: '3Tcxw4Fwl',
+ children: [
+ {
+ id: 'iIai9O65I',
+ label: i18n.ts.emailAddress,
+ keywords: ['email', 'address'],
+ },
+ {
+ id: 'i6cC6oi0m',
+ label: i18n.ts.receiveAnnouncementFromInstance,
+ keywords: ['announcement', 'email'],
+ },
+ {
+ id: 'C1YTinP11',
+ label: i18n.ts.emailNotification,
+ keywords: ['notification', 'email'],
+ },
+ ],
+ label: i18n.ts.email,
+ keywords: ['email'],
+ path: '/settings/email',
+ icon: 'ti ti-mail',
+ },
+ {
+ id: 'tnYoppRiv',
+ children: [
+ {
+ id: 'cN3dsGNxu',
+ label: i18n.ts.usageAmount,
+ keywords: ['capacity', 'usage'],
+ },
+ {
+ id: 'rOAOU2P6C',
+ label: i18n.ts.statistics,
+ keywords: ['statistics', 'usage'],
+ },
+ {
+ id: 'uXGlQXATx',
+ label: i18n.ts.uploadFolder,
+ keywords: ['default', 'upload', 'folder'],
+ },
+ {
+ id: 'goQdtf3dD',
+ label: i18n.ts.keepOriginalUploading,
+ keywords: ['keep', 'original', 'raw', 'upload', i18n.ts.keepOriginalUploadingDescription],
+ },
+ {
+ id: '83xRo0XJl',
+ label: i18n.ts.keepOriginalFilename,
+ keywords: ['keep', 'original', 'filename', i18n.ts.keepOriginalFilenameDescription],
+ },
+ {
+ id: 'wf77yRQQq',
+ label: i18n.ts.alwaysMarkSensitive,
+ keywords: ['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file'],
+ },
+ {
+ id: '3pxwNB8e4',
+ label: i18n.ts.enableAutoSensitive,
+ keywords: ['auto', 'nsfw', 'sensitive', 'media', 'file', i18n.ts.enableAutoSensitiveDescription],
+ },
+ ],
+ label: i18n.ts.drive,
+ keywords: ['drive', i18n.ts._settings.driveBanner],
+ path: '/settings/drive',
+ icon: 'ti ti-cloud',
+ },
+ {
+ id: 'FfZdOs8y',
+ children: [
+ {
+ id: 'B1ZU6Ur54',
+ label: i18n.ts._deck.enableSyncBetweenDevicesForProfiles,
+ keywords: ['sync', 'profiles', 'devices'],
+ },
+ {
+ id: 'iEF0gqNAo',
+ label: i18n.ts._deck.useSimpleUiForNonRootPages,
+ keywords: ['ui', 'root', 'page'],
+ },
+ {
+ id: 'BNdSeWxZn',
+ label: i18n.ts.defaultNavigationBehaviour,
+ keywords: ['default', 'navigation', 'behaviour', 'window'],
+ },
+ {
+ id: 'zT9pGm8DF',
+ label: i18n.ts._deck.alwaysShowMainColumn,
+ keywords: ['always', 'show', 'main', 'column'],
+ },
+ {
+ id: '5dk2xv1vc',
+ label: i18n.ts._deck.columnAlign,
+ keywords: ['column', 'align'],
+ },
+ ],
+ label: i18n.ts.deck,
+ keywords: ['deck', 'ui'],
+ path: '/settings/deck',
+ icon: 'ti ti-columns',
+ },
+ {
+ id: 'BlJ2rsw9h',
+ children: [
+ {
+ id: '9bLU1nIjt',
+ label: i18n.ts._settings.api,
+ keywords: ['api', 'app', 'token', 'accessToken'],
+ },
+ {
+ id: '5VSGOVYR0',
+ label: i18n.ts._settings.webhook,
+ keywords: ['webhook'],
+ },
+ ],
+ label: i18n.ts._settings.serviceConnection,
+ keywords: ['app', 'service', 'connect', 'webhook', 'api', 'token', i18n.ts._settings.serviceConnectionBanner],
+ path: '/settings/connect',
+ icon: 'ti ti-link',
+ },
+ {
+ id: 'gtaOSdIJB',
+ label: i18n.ts.avatarDecorations,
+ keywords: ['avatar', 'icon', 'decoration'],
+ path: '/settings/avatar-decoration',
+ icon: 'ti ti-sparkles',
+ },
+ {
+ id: 'zK6posor9',
+ label: i18n.ts.accounts,
+ keywords: ['accounts'],
+ path: '/settings/accounts',
+ icon: 'ti ti-users',
+ },
+ {
+ id: '330Q4mf8E',
+ children: [
+ {
+ id: 'eGSjUDIKu',
+ label: i18n.ts._exportOrImport.allNotes,
+ keywords: ['notes'],
+ },
+ {
+ id: 'iMDgUVgRu',
+ label: i18n.ts._exportOrImport.favoritedNotes,
+ keywords: ['favorite', 'notes'],
+ },
+ {
+ id: '3y6KgkVbT',
+ label: i18n.ts._exportOrImport.clips,
+ keywords: ['clip', 'notes'],
+ },
+ {
+ id: 'cKiHkj8HE',
+ label: i18n.ts._exportOrImport.followingList,
+ keywords: ['following', 'users'],
+ },
+ {
+ id: '3zzmQXn0t',
+ label: i18n.ts._exportOrImport.userLists,
+ keywords: ['user', 'lists'],
+ },
+ {
+ id: '3ZGXcEqWZ',
+ label: i18n.ts._exportOrImport.muteList,
+ keywords: ['mute', 'users'],
+ },
+ {
+ id: '84oL7B1Dr',
+ label: i18n.ts._exportOrImport.blockingList,
+ keywords: ['block', 'users'],
+ },
+ {
+ id: 'ckqi48Kbl',
+ label: i18n.ts.antennas,
+ keywords: ['antennas'],
+ },
+ ],
+ label: i18n.ts._settings.accountData,
+ keywords: ['import', 'export', 'data', 'archive', i18n.ts._settings.accountDataBanner],
+ path: '/settings/account-data',
+ icon: 'ti ti-package',
+ },
+ {
+ id: 'f08Mi1Uwn',
+ children: [
+ {
+ id: 'C5dRH2Ypy',
+ label: i18n.ts.reduceUiAnimation,
+ keywords: ['animation', 'motion', 'reduce'],
+ },
+ {
+ id: '5mZxz2cru',
+ label: i18n.ts.disableShowingAnimatedImages,
+ keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'],
+ },
+ {
+ id: 'c0Iy5hL5o',
+ label: i18n.ts.enableAnimatedMfm,
+ keywords: ['mfm', 'enable', 'show', 'animated'],
+ },
+ {
+ id: '4HYFjs2Nv',
+ label: i18n.ts.enableHorizontalSwipe,
+ keywords: ['swipe', 'horizontal', 'tab'],
+ },
+ {
+ id: 'kYVJ3SVNq',
+ label: i18n.ts.keepScreenOn,
+ keywords: ['keep', 'screen', 'display', 'on'],
+ },
+ {
+ id: 'w4Bv0meAt',
+ label: i18n.ts.useNativeUIForVideoAudioPlayer,
+ keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
+ },
+ {
+ id: 'b1GYEEJeh',
+ label: i18n.ts._settings.makeEveryTextElementsSelectable,
+ keywords: ['text', 'selectable'],
+ },
+ {
+ id: 'vVLxwINTJ',
+ label: i18n.ts.menuStyle,
+ keywords: ['menu', 'style', 'popup', 'drawer'],
+ },
+ {
+ id: '14cMhMLHL',
+ label: i18n.ts._contextMenu.title,
+ keywords: ['contextmenu', 'system', 'native'],
+ },
+ {
+ id: 'oSo4LXMX9',
+ label: i18n.ts.fontSize,
+ keywords: ['font', 'size'],
+ },
+ {
+ id: '7LQSAThST',
+ label: i18n.ts.useSystemFont,
+ keywords: ['font', 'system', 'native'],
+ },
+ ],
+ label: i18n.ts.accessibility,
+ keywords: ['accessibility', i18n.ts._settings.accessibilityBanner],
+ path: '/settings/accessibility',
+ icon: 'ti ti-accessible',
+ },
+] as const;
+
+export type SearchIndex = typeof searchIndexes;
diff --git a/packages/frontend/src/scripts/boost-quote.ts b/packages/frontend/src/utility/boost-quote.ts
index feb949772b..feb949772b 100644
--- a/packages/frontend/src/scripts/boost-quote.ts
+++ b/packages/frontend/src/utility/boost-quote.ts
diff --git a/packages/frontend/src/scripts/cache.ts b/packages/frontend/src/utility/cache.ts
index 0fbdf34d5d..0fbdf34d5d 100644
--- a/packages/frontend/src/scripts/cache.ts
+++ b/packages/frontend/src/utility/cache.ts
diff --git a/packages/frontend/src/scripts/chart-legend.ts b/packages/frontend/src/utility/chart-legend.ts
index 2d534f60c1..e701d18dd2 100644
--- a/packages/frontend/src/scripts/chart-legend.ts
+++ b/packages/frontend/src/utility/chart-legend.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Plugin } from 'chart.js';
+import type { Plugin } from 'chart.js';
import MkChartLegend from '@/components/MkChartLegend.vue';
export const chartLegend = (legend: InstanceType<typeof MkChartLegend>) => ({
diff --git a/packages/frontend/src/scripts/chart-vline.ts b/packages/frontend/src/utility/chart-vline.ts
index 24e41245e7..465ca591c6 100644
--- a/packages/frontend/src/scripts/chart-vline.ts
+++ b/packages/frontend/src/utility/chart-vline.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Plugin } from 'chart.js';
+import type { Plugin } from 'chart.js';
export const chartVLine = (vLineColor: string) => ({
id: 'vLine',
diff --git a/packages/frontend/src/scripts/check-animated-mfm.ts b/packages/frontend/src/utility/check-animated-mfm.ts
index 2614dfb4f1..2614dfb4f1 100644
--- a/packages/frontend/src/scripts/check-animated-mfm.ts
+++ b/packages/frontend/src/utility/check-animated-mfm.ts
diff --git a/packages/frontend/src/scripts/check-permissions.ts b/packages/frontend/src/utility/check-permissions.ts
index ed86529d5b..2de8fd2cd1 100644
--- a/packages/frontend/src/scripts/check-permissions.ts
+++ b/packages/frontend/src/utility/check-permissions.ts
@@ -4,7 +4,7 @@
*/
import { instance } from '@/instance.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
export const notesSearchAvailable = (
// FIXME: instance.policies would be null in Vitest
diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/utility/check-reaction-permissions.ts
index c3c3f419a9..281ea2520e 100644
--- a/packages/frontend/src/scripts/check-reaction-permissions.ts
+++ b/packages/frontend/src/utility/check-reaction-permissions.ts
@@ -4,7 +4,7 @@
*/
import * as Misskey from 'misskey-js';
-import { UnicodeEmojiDef } from '@@/js/emojilist.js';
+import type { UnicodeEmojiDef } from '@@/js/emojilist.js';
export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean {
if (typeof emoji === 'string') return true; // UnicodeEmojiDefにも無い絵文字であれば文字列で来る。Unicode絵文字であることには変わりないので常にリアクション可能とする;
diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/utility/check-word-mute.ts
index 194ef0f420..194ef0f420 100644
--- a/packages/frontend/src/scripts/check-word-mute.ts
+++ b/packages/frontend/src/utility/check-word-mute.ts
diff --git a/packages/frontend/src/scripts/chiptune2.ts b/packages/frontend/src/utility/chiptune2.ts
index 220002ff1e..220002ff1e 100644
--- a/packages/frontend/src/scripts/chiptune2.ts
+++ b/packages/frontend/src/utility/chiptune2.ts
diff --git a/packages/frontend/src/scripts/clear-cache.ts b/packages/frontend/src/utility/clear-cache.ts
index 71d1232710..b6ae254727 100644
--- a/packages/frontend/src/scripts/clear-cache.ts
+++ b/packages/frontend/src/utility/clear-cache.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { unisonReload } from '@/utility/unison-reload.js';
import * as os from '@/os.js';
import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
diff --git a/packages/frontend/src/scripts/clicker-game.ts b/packages/frontend/src/utility/clicker-game.ts
index f9c4bc1829..0544be7757 100644
--- a/packages/frontend/src/scripts/clicker-game.ts
+++ b/packages/frontend/src/utility/clicker-game.ts
@@ -4,7 +4,7 @@
*/
import { ref, computed } from 'vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
type SaveData = {
gameVersion: number;
diff --git a/packages/frontend/src/scripts/clone.ts b/packages/frontend/src/utility/clone.ts
index ea8eea14b5..ea8eea14b5 100644
--- a/packages/frontend/src/scripts/clone.ts
+++ b/packages/frontend/src/utility/clone.ts
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/utility/code-highlighter.ts
index 4d57dcd944..4f2aff9d4c 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/utility/code-highlighter.ts
@@ -10,18 +10,20 @@ import { bundledThemesInfo } from 'shiki/themes';
import { bundledLanguagesInfo } from 'shiki/langs';
import lightTheme from '@@/themes/_light.json5';
import darkTheme from '@@/themes/_dark.json5';
+import defaultLightTheme from '@@/themes/l-light.json5';
+import defaultDarkTheme from '@@/themes/d-green-lime.json5';
import { unique } from './array.js';
import { deepClone } from './clone.js';
import { deepMerge } from './merge.js';
import type { HighlighterCore, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki/core';
-import { ColdDeviceStorage } from '@/store.js';
+import { prefer } from '@/preferences.js';
let _highlighter: HighlighterCore | null = null;
export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>;
export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>;
export async function getTheme(mode: 'light' | 'dark', getName = false): Promise<ThemeRegistration | ThemeRegistrationRaw | string | null> {
- const theme = deepClone(ColdDeviceStorage.get(mode === 'light' ? 'lightTheme' : 'darkTheme'));
+ const theme = deepClone(mode === 'light' ? prefer.s.lightTheme ?? defaultLightTheme : prefer.s.darkTheme ?? defaultDarkTheme);
if (theme.base) {
const base = [lightTheme, darkTheme].find(x => x.id === theme.base);
@@ -77,19 +79,19 @@ async function initHighlighter() {
],
});
- ColdDeviceStorage.watch('lightTheme', async () => {
- const newTheme = await getTheme('light');
- if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
- highlighter.loadTheme(newTheme);
- }
- });
-
- ColdDeviceStorage.watch('darkTheme', async () => {
- const newTheme = await getTheme('dark');
- if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
- highlighter.loadTheme(newTheme);
- }
- });
+ // TODO
+ //watch('lightTheme', async () => {
+ // const newTheme = await getTheme('light');
+ // if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
+ // highlighter.loadTheme(newTheme);
+ // }
+ //});
+ //watch('darkTheme', async () => {
+ // const newTheme = await getTheme('dark');
+ // if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
+ // highlighter.loadTheme(newTheme);
+ // }
+ //});
_highlighter = highlighter;
diff --git a/packages/frontend/src/scripts/collect-page-vars.ts b/packages/frontend/src/utility/collect-page-vars.ts
index 5096c0669e..5096c0669e 100644
--- a/packages/frontend/src/scripts/collect-page-vars.ts
+++ b/packages/frontend/src/utility/collect-page-vars.ts
diff --git a/packages/frontend/src/scripts/color.ts b/packages/frontend/src/utility/color.ts
index a11255ffd1..a11255ffd1 100644
--- a/packages/frontend/src/scripts/color.ts
+++ b/packages/frontend/src/utility/color.ts
diff --git a/packages/frontend/src/scripts/confetti.ts b/packages/frontend/src/utility/confetti.ts
index 8e53a6ceeb..8e53a6ceeb 100644
--- a/packages/frontend/src/scripts/confetti.ts
+++ b/packages/frontend/src/utility/confetti.ts
diff --git a/packages/frontend/src/scripts/contains.ts b/packages/frontend/src/utility/contains.ts
index 6137c06e85..6137c06e85 100644
--- a/packages/frontend/src/scripts/contains.ts
+++ b/packages/frontend/src/utility/contains.ts
diff --git a/packages/frontend/src/scripts/copy-to-clipboard.ts b/packages/frontend/src/utility/copy-to-clipboard.ts
index 08f5b52dae..08a759588e 100644
--- a/packages/frontend/src/scripts/copy-to-clipboard.ts
+++ b/packages/frontend/src/utility/copy-to-clipboard.ts
@@ -3,9 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
+
/**
* Clipboardに値をコピー(TODO: 文字列以外も対応)
*/
export function copyToClipboard(input: string | null) {
- if (input) navigator.clipboard.writeText(input);
-}
+ if (input) {
+ navigator.clipboard.writeText(input);
+ os.toast(i18n.ts.copiedToClipboard);
+ }
+};
diff --git a/packages/frontend/src/utility/deep-equal.ts b/packages/frontend/src/utility/deep-equal.ts
new file mode 100644
index 0000000000..2859641dc7
--- /dev/null
+++ b/packages/frontend/src/utility/deep-equal.ts
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+type JsonLike = string | number | boolean | null | undefined | JsonLike[] | { [key: string]: JsonLike } | Map<string, JsonLike>;
+
+export function deepEqual(a: JsonLike, b: JsonLike): boolean {
+ if (a === b) return true;
+ if (typeof a !== typeof b) return false;
+
+ if (a === null) return b === null;
+
+ if (a === undefined) return b === undefined;
+
+ if (Array.isArray(a) && Array.isArray(b)) {
+ if (a.length !== b.length) return false;
+ for (let i = 0; i < a.length; i++) {
+ if (!deepEqual(a[i], b[i])) return false;
+ }
+ return true;
+ } else if (a instanceof Map && b instanceof Map) {
+ if (a.size !== b.size) return false;
+ for (const [k, v] of a) {
+ if (!deepEqual(v, b.get(k))) return false;
+ }
+ return true;
+ } else if (((typeof a) === 'object') && ((typeof b) === 'object')) {
+ const aks = Object.keys(a);
+ const bks = Object.keys(b as { [key: string]: JsonLike });
+ if (aks.length !== bks.length) return false;
+ for (let i = 0; i < aks.length; i++) {
+ const k = aks[i];
+ if (!deepEqual(a[k], (b as { [key: string]: JsonLike })[k])) return false;
+ }
+ return true;
+ }
+
+ return false;
+}
diff --git a/packages/frontend/src/scripts/device-kind.ts b/packages/frontend/src/utility/device-kind.ts
index 7aadb617ca..7aadb617ca 100644
--- a/packages/frontend/src/scripts/device-kind.ts
+++ b/packages/frontend/src/utility/device-kind.ts
diff --git a/packages/frontend/src/scripts/emoji-picker.ts b/packages/frontend/src/utility/emoji-picker.ts
index 14b5cbf35e..6279786b2d 100644
--- a/packages/frontend/src/scripts/emoji-picker.ts
+++ b/packages/frontend/src/utility/emoji-picker.ts
@@ -3,9 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { defineAsyncComponent, Ref, ref } from 'vue';
+import { defineAsyncComponent, ref, watch } from 'vue';
+import type { Ref } from 'vue';
import { popup } from '@/os.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
/**
* 絵文字ピッカーを表示する。
@@ -24,7 +25,14 @@ class EmojiPicker {
}
public async init() {
- const emojisRef = defaultStore.reactiveState.pinnedEmojis;
+ const emojisRef = ref<string[]>([]);
+
+ watch([prefer.r.emojiPaletteForMain, prefer.r.emojiPalettes], () => {
+ emojisRef.value = prefer.s.emojiPaletteForMain == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForMain)?.emojis ?? [];
+ }, {
+ immediate: true,
+ });
+
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src: this.src,
pinnedEmojis: emojisRef,
diff --git a/packages/frontend/src/scripts/extract-mentions.ts b/packages/frontend/src/utility/extract-mentions.ts
index 89a5ce1df8..89a5ce1df8 100644
--- a/packages/frontend/src/scripts/extract-mentions.ts
+++ b/packages/frontend/src/utility/extract-mentions.ts
diff --git a/packages/frontend/src/scripts/extract-url-from-mfm.ts b/packages/frontend/src/utility/extract-url-from-mfm.ts
index a4c84aa740..baebbff8ae 100644
--- a/packages/frontend/src/scripts/extract-url-from-mfm.ts
+++ b/packages/frontend/src/utility/extract-url-from-mfm.ts
@@ -4,7 +4,7 @@
*/
import * as mfm from '@transfem-org/sfm-js';
-import { unique } from '@/scripts/array.js';
+import { unique } from '@/utility/array.js';
// unique without hash
// [ http://a/#1, http://a/#2, http://b/#3 ] => [ http://a/#1, http://b/#3 ]
diff --git a/packages/frontend/src/scripts/favicon-dot.ts b/packages/frontend/src/utility/favicon-dot.ts
index a903b4cc7f..a903b4cc7f 100644
--- a/packages/frontend/src/scripts/favicon-dot.ts
+++ b/packages/frontend/src/utility/favicon-dot.ts
diff --git a/packages/frontend/src/scripts/file-drop.ts b/packages/frontend/src/utility/file-drop.ts
index c2e863c0dc..4259fe25e9 100644
--- a/packages/frontend/src/scripts/file-drop.ts
+++ b/packages/frontend/src/utility/file-drop.ts
@@ -15,7 +15,7 @@ export type DroppedDirectory = {
isFile: false;
path: string;
children: DroppedItem[];
-}
+};
export async function extractDroppedItems(ev: DragEvent): Promise<DroppedItem[]> {
const dropItems = ev.dataTransfer?.items;
diff --git a/packages/frontend/src/scripts/focus-trap.ts b/packages/frontend/src/utility/focus-trap.ts
index fb7caea830..13d3bc56d2 100644
--- a/packages/frontend/src/scripts/focus-trap.ts
+++ b/packages/frontend/src/utility/focus-trap.ts
@@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
+import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
const focusTrapElements = new Set<HTMLElement>();
const ignoreElements = [
@@ -50,7 +50,7 @@ function releaseFocusTrap(el: HTMLElement): void {
const highestZIndexElement = getHighestZIndexElement();
- if (el.parentElement != null && el !== document.body) {
+ if (el.parentElement != null && el !== window.document.body) {
el.parentElement.childNodes.forEach((siblingNode) => {
const siblingEl = getHTMLElementOrNull(siblingNode);
if (!siblingEl) return;
@@ -104,7 +104,7 @@ export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEl
el.inert = false;
}
- if (el.parentElement != null && el !== document.body) {
+ if (el.parentElement != null && el !== window.document.body) {
el.parentElement.childNodes.forEach((siblingNode) => {
const siblingEl = getHTMLElementOrNull(siblingNode);
if (!siblingEl) return;
diff --git a/packages/frontend/src/scripts/focus.ts b/packages/frontend/src/utility/focus.ts
index 81278b17ea..cbbe8226d7 100644
--- a/packages/frontend/src/scripts/focus.ts
+++ b/packages/frontend/src/utility/focus.ts
@@ -4,7 +4,7 @@
*/
import { getScrollPosition, getScrollContainer, getStickyBottom, getStickyTop } from '@@/js/scroll.js';
-import { getElementOrNull, getNodeOrNull } from '@/scripts/get-dom-node-or-null.js';
+import { getElementOrNull, getNodeOrNull } from '@/utility/get-dom-node-or-null.js';
type MaybeHTMLElement = EventTarget | Node | Element | HTMLElement;
@@ -58,7 +58,7 @@ export const focusParent = (input: MaybeHTMLElement | null | undefined, self = f
const focusOrScroll = (element: HTMLElement, scroll: boolean) => {
if (scroll) {
- const scrollContainer = getScrollContainer(element) ?? document.documentElement;
+ const scrollContainer = getScrollContainer(element) ?? window.document.documentElement;
const scrollContainerTop = getScrollPosition(scrollContainer);
const stickyTop = getStickyTop(element, scrollContainer);
const stickyBottom = getStickyBottom(element, scrollContainer);
@@ -74,7 +74,7 @@ const focusOrScroll = (element: HTMLElement, scroll: boolean) => {
scrollContainer.scrollTo({ top: scrollTo, behavior: 'instant' });
}
- if (document.activeElement !== element) {
+ if (window.document.activeElement !== element) {
element.focus({ preventScroll: true });
}
};
diff --git a/packages/frontend/src/scripts/following-feed-utils.ts b/packages/frontend/src/utility/following-feed-utils.ts
index 39f17949d6..39f17949d6 100644
--- a/packages/frontend/src/scripts/following-feed-utils.ts
+++ b/packages/frontend/src/utility/following-feed-utils.ts
diff --git a/packages/frontend/src/scripts/form.ts b/packages/frontend/src/utility/form.ts
index 1032e97ac9..1032e97ac9 100644
--- a/packages/frontend/src/scripts/form.ts
+++ b/packages/frontend/src/utility/form.ts
diff --git a/packages/frontend/src/scripts/format-time-string.ts b/packages/frontend/src/utility/format-time-string.ts
index 35ad77d982..d383f143e1 100644
--- a/packages/frontend/src/scripts/format-time-string.ts
+++ b/packages/frontend/src/utility/format-time-string.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-const defaultLocaleStringFormats: {[index: string]: string} = {
+const defaultLocaleStringFormats: { [index: string]: string } = {
'weekday': 'narrow',
'era': 'narrow',
'year': 'numeric',
diff --git a/packages/frontend/src/scripts/fullscreen.ts b/packages/frontend/src/utility/fullscreen.ts
index 7a0a018ef3..6702393cf1 100644
--- a/packages/frontend/src/scripts/fullscreen.ts
+++ b/packages/frontend/src/utility/fullscreen.ts
@@ -35,8 +35,8 @@ export const requestFullscreen = ({ videoEl, playerEl, options }: RequestFullscr
export const exitFullscreen = ({ videoEl }: ExitFullscreenProps) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
- if (document.exitFullscreen != null) {
- document.exitFullscreen();
+ if (window.document.exitFullscreen != null) {
+ window.document.exitFullscreen();
return;
}
if (videoEl.webkitExitFullscreen != null) {
diff --git a/packages/frontend/src/scripts/get-account-from-id.ts b/packages/frontend/src/utility/get-account-from-id.ts
index 40afa10f2d..5d9662a747 100644
--- a/packages/frontend/src/scripts/get-account-from-id.ts
+++ b/packages/frontend/src/utility/get-account-from-id.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { get } from '@/scripts/idb-proxy.js';
+import { get } from '@/utility/idb-proxy.js';
export async function getAccountFromId(id: string) {
const accounts = await get('accounts') as { token: string; id: string; }[];
diff --git a/packages/frontend/src/scripts/get-appear-note.ts b/packages/frontend/src/utility/get-appear-note.ts
index 40ce80eac9..40ce80eac9 100644
--- a/packages/frontend/src/scripts/get-appear-note.ts
+++ b/packages/frontend/src/utility/get-appear-note.ts
diff --git a/packages/frontend/src/scripts/get-bg-color.ts b/packages/frontend/src/utility/get-bg-color.ts
index ccf60b454f..ccf60b454f 100644
--- a/packages/frontend/src/scripts/get-bg-color.ts
+++ b/packages/frontend/src/utility/get-bg-color.ts
diff --git a/packages/frontend/src/scripts/get-dom-node-or-null.ts b/packages/frontend/src/utility/get-dom-node-or-null.ts
index fbf54675fd..fbf54675fd 100644
--- a/packages/frontend/src/scripts/get-dom-node-or-null.ts
+++ b/packages/frontend/src/utility/get-dom-node-or-null.ts
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/utility/get-drive-file-menu.ts
index c8ab9238d3..3c6cbba002 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/utility/get-drive-file-menu.ts
@@ -5,12 +5,12 @@
import * as Misskey from 'misskey-js';
import { defineAsyncComponent } from 'vue';
+import type { MenuItem } from '@/types/menu.js';
import { i18n } from '@/i18n.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import type { MenuItem } from '@/types/menu.js';
-import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { prefer } from '@/preferences.js';
function rename(file: Misskey.entities.DriveFile) {
os.inputText({
@@ -65,7 +65,6 @@ function toggleSensitive(file: Misskey.entities.DriveFile) {
function copyUrl(file: Misskey.entities.DriveFile) {
copyToClipboard(file.url);
- os.success();
}
/*
@@ -148,9 +147,9 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
action: () => deleteFile(file),
});
- if (defaultStore.state.devMode) {
+ if (prefer.s.devMode) {
menuItems.push({ type: 'divider' }, {
- icon: 'ti ti-id',
+ icon: 'ti ti-hash',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(file.id);
diff --git a/packages/frontend/src/scripts/get-embed-code.ts b/packages/frontend/src/utility/get-embed-code.ts
index 158ab9c7f8..d458e64f19 100644
--- a/packages/frontend/src/scripts/get-embed-code.ts
+++ b/packages/frontend/src/utility/get-embed-code.ts
@@ -4,11 +4,11 @@
*/
import { defineAsyncComponent } from 'vue';
import { v4 as uuid } from 'uuid';
-import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
import { url } from '@@/js/config.js';
-import * as os from '@/os.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
+import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
+import * as os from '@/os.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
const MOBILE_THRESHOLD = 500;
@@ -74,7 +74,6 @@ export function genEmbedCode(entity: EmbeddableEntity, id: string, params?: Embe
// PCじゃない場合はコードカスタマイズ画面を出さずにそのままコピー
if (window.innerWidth < MOBILE_THRESHOLD) {
copyToClipboard(getEmbedCode(`/embed/${entity}/${id}`, _params));
- os.success();
} else {
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkEmbedCodeGenDialog.vue')), {
entity,
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts
index 463fec6f97..6762ce7e43 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/utility/get-note-menu.ts
@@ -3,25 +3,28 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { defineAsyncComponent, Ref, ShallowRef } from 'vue';
+import { defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
import { url } from '@@/js/config.js';
import { claimAchievement } from './achievements.js';
+import type { Ref, ShallowRef } from 'vue';
import type { MenuItem } from '@/types/menu.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { defaultStore, noteActions } from '@/store.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { store } from '@/store.js';
import { miLocalStorage } from '@/local-storage.js';
-import { getUserMenu } from '@/scripts/get-user-menu.js';
+import { getUserMenu } from '@/utility/get-user-menu.js';
import { clipsCache, favoritedChannelsCache } from '@/cache.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
-import { isSupportShare } from '@/scripts/navigator.js';
-import { getAppearNote } from '@/scripts/get-appear-note.js';
-import { genEmbedCode } from '@/scripts/get-embed-code.js';
+import { isSupportShare } from '@/utility/navigator.js';
+import { getAppearNote } from '@/utility/get-appear-note.js';
+import { genEmbedCode } from '@/utility/get-embed-code.js';
+import { prefer } from '@/preferences.js';
+import { getPluginHandlers } from '@/plugin.js';
export async function getNoteClipMenu(props: {
note: Misskey.entities.Note;
@@ -154,7 +157,6 @@ export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string):
text,
action: (): void => {
copyToClipboard(`${url}/notes/${note.id}`);
- os.success();
},
};
}
@@ -245,7 +247,6 @@ export function getNoteMenu(props: {
function copyContent(): void {
copyToClipboard(appearNote.text);
- os.success();
}
function togglePin(pin: boolean): void {
@@ -332,7 +333,6 @@ export function getNoteMenu(props: {
text: i18n.ts.copyRemoteLink,
action: () => {
copyToClipboard(appearNote.url ?? appearNote.uri);
- os.success();
},
}, {
icon: 'ti ti-external-link',
@@ -497,7 +497,6 @@ export function getNoteMenu(props: {
text: i18n.ts.copyRemoteLink,
action: () => {
copyToClipboard(appearNote.url ?? appearNote.uri);
- os.success();
},
}, {
icon: 'ti ti-external-link',
@@ -511,6 +510,7 @@ export function getNoteMenu(props: {
}
}
+ const noteActions = getPluginHandlers('note_action');
if (noteActions.length > 0) {
menuItems.push({ type: 'divider' });
@@ -523,13 +523,12 @@ export function getNoteMenu(props: {
})));
}
- if (defaultStore.state.devMode) {
+ if (prefer.s.devMode) {
menuItems.push({ type: 'divider' }, {
- icon: 'ti ti-id',
+ icon: 'ti ti-hash',
text: i18n.ts.copyNoteId,
action: () => {
copyToClipboard(appearNote.id);
- os.success();
},
});
}
@@ -574,7 +573,7 @@ export function getRenoteMenu(props: {
icon: 'ti ti-repeat',
action: () => {
const el = props.renoteButton.value;
- if (el) {
+ if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -612,7 +611,7 @@ export function getRenoteMenu(props: {
icon: 'ti ti-repeat',
action: () => {
const el = props.renoteButton.value;
- if (el) {
+ if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
@@ -621,8 +620,8 @@ export function getRenoteMenu(props: {
});
}
- const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
- const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
+ const configuredVisibility = prefer.s.rememberNoteVisibility ? store.s.visibility : prefer.s.defaultNoteVisibility;
+ const localOnly = prefer.s.rememberNoteVisibility ? store.s.localOnly : prefer.s.defaultNoteLocalOnly;
let visibility = appearNote.visibility;
visibility = smallerVisibility(visibility, configuredVisibility);
@@ -663,7 +662,7 @@ export function getRenoteMenu(props: {
text: channel.name,
action: () => {
const el = props.renoteButton.value;
- if (el) {
+ if (el && prefer.s.animation) {
const rect = el.getBoundingClientRect();
const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.offsetHeight / 2);
diff --git a/packages/frontend/src/scripts/get-note-summary.ts b/packages/frontend/src/utility/get-note-summary.ts
index 4e093bcf4c..4e093bcf4c 100644
--- a/packages/frontend/src/scripts/get-note-summary.ts
+++ b/packages/frontend/src/utility/get-note-summary.ts
diff --git a/packages/frontend/src/scripts/get-note-versions-menu.ts b/packages/frontend/src/utility/get-note-versions-menu.ts
index 345cec9018..345cec9018 100644
--- a/packages/frontend/src/scripts/get-note-versions-menu.ts
+++ b/packages/frontend/src/utility/get-note-versions-menu.ts
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/utility/get-user-menu.ts
index 2fbdaf5d3c..9693197ab4 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/utility/get-user-menu.ts
@@ -6,21 +6,22 @@
import { toUnicode } from 'punycode.js';
import { defineAsyncComponent, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
-import { i18n } from '@/i18n.js';
-import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { host, url } from '@@/js/config.js';
+import type { Router } from '@/router.js';
+import type { MenuItem } from '@/types/menu.js';
+import { i18n } from '@/i18n.js';
+import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { defaultStore, userActions } from '@/store.js';
-import { $i, iAmModerator } from '@/account.js';
-import { notesSearchAvailable, canSearchNonLocalNotes } from '@/scripts/check-permissions.js';
-import { IRouter } from '@/nirax.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { $i, iAmModerator } from '@/i.js';
+import { notesSearchAvailable, canSearchNonLocalNotes } from '@/utility/check-permissions.js';
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
-import { mainRouter } from '@/router/main.js';
-import { genEmbedCode } from '@/scripts/get-embed-code.js';
-import type { MenuItem } from '@/types/menu.js';
+import { mainRouter } from '@/router.js';
+import { genEmbedCode } from '@/utility/get-embed-code.js';
+import { prefer } from '@/preferences.js';
+import { getPluginHandlers } from '@/plugin.js';
-export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) {
+export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router = mainRouter) {
const meId = $i ? $i.id : null;
const cleanups = [] as (() => void)[];
@@ -149,24 +150,6 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
const menuItems: MenuItem[] = [];
- menuItems.push({
- icon: 'ti ti-at',
- text: i18n.ts.copyUsername,
- action: () => {
- copyToClipboard(`@${user.username}@${user.host ?? host}`);
- },
- });
-
- if (notesSearchAvailable && (user.host == null || canSearchNonLocalNotes)) {
- menuItems.push({
- icon: 'ti ti-search',
- text: i18n.ts.searchThisUsersNotes,
- action: () => {
- router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`);
- },
- });
- }
-
if (iAmModerator) {
menuItems.push({
icon: 'ti ti-user-exclamation',
@@ -174,10 +157,27 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
action: () => {
router.push(`/admin/user/${user.id}`);
},
- });
+ }, { type: 'divider' });
}
menuItems.push({
+ icon: 'ti ti-at',
+ text: i18n.ts.copyUsername,
+ action: () => {
+ copyToClipboard(`@${user.username}@${user.host ?? host}`);
+ },
+ });
+
+ menuItems.push({
+ icon: 'ti ti-share',
+ text: i18n.ts.copyProfileUrl,
+ action: () => {
+ const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
+ copyToClipboard(`${url}/${canonical}`);
+ },
+ });
+
+ menuItems.push({
icon: 'ti ti-rss',
text: i18n.ts.copyRSS,
action: () => {
@@ -208,24 +208,18 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
});
}
- menuItems.push({
- icon: 'ti ti-share',
- text: i18n.ts.copyProfileUrl,
- action: () => {
- const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
- copyToClipboard(`${url}/${canonical}`);
- },
- });
-
- if ($i) {
+ if (notesSearchAvailable && (user.host == null || canSearchNonLocalNotes)) {
menuItems.push({
- icon: 'ti ti-mail',
- text: i18n.ts.sendMessage,
+ icon: 'ti ti-search',
+ text: i18n.ts.searchThisUsersNotes,
action: () => {
- const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
- os.post({ specified: user, initialText: `${canonical} ` });
+ router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`);
},
- }, { type: 'divider' }, {
+ });
+ }
+
+ if ($i) {
+ menuItems.push({ type: 'divider' }, {
icon: 'ti ti-pencil',
text: i18n.ts.editMemo,
action: editMemo,
@@ -250,7 +244,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
listId: list.id,
userId: user.id,
}).then(() => {
- list.userIds?.splice(list.userIds?.indexOf(user.id), 1);
+ list.userIds?.splice(list.userIds.indexOf(user.id), 1);
});
}
}));
@@ -361,6 +355,18 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
//}
menuItems.push({ type: 'divider' }, {
+ icon: 'ti ti-mail',
+ text: i18n.ts.sendMessage,
+ action: () => {
+ const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
+ os.post({ specified: user, initialText: `${canonical} ` });
+ },
+ }, {
+ type: 'link',
+ icon: 'ti ti-messages',
+ text: i18n.ts._chat.chatWithThisUser,
+ to: `/chat/user/${user.id}`,
+ }, { type: 'divider' }, {
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
action: toggleMute,
@@ -397,9 +403,9 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
});
}
- if (defaultStore.state.devMode) {
+ if (prefer.s.devMode) {
menuItems.push({ type: 'divider' }, {
- icon: 'ti ti-id',
+ icon: 'ti ti-hash',
text: i18n.ts.copyUserId,
action: () => {
copyToClipboard(user.id);
@@ -417,6 +423,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
});
}
+ const userActions = getPluginHandlers('user_action');
if (userActions.length > 0) {
menuItems.push({ type: 'divider' }, ...userActions.map(action => ({
icon: 'ti ti-plug',
diff --git a/packages/frontend/src/scripts/get-user-name.ts b/packages/frontend/src/utility/get-user-name.ts
index 56e91abba0..56e91abba0 100644
--- a/packages/frontend/src/scripts/get-user-name.ts
+++ b/packages/frontend/src/utility/get-user-name.ts
diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/utility/hotkey.ts
index d5304ee210..852abb6140 100644
--- a/packages/frontend/src/scripts/hotkey.ts
+++ b/packages/frontend/src/utility/hotkey.ts
@@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { getHTMLElementOrNull } from "@/scripts/get-dom-node-or-null.js";
+import { getHTMLElementOrNull } from "@/utility/get-dom-node-or-null.js";
//#region types
export type Keymap = Record<string, CallbackFunction | CallbackObject>;
@@ -54,9 +54,9 @@ export const makeHotkey = (keymap: Keymap) => {
const actions = parseKeymap(keymap);
return (ev: KeyboardEvent) => {
if ('pswp' in window && window.pswp != null) return;
- if (document.activeElement != null) {
- if (IGNORE_ELEMENTS.includes(document.activeElement.tagName.toLowerCase())) return;
- if (getHTMLElementOrNull(document.activeElement)?.isContentEditable) return;
+ if (window.document.activeElement != null) {
+ if (IGNORE_ELEMENTS.includes(window.document.activeElement.tagName.toLowerCase())) return;
+ if (getHTMLElementOrNull(window.document.activeElement)?.isContentEditable) return;
}
for (const action of actions) {
if (matchPatterns(ev, action)) {
diff --git a/packages/frontend/src/scripts/idb-proxy.ts b/packages/frontend/src/utility/idb-proxy.ts
index 20f51660c7..20f51660c7 100644
--- a/packages/frontend/src/scripts/idb-proxy.ts
+++ b/packages/frontend/src/utility/idb-proxy.ts
diff --git a/packages/frontend/src/scripts/idle-render.ts b/packages/frontend/src/utility/idle-render.ts
index 6adfedcb9f..6adfedcb9f 100644
--- a/packages/frontend/src/scripts/idle-render.ts
+++ b/packages/frontend/src/utility/idle-render.ts
diff --git a/packages/frontend/src/scripts/init-chart.ts b/packages/frontend/src/utility/init-chart.ts
index 41e1636aa7..260899c1d7 100644
--- a/packages/frontend/src/scripts/init-chart.ts
+++ b/packages/frontend/src/utility/init-chart.ts
@@ -24,7 +24,7 @@ import {
import gradient from 'chartjs-plugin-gradient';
import zoomPlugin from 'chartjs-plugin-zoom';
import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
import 'chartjs-adapter-date-fns';
export function initChart() {
@@ -50,9 +50,9 @@ export function initChart() {
);
// フォントカラー
- Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg');
+ Chart.defaults.color = getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-fg');
- Chart.defaults.borderColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
+ Chart.defaults.borderColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
Chart.defaults.animation = false;
}
diff --git a/packages/frontend/src/scripts/initialize-sw.ts b/packages/frontend/src/utility/initialize-sw.ts
index 867ebf19ed..867ebf19ed 100644
--- a/packages/frontend/src/scripts/initialize-sw.ts
+++ b/packages/frontend/src/utility/initialize-sw.ts
diff --git a/packages/frontend/src/scripts/intl-const.ts b/packages/frontend/src/utility/intl-const.ts
index 385f59ec39..385f59ec39 100644
--- a/packages/frontend/src/scripts/intl-const.ts
+++ b/packages/frontend/src/utility/intl-const.ts
diff --git a/packages/frontend/src/utility/intl-string.ts b/packages/frontend/src/utility/intl-string.ts
new file mode 100644
index 0000000000..a5b5bbb592
--- /dev/null
+++ b/packages/frontend/src/utility/intl-string.ts
@@ -0,0 +1,97 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { versatileLang } from '@@/js/intl-const.js';
+import type { toHiragana as toHiraganaType } from 'wanakana';
+
+let toHiragana: typeof toHiraganaType = (str?: string) => str ?? '';
+let isWanakanaLoaded = false;
+
+/**
+ * ローマ字変換のセットアップ(日本語以外の環境で読み込まないのでlazy-loading)
+ *
+ * ここの比較系関数を使う際は事前に呼び出す必要がある
+ */
+export async function initIntlString(forceWanakana = false) {
+ if ((!versatileLang.includes('ja') && !forceWanakana) || isWanakanaLoaded) return;
+ const { toHiragana: _toHiragana } = await import('wanakana');
+ toHiragana = _toHiragana;
+ isWanakanaLoaded = true;
+}
+
+/**
+ * - 全角英数字を半角に
+ * - 半角カタカナを全角に
+ * - 濁点・半濁点がリガチャになっている(例: `か` + `゛` )ひらがな・カタカナを結合
+ * - 異体字を正規化
+ * - 小文字に揃える
+ * - 文字列のトリム
+ */
+export function normalizeString(str: string) {
+ const segmenter = new Intl.Segmenter(versatileLang, { granularity: 'grapheme' });
+ return [...segmenter.segment(str)].map(({ segment }) => segment.normalize('NFKC')).join('').toLowerCase().trim();
+}
+
+// https://qiita.com/non-caffeine/items/77360dda05c8ce510084
+const hyphens = [
+ 0x002d, // hyphen-minus
+ 0x02d7, // modifier letter minus sign
+ 0x1173, // hangul jongseong eu
+ 0x1680, // ogham space mark
+ 0x1b78, // balinese musical symbol left-hand open pang
+ 0x2010, // hyphen
+ 0x2011, // non-breaking hyphen
+ 0x2012, // figure dash
+ 0x2013, // en dash
+ 0x2014, // em dash
+ 0x2015, // horizontal bar
+ 0x2043, // hyphen bullet
+ 0x207b, // superscript minus
+ 0x2212, // minus sign
+ 0x25ac, // black rectangle
+ 0x2500, // box drawings light horizontal
+ 0x2501, // box drawings heavy horizontal
+ 0x2796, // heavy minus sign
+ 0x30fc, // katakana-hiragana prolonged sound mark
+ 0x3161, // hangul letter eu
+ 0xfe58, // small em dash
+ 0xfe63, // small hyphen-minus
+ 0xff0d, // fullwidth hyphen-minus
+ 0xff70, // halfwidth katakana-hiragana prolonged sound mark
+ 0x10110, // aegean number ten
+ 0x10191, // roman uncia sign
+];
+
+const hyphensCodePoints = hyphens.map(code => `\\u{${code.toString(16).padStart(4, '0')}}`);
+
+/** ハイフンを統一(ローマ字半角入力時に`ー`と`-`が判定できない問題の調整) */
+export function normalizeHyphens(str: string) {
+ return str.replace(new RegExp(`[${hyphensCodePoints.join('')}]`, 'ug'), '\u002d');
+}
+
+/**
+ * `normalizeString` に加えて、カタカナ・ローマ字をひらがなに揃え、ハイフンを統一
+ *
+ * (ローマ字じゃないものもローマ字として認識され変換されるので、文字列比較の際は `normalizeString` を併用する必要あり)
+ */
+export function normalizeStringWithHiragana(str: string) {
+ return normalizeHyphens(toHiragana(normalizeString(str), { convertLongVowelMark: false }));
+}
+
+/** aとbが同じかどうか */
+export function compareStringEquals(a: string, b: string) {
+ return (
+ normalizeString(a) === normalizeString(b) ||
+ normalizeStringWithHiragana(a) === normalizeStringWithHiragana(b)
+ );
+}
+
+/** baseにqueryが含まれているかどうか */
+export function compareStringIncludes(base: string, query: string) {
+ return (
+ normalizeString(base).includes(normalizeString(query)) ||
+ normalizeStringWithHiragana(base).includes(normalizeStringWithHiragana(query))
+ );
+}
diff --git a/packages/frontend/src/scripts/is-device-darkmode.ts b/packages/frontend/src/utility/is-device-darkmode.ts
index 4f487c7cb9..4f487c7cb9 100644
--- a/packages/frontend/src/scripts/is-device-darkmode.ts
+++ b/packages/frontend/src/utility/is-device-darkmode.ts
diff --git a/packages/frontend/src/scripts/isFfVisibleForMe.ts b/packages/frontend/src/utility/isFfVisibleForMe.ts
index e28e5725bc..48ef1c4e49 100644
--- a/packages/frontend/src/scripts/isFfVisibleForMe.ts
+++ b/packages/frontend/src/utility/isFfVisibleForMe.ts
@@ -4,7 +4,7 @@
*/
import * as Misskey from 'misskey-js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
export function isFollowingVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
if ($i && ($i.id === user.id || $i.isAdmin || $i.isModerator)) return true;
diff --git a/packages/frontend/src/scripts/key-event.ts b/packages/frontend/src/utility/key-event.ts
index a72776d48c..020a6c2174 100644
--- a/packages/frontend/src/scripts/key-event.ts
+++ b/packages/frontend/src/utility/key-event.ts
@@ -7,7 +7,7 @@
* {@link KeyboardEvent.code} の値を表す文字列。不足分は適宜追加する
* @see https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values
*/
-export type KeyCode =
+export type KeyCode = (
| 'Backspace'
| 'Tab'
| 'Enter'
@@ -94,32 +94,32 @@ export type KeyCode =
| 'Quote'
| 'Meta'
| 'AltGraph'
- ;
+);
/**
* 修飾キーを表す文字列。不足分は適宜追加する。
*/
-export type KeyModifier =
+export type KeyModifier = (
| 'Shift'
| 'Control'
| 'Alt'
| 'Meta'
- ;
+);
/**
* 押下されたキー以外の状態を表す文字列。不足分は適宜追加する。
*/
-export type KeyState =
+export type KeyState = (
| 'composing'
| 'repeat'
- ;
+);
export type KeyEventHandler = {
modifiers?: KeyModifier[];
states?: KeyState[];
code: KeyCode | 'any';
handler: (event: KeyboardEvent) => void;
-}
+};
export function handleKeyEvent(event: KeyboardEvent, handlers: KeyEventHandler[]) {
function checkModifier(ev: KeyboardEvent, modifiers? : KeyModifier[]) {
diff --git a/packages/frontend/src/scripts/langmap.ts b/packages/frontend/src/utility/langmap.ts
index b32de15963..b32de15963 100644
--- a/packages/frontend/src/scripts/langmap.ts
+++ b/packages/frontend/src/utility/langmap.ts
diff --git a/packages/frontend/src/scripts/libopenmpt/LICENSE b/packages/frontend/src/utility/libopenmpt/LICENSE
index 2daefe981f..2daefe981f 100644
--- a/packages/frontend/src/scripts/libopenmpt/LICENSE
+++ b/packages/frontend/src/utility/libopenmpt/LICENSE
diff --git a/packages/frontend/src/scripts/libopenmpt/libopenmpt.js b/packages/frontend/src/utility/libopenmpt/libopenmpt.js
index e2535529ce..e2535529ce 100644
--- a/packages/frontend/src/scripts/libopenmpt/libopenmpt.js
+++ b/packages/frontend/src/utility/libopenmpt/libopenmpt.js
diff --git a/packages/frontend/src/scripts/libopenmpt/libopenmpt.wasm b/packages/frontend/src/utility/libopenmpt/libopenmpt.wasm
index 8c11a68d5d..8c11a68d5d 100644
--- a/packages/frontend/src/scripts/libopenmpt/libopenmpt.wasm
+++ b/packages/frontend/src/utility/libopenmpt/libopenmpt.wasm
Binary files differ
diff --git a/packages/frontend/src/scripts/libopenmpt/readme.md b/packages/frontend/src/utility/libopenmpt/readme.md
index 4b99a6c40f..4b99a6c40f 100644
--- a/packages/frontend/src/scripts/libopenmpt/readme.md
+++ b/packages/frontend/src/utility/libopenmpt/readme.md
diff --git a/packages/frontend/src/scripts/login-id.ts b/packages/frontend/src/utility/login-id.ts
index b52735caa0..b52735caa0 100644
--- a/packages/frontend/src/scripts/login-id.ts
+++ b/packages/frontend/src/utility/login-id.ts
diff --git a/packages/frontend/src/utility/lookup.ts b/packages/frontend/src/utility/lookup.ts
new file mode 100644
index 0000000000..90611094fa
--- /dev/null
+++ b/packages/frontend/src/utility/lookup.ts
@@ -0,0 +1,84 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { Router } from '@/router.js';
+import * as os from '@/os.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { i18n } from '@/i18n.js';
+import { mainRouter } from '@/router.js';
+
+export async function lookup(router?: Router) {
+ const _router = router ?? mainRouter;
+
+ const { canceled, result: temp } = await os.inputText({
+ title: i18n.ts.lookup,
+ });
+ const query = temp ? temp.trim() : '';
+ if (canceled || query.length <= 1) return;
+
+ if (query.startsWith('@') && !query.includes(' ')) {
+ _router.push(`/${query}`);
+ return;
+ }
+
+ if (query.startsWith('#')) {
+ _router.push(`/tags/${encodeURIComponent(query.substring(1))}`);
+ return;
+ }
+
+ if (query.startsWith('https://')) {
+ const res = await apLookup(query);
+
+ if (res.type === 'User') {
+ _router.push(`/@${res.object.username}@${res.object.host}`);
+ } else if (res.type === 'Note') {
+ _router.push(`/notes/${res.object.id}`);
+ }
+
+ return;
+ }
+}
+
+export async function apLookup(query: string) {
+ const promise = misskeyApi('ap/show', {
+ uri: query,
+ });
+
+ os.promiseDialog(promise, null, (err) => {
+ let title = i18n.ts.somethingHappened;
+ let text = err.message + '\n' + err.id;
+
+ switch (err.id) {
+ case '974b799e-1a29-4889-b706-18d4dd93e266':
+ title = i18n.ts._remoteLookupErrors._federationNotAllowed.title;
+ text = i18n.ts._remoteLookupErrors._federationNotAllowed.description;
+ break;
+ case '1a5eab56-e47b-48c2-8d5e-217b897d70db':
+ title = i18n.ts._remoteLookupErrors._uriInvalid.title;
+ text = i18n.ts._remoteLookupErrors._uriInvalid.description;
+ break;
+ case '81b539cf-4f57-4b29-bc98-032c33c0792e':
+ title = i18n.ts._remoteLookupErrors._requestFailed.title;
+ text = i18n.ts._remoteLookupErrors._requestFailed.description;
+ break;
+ case '70193c39-54f3-4813-82f0-70a680f7495b':
+ title = i18n.ts._remoteLookupErrors._responseInvalid.title;
+ text = i18n.ts._remoteLookupErrors._responseInvalid.description;
+ break;
+ case 'dc94d745-1262-4e63-a17d-fecaa57efc82':
+ title = i18n.ts._remoteLookupErrors._noSuchObject.title;
+ text = i18n.ts._remoteLookupErrors._noSuchObject.description;
+ break;
+ }
+
+ os.alert({
+ type: 'error',
+ title,
+ text,
+ });
+ }, i18n.ts.fetchingAsApObject);
+
+ return await promise;
+}
diff --git a/packages/frontend/src/scripts/media-has-audio.ts b/packages/frontend/src/utility/media-has-audio.ts
index 4bf3ee5d97..4bf3ee5d97 100644
--- a/packages/frontend/src/scripts/media-has-audio.ts
+++ b/packages/frontend/src/utility/media-has-audio.ts
diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/utility/media-proxy.ts
index 78eba35ead..78eba35ead 100644
--- a/packages/frontend/src/scripts/media-proxy.ts
+++ b/packages/frontend/src/utility/media-proxy.ts
diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/utility/merge.ts
index 004b6d42a4..004b6d42a4 100644
--- a/packages/frontend/src/scripts/merge.ts
+++ b/packages/frontend/src/utility/merge.ts
diff --git a/packages/frontend/src/scripts/mfm-function-picker.ts b/packages/frontend/src/utility/mfm-function-picker.ts
index 2911469cdd..2d0f950f20 100644
--- a/packages/frontend/src/scripts/mfm-function-picker.ts
+++ b/packages/frontend/src/utility/mfm-function-picker.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Ref, nextTick } from 'vue';
+import { nextTick } from 'vue';
+import type { Ref } from 'vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { MFM_TAGS } from '@@/js/const.js';
diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/utility/misskey-api.ts
index dc07ad477b..72ba54ade3 100644
--- a/packages/frontend/src/scripts/misskey-api.ts
+++ b/packages/frontend/src/utility/misskey-api.ts
@@ -6,7 +6,7 @@
import * as Misskey from 'misskey-js';
import { ref } from 'vue';
import { apiUrl } from '@@/js/config.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
export const pendingApiRequestsCount = ref(0);
export type Endpoint = keyof Misskey.Endpoints;
diff --git a/packages/frontend/src/scripts/navigator.ts b/packages/frontend/src/utility/navigator.ts
index ffc0a457f4..ffc0a457f4 100644
--- a/packages/frontend/src/scripts/navigator.ts
+++ b/packages/frontend/src/utility/navigator.ts
diff --git a/packages/frontend/src/scripts/physics.ts b/packages/frontend/src/utility/physics.ts
index 8a4e9319b3..5de34fd094 100644
--- a/packages/frontend/src/scripts/physics.ts
+++ b/packages/frontend/src/utility/physics.ts
@@ -28,7 +28,7 @@ export function physics(container: HTMLElement) {
// create renderer
const render = Matter.Render.create({
engine: engine,
- //element: document.getElementById('debug'),
+ //element: window.document.getElementById('debug'),
options: {
width: containerWidth,
height: containerHeight,
diff --git a/packages/frontend/src/scripts/player-url-transform.ts b/packages/frontend/src/utility/player-url-transform.ts
index 39c6df6500..39c6df6500 100644
--- a/packages/frontend/src/scripts/player-url-transform.ts
+++ b/packages/frontend/src/utility/player-url-transform.ts
diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/utility/please-login.ts
index a8a330eb6d..9253105f48 100644
--- a/packages/frontend/src/scripts/please-login.ts
+++ b/packages/frontend/src/utility/please-login.ts
@@ -4,7 +4,7 @@
*/
import { defineAsyncComponent } from 'vue';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { popup } from '@/os.js';
diff --git a/packages/frontend/src/scripts/popout.ts b/packages/frontend/src/utility/popout.ts
index 5b141222e8..5b141222e8 100644
--- a/packages/frontend/src/scripts/popout.ts
+++ b/packages/frontend/src/utility/popout.ts
diff --git a/packages/frontend/src/scripts/popup-position.ts b/packages/frontend/src/utility/popup-position.ts
index be49532cf8..be49532cf8 100644
--- a/packages/frontend/src/scripts/popup-position.ts
+++ b/packages/frontend/src/utility/popup-position.ts
diff --git a/packages/frontend/src/scripts/post-message.ts b/packages/frontend/src/utility/post-message.ts
index 11b6f52ddd..11b6f52ddd 100644
--- a/packages/frontend/src/scripts/post-message.ts
+++ b/packages/frontend/src/utility/post-message.ts
diff --git a/packages/frontend/src/utility/random-id.ts b/packages/frontend/src/utility/random-id.ts
new file mode 100644
index 0000000000..4e5943a97f
--- /dev/null
+++ b/packages/frontend/src/utility/random-id.ts
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+const CHARS = 'abcdefghijklmnopqrstuvwxyz'; // CSSの<custom-ident>などで使われることもあるのでa-z以外使うな
+
+export function randomId(length = 32, characters = CHARS) {
+ let result = '';
+ const charactersLength = characters.length;
+ for ( let i = 0; i < length; i++ ) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+}
diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/utility/reaction-picker.ts
index 7aec05c0cf..7c159fa2da 100644
--- a/packages/frontend/src/scripts/reaction-picker.ts
+++ b/packages/frontend/src/utility/reaction-picker.ts
@@ -4,9 +4,10 @@
*/
import * as Misskey from 'misskey-js';
-import { defineAsyncComponent, Ref, ref } from 'vue';
+import { defineAsyncComponent, ref, watch } from 'vue';
+import type { Ref } from 'vue';
import { popup } from '@/os.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
class ReactionPicker {
private src: Ref<HTMLElement | null> = ref(null);
@@ -20,7 +21,14 @@ class ReactionPicker {
}
public async init() {
- const reactionsRef = defaultStore.reactiveState.reactions;
+ const reactionsRef = ref<string[]>([]);
+
+ watch([prefer.r.emojiPaletteForReaction, prefer.r.emojiPalettes], () => {
+ reactionsRef.value = prefer.s.emojiPaletteForReaction == null ? prefer.s.emojiPalettes[0].emojis : prefer.s.emojiPalettes.find(palette => palette.id === prefer.s.emojiPaletteForReaction)?.emojis ?? [];
+ }, {
+ immediate: true,
+ });
+
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src: this.src,
pinnedEmojis: reactionsRef,
diff --git a/packages/frontend/src/scripts/reload-ask.ts b/packages/frontend/src/utility/reload-ask.ts
index 733d91b85a..7c7ea113d4 100644
--- a/packages/frontend/src/scripts/reload-ask.ts
+++ b/packages/frontend/src/utility/reload-ask.ts
@@ -5,7 +5,7 @@
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { unisonReload } from '@/utility/unison-reload.js';
let isReloadConfirming = false;
@@ -35,6 +35,6 @@ export async function reloadAsk(opts: {
if (opts.unison) {
unisonReload();
} else {
- location.reload();
+ window.location.reload();
}
}
diff --git a/packages/frontend/src/scripts/sanitize-html.ts b/packages/frontend/src/utility/sanitize-html.ts
index fc9db9bbdb..fc9db9bbdb 100644
--- a/packages/frontend/src/scripts/sanitize-html.ts
+++ b/packages/frontend/src/utility/sanitize-html.ts
diff --git a/packages/frontend/src/scripts/search-emoji.ts b/packages/frontend/src/utility/search-emoji.ts
index 4192a2df8f..4192a2df8f 100644
--- a/packages/frontend/src/scripts/search-emoji.ts
+++ b/packages/frontend/src/utility/search-emoji.ts
diff --git a/packages/frontend/src/scripts/search-engine-map.ts b/packages/frontend/src/utility/search-engine-map.ts
index 03e5061597..03e5061597 100644
--- a/packages/frontend/src/scripts/search-engine-map.ts
+++ b/packages/frontend/src/utility/search-engine-map.ts
diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/utility/select-file.ts
index c25b4d73bd..b9b3687483 100644
--- a/packages/frontend/src/scripts/select-file.ts
+++ b/packages/frontend/src/utility/select-file.ts
@@ -6,11 +6,11 @@
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
-import { uploadFile } from '@/scripts/upload.js';
+import { uploadFile } from '@/utility/upload.js';
+import { prefer } from '@/preferences.js';
export function chooseFileFromPc(
multiple: boolean,
@@ -20,12 +20,12 @@ export function chooseFileFromPc(
nameConverter?: (file: File) => string | undefined;
},
): Promise<Misskey.entities.DriveFile[]> {
- const uploadFolder = options?.uploadFolder ?? defaultStore.state.uploadFolder;
- const keepOriginal = options?.keepOriginal ?? defaultStore.state.keepOriginalUploading;
+ const uploadFolder = options?.uploadFolder ?? prefer.s.uploadFolder;
+ const keepOriginal = options?.keepOriginal ?? prefer.s.keepOriginalUploading;
const nameConverter = options?.nameConverter ?? (() => undefined);
return new Promise((res, rej) => {
- const input = document.createElement('input');
+ const input = window.document.createElement('input');
input.type = 'file';
input.multiple = multiple;
input.onchange = () => {
@@ -82,7 +82,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
misskeyApi('drive/files/upload-from-url', {
url: url,
- folderId: defaultStore.state.uploadFolder,
+ folderId: prefer.s.uploadFolder,
marker,
});
@@ -96,7 +96,7 @@ export function chooseFileFromUrl(): 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);
+ const keepOriginal = ref(prefer.s.keepOriginalUploading);
os.popupMenu([label ? {
text: label,
diff --git a/packages/frontend/src/scripts/show-moved-dialog.ts b/packages/frontend/src/utility/show-moved-dialog.ts
index 35b3ef79d8..db21b028cd 100644
--- a/packages/frontend/src/scripts/show-moved-dialog.ts
+++ b/packages/frontend/src/utility/show-moved-dialog.ts
@@ -4,7 +4,7 @@
*/
import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
export function showMovedDialog() {
diff --git a/packages/frontend/src/scripts/show-suspended-dialog.ts b/packages/frontend/src/utility/show-suspended-dialog.ts
index 8b89dbb936..8b89dbb936 100644
--- a/packages/frontend/src/scripts/show-suspended-dialog.ts
+++ b/packages/frontend/src/utility/show-suspended-dialog.ts
diff --git a/packages/frontend/src/scripts/show-system-account-dialog.ts b/packages/frontend/src/utility/show-system-account-dialog.ts
index 3c28d901fc..3c28d901fc 100644
--- a/packages/frontend/src/scripts/show-system-account-dialog.ts
+++ b/packages/frontend/src/utility/show-system-account-dialog.ts
diff --git a/packages/frontend/src/scripts/shuffle.ts b/packages/frontend/src/utility/shuffle.ts
index 1f6ef1928c..1f6ef1928c 100644
--- a/packages/frontend/src/scripts/shuffle.ts
+++ b/packages/frontend/src/utility/shuffle.ts
diff --git a/packages/frontend/src/scripts/snowfall-effect.ts b/packages/frontend/src/utility/snowfall-effect.ts
index d88bdb6660..5c86969876 100644
--- a/packages/frontend/src/scripts/snowfall-effect.ts
+++ b/packages/frontend/src/utility/snowfall-effect.ts
@@ -156,7 +156,7 @@ export class SnowfallEffect {
easing: 0.0005,
};
/**
- * @throws {Error} - Thrown when it fails to get WebGL context for the canvas
+ * @throws {Error} - Thrown when it fails to get WebGL context for the canvas
*/
constructor(options: {
sakura?: boolean;
@@ -172,7 +172,7 @@ export class SnowfallEffect {
const gl = canvas.getContext('webgl2', { antialias: true });
if (gl == null) throw new Error('Failed to get WebGL context');
- document.body.append(canvas);
+ window.document.body.append(canvas);
this.canvas = canvas;
this.gl = gl;
@@ -190,7 +190,7 @@ export class SnowfallEffect {
}
private initCanvas(): HTMLCanvasElement {
- const canvas = document.createElement('canvas');
+ const canvas = window.document.createElement('canvas');
Object.assign(canvas.style, {
position: 'fixed',
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/utility/sound.ts
index 2008afe045..f217bdfcd5 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/utility/sound.ts
@@ -3,8 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import type { SoundStore } from '@/store.js';
-import { defaultStore } from '@/store.js';
+import type { SoundStore } from '@/preferences/def.js';
+import { prefer } from '@/preferences.js';
+import { PREF_DEF } from '@/preferences/def.js';
let ctx: AudioContext;
const cache = new Map<string, AudioBuffer>();
@@ -76,6 +77,7 @@ export const operationTypes = [
'note',
'notification',
'reaction',
+ 'chatMessage',
] as const;
/** サウンドの種類 */
@@ -107,7 +109,7 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; })
let response: Response;
try {
- response = await fetch(url);
+ response = await window.fetch(url);
} catch (err) {
return;
}
@@ -127,11 +129,11 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; })
* @param type スプライトの種類を指定
*/
export function playMisskeySfx(operationType: OperationType) {
- const sound = defaultStore.state[`sound_${operationType}`];
+ const sound = prefer.s[`sound.on.${operationType}`];
playMisskeySfxFile(sound).then((succeed) => {
if (!succeed && sound.type === '_driveFile_') {
// ドライブファイルが存在しない場合はデフォルトのサウンドを再生する
- const soundName = defaultStore.def[`sound_${operationType}`].default.type as Exclude<SoundType, '_driveFile_'>;
+ const soundName = PREF_DEF[`sound_${operationType}`].default.type as Exclude<SoundType, '_driveFile_'>;
if (_DEV_) console.log(`Failed to play sound: ${sound.fileUrl}, so play default sound: ${soundName}`);
playMisskeySfxFileInternal({
type: soundName,
@@ -166,7 +168,7 @@ async function playMisskeySfxFileInternal(soundStore: SoundStore): Promise<boole
if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
return false;
}
- const masterVolume = defaultStore.state.sound_masterVolume;
+ const masterVolume = prefer.s['sound.masterVolume'];
if (isMute() || masterVolume === 0 || soundStore.volume === 0) {
return true; // ミュート時は成功として扱う
}
@@ -198,10 +200,10 @@ export function createSourceNode(buffer: AudioBuffer, opts: {
pan?: number;
playbackRate?: number;
}): {
- soundSource: AudioBufferSourceNode;
- panNode: StereoPannerNode;
- gainNode: GainNode;
-} {
+ soundSource: AudioBufferSourceNode;
+ panNode: StereoPannerNode;
+ gainNode: GainNode;
+ } {
const panNode = ctx.createStereoPanner();
panNode.pan.value = opts.pan ?? 0;
@@ -225,7 +227,7 @@ export function createSourceNode(buffer: AudioBuffer, opts: {
* @param file ファイルのURL(ドライブIDではない)
*/
export async function getSoundDuration(file: string): Promise<number> {
- const audioEl = document.createElement('audio');
+ const audioEl = window.document.createElement('audio');
audioEl.src = file;
return new Promise((resolve) => {
const si = setInterval(() => {
@@ -242,13 +244,13 @@ export async function getSoundDuration(file: string): Promise<number> {
* ミュートすべきかどうかを判断する
*/
export function isMute(): boolean {
- if (defaultStore.state.sound_notUseSound) {
+ if (prefer.s['sound.notUseSound']) {
// サウンドを出力しない
return true;
}
// noinspection RedundantIfStatementJS
- if (defaultStore.state.sound_useSoundOnlyWhenActive && document.visibilityState === 'hidden') {
+ if (prefer.s['sound.useSoundOnlyWhenActive'] && window.document.visibilityState === 'hidden') {
// ブラウザがアクティブな時のみサウンドを出力する
return true;
}
diff --git a/packages/frontend/src/scripts/sticky-sidebar.ts b/packages/frontend/src/utility/sticky-sidebar.ts
index 50f1e6ecc8..867c9b8324 100644
--- a/packages/frontend/src/scripts/sticky-sidebar.ts
+++ b/packages/frontend/src/utility/sticky-sidebar.ts
@@ -18,7 +18,7 @@ export class StickySidebar {
this.container = container;
this.el = this.container.children[0] as HTMLElement;
this.el.style.position = 'sticky';
- this.spacer = document.createElement('div');
+ this.spacer = window.document.createElement('div');
this.container.prepend(this.spacer);
this.marginTop = marginTop;
this.offsetTop = this.container.getBoundingClientRect().top;
diff --git a/packages/frontend/src/scripts/stream-mock.ts b/packages/frontend/src/utility/stream-mock.ts
index cb0e607fcb..9b1b368de4 100644
--- a/packages/frontend/src/scripts/stream-mock.ts
+++ b/packages/frontend/src/utility/stream-mock.ts
@@ -37,9 +37,9 @@ export class StreamMock extends EventEmitter<StreamEvents> implements IStream {
// do nothing
}
- public send(typeOrPayload: string): void
- public send(typeOrPayload: string, payload: any): void
- public send(typeOrPayload: Record<string, any> | any[]): void
+ public send(typeOrPayload: string): void;
+ public send(typeOrPayload: string, payload: any): void;
+ public send(typeOrPayload: Record<string, any> | any[]): void;
public send(typeOrPayload: string | Record<string, any> | any[], payload?: any): void {
// do nothing
}
diff --git a/packages/frontend/src/scripts/test-utils.ts b/packages/frontend/src/utility/test-utils.ts
index 52bb2d94e0..52bb2d94e0 100644
--- a/packages/frontend/src/scripts/test-utils.ts
+++ b/packages/frontend/src/utility/test-utils.ts
diff --git a/packages/frontend/src/scripts/theme-editor.ts b/packages/frontend/src/utility/theme-editor.ts
index 0092af1640..ea07e5f2ff 100644
--- a/packages/frontend/src/scripts/theme-editor.ts
+++ b/packages/frontend/src/utility/theme-editor.ts
@@ -5,7 +5,8 @@
import { v4 as uuid } from 'uuid';
-import { themeProps, Theme } from './theme.js';
+import type { Theme } from '@/theme.js';
+import { themeProps } from '@/theme.js';
export type Default = null;
export type Color = string;
diff --git a/packages/frontend/src/scripts/time.ts b/packages/frontend/src/utility/time.ts
index 275b67ed00..275b67ed00 100644
--- a/packages/frontend/src/scripts/time.ts
+++ b/packages/frontend/src/utility/time.ts
diff --git a/packages/frontend/src/scripts/timezones.ts b/packages/frontend/src/utility/timezones.ts
index c7582e06da..c7582e06da 100644
--- a/packages/frontend/src/scripts/timezones.ts
+++ b/packages/frontend/src/utility/timezones.ts
diff --git a/packages/frontend/src/scripts/touch.ts b/packages/frontend/src/utility/touch.ts
index 13c9d648dc..adc2e4c093 100644
--- a/packages/frontend/src/scripts/touch.ts
+++ b/packages/frontend/src/utility/touch.ts
@@ -4,7 +4,7 @@
*/
import { ref } from 'vue';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { deviceKind } from '@/utility/device-kind.js';
const isTouchSupported = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0;
diff --git a/packages/frontend/src/scripts/unison-reload.ts b/packages/frontend/src/utility/unison-reload.ts
index a24941d02e..c4804192f8 100644
--- a/packages/frontend/src/scripts/unison-reload.ts
+++ b/packages/frontend/src/utility/unison-reload.ts
@@ -12,9 +12,9 @@ export const reloadChannel = new BroadcastChannel<string | null>('reload');
export function unisonReload(path?: string) {
if (path !== undefined) {
reloadChannel.postMessage(path);
- location.href = path;
+ window.location.href = path;
} else {
reloadChannel.postMessage(null);
- location.reload();
+ window.location.reload();
}
}
diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/utility/upload.ts
index 713573a377..e13d793ffb 100644
--- a/packages/frontend/src/scripts/upload.ts
+++ b/packages/frontend/src/utility/upload.ts
@@ -7,13 +7,13 @@ import { reactive, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { v4 as uuid } from 'uuid';
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
-import { getCompressionConfig } from './upload/compress-config.js';
-import { defaultStore } from '@/store.js';
import { apiUrl } from '@@/js/config.js';
-import { $i } from '@/account.js';
+import { getCompressionConfig } from './upload/compress-config.js';
+import { $i } from '@/i.js';
import { alert } from '@/os.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
+import { prefer } from '@/preferences.js';
type Uploading = {
id: string;
@@ -32,9 +32,9 @@ const mimeTypeMap = {
export function uploadFile(
file: File,
- folder?: string | Misskey.entities.DriveFolder,
+ folder?: string | Misskey.entities.DriveFolder | null,
name?: string,
- keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
+ keepOriginal: boolean = prefer.s.keepOriginalUploading,
): Promise<Misskey.entities.DriveFile> {
if ($i == null) throw new Error('Not logged in');
@@ -59,7 +59,7 @@ export function uploadFile(
const ctx = reactive<Uploading>({
id,
- name: defaultStore.state.keepOriginalFilename ? filename : id + extension,
+ name: prefer.s.keepOriginalFilename ? filename : id + extension,
progressMax: undefined,
progressValue: undefined,
img: window.URL.createObjectURL(file),
diff --git a/packages/frontend/src/scripts/upload/compress-config.ts b/packages/frontend/src/utility/upload/compress-config.ts
index 3046b7f518..3046b7f518 100644
--- a/packages/frontend/src/scripts/upload/compress-config.ts
+++ b/packages/frontend/src/utility/upload/compress-config.ts
diff --git a/packages/frontend/src/scripts/upload/isWebpSupported.ts b/packages/frontend/src/utility/upload/isWebpSupported.ts
index 2511236ecc..affd81fd57 100644
--- a/packages/frontend/src/scripts/upload/isWebpSupported.ts
+++ b/packages/frontend/src/utility/upload/isWebpSupported.ts
@@ -6,7 +6,7 @@
let isWebpSupportedCache: boolean | undefined;
export function isWebpSupported() {
if (isWebpSupportedCache === undefined) {
- const canvas = document.createElement('canvas');
+ const canvas = window.document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
isWebpSupportedCache = canvas.toDataURL('image/webp').startsWith('data:image/webp');
diff --git a/packages/frontend/src/scripts/warning-external-website.ts b/packages/frontend/src/utility/warning-external-website.ts
index 0c9b5ba806..0c9b5ba806 100644
--- a/packages/frontend/src/scripts/warning-external-website.ts
+++ b/packages/frontend/src/utility/warning-external-website.ts
diff --git a/packages/frontend/src/widgets/WidgetActivity.vue b/packages/frontend/src/widgets/WidgetActivity.vue
index 0aaf18ddd1..db03d1406c 100644
--- a/packages/frontend/src/widgets/WidgetActivity.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.vue
@@ -21,13 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
import XCalendar from './WidgetActivity.calendar.vue';
import XChart from './WidgetActivity.chart.vue';
-import { GetFormResultType } from '@/scripts/form.js';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import type { GetFormResultType } from '@/utility/form.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
const name = 'activity';
diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue
index 00001005de..2bc7facc88 100644
--- a/packages/frontend/src/widgets/WidgetAichan.vue
+++ b/packages/frontend/src/widgets/WidgetAichan.vue
@@ -10,9 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, shallowRef } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { onMounted, onUnmounted, useTemplateRef } from 'vue';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
const name = 'ai';
@@ -34,7 +35,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
emit,
);
-const live2d = shallowRef<HTMLIFrameElement>();
+const live2d = useTemplateRef('live2d');
const touched = () => {
//if (this.live2d) this.live2d.changeExpression('gurugurume');
diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue
index 946124058c..698b4049c0 100644
--- a/packages/frontend/src/widgets/WidgetAiscript.vue
+++ b/packages/frontend/src/widgets/WidgetAiscript.vue
@@ -21,12 +21,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import * as os from '@/os.js';
import MkContainer from '@/components/MkContainer.vue';
-import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
-import { $i } from '@/account.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
+import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
const name = 'aiscript';
diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
index fa79e4aeb7..429b0e0ffb 100644
--- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue
+++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
@@ -13,16 +13,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, Ref, ref, watch } from 'vue';
+import { onMounted, ref, watch } from 'vue';
+import type { Ref } from 'vue';
import { Interpreter, Parser } from '@syuilo/aiscript';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import * as os from '@/os.js';
-import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
-import { $i } from '@/account.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
+import { $i } from '@/i.js';
import MkAsUi from '@/components/MkAsUi.vue';
import MkContainer from '@/components/MkContainer.vue';
-import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui.js';
+import { registerAsUiLib } from '@/aiscript/ui.js';
+import type { AsUiComponent, AsUiRoot } from '@/aiscript/ui.js';
const name = 'aiscriptApp';
diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
index c2bda85ac7..6fe743aed2 100644
--- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
+++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkAvatar v-for="user in users" :key="user.id" :user="user.followee" link preview></MkAvatar>
</div>
<div v-else :class="$style.bdayFFallback">
- <img :src="infoImageUrl" class="_ghost" :class="$style.bdayFFallbackImage"/>
+ <img :src="infoImageUrl" draggable="false" :class="$style.bdayFFallbackImage"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</div>
@@ -26,13 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i.js';
const name = i18n.ts._widgets.birthdayFollowings;
diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue
index 6080e120ec..4afe735a22 100644
--- a/packages/frontend/src/widgets/WidgetButton.vue
+++ b/packages/frontend/src/widgets/WidgetButton.vue
@@ -13,11 +13,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { Interpreter, Parser } from '@syuilo/aiscript';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import * as os from '@/os.js';
-import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
-import { $i } from '@/account.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
+import { $i } from '@/i.js';
import MkButton from '@/components/MkButton.vue';
const name = 'button';
diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue
index a3fe93f025..bc6a61399a 100644
--- a/packages/frontend/src/widgets/WidgetCalendar.vue
+++ b/packages/frontend/src/widgets/WidgetCalendar.vue
@@ -39,8 +39,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import { i18n } from '@/i18n.js';
import { useInterval } from '@@/js/use-interval.js';
@@ -207,7 +208,7 @@ defineExpose<WidgetComponentExpose>({
.meter {
width: 100%;
overflow: hidden;
- background: var(--MI_THEME-X11);
+ background: light-dark(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.3));
border-radius: var(--MI-radius-sm);
}
diff --git a/packages/frontend/src/widgets/WidgetClicker.vue b/packages/frontend/src/widgets/WidgetClicker.vue
index 5c978fdf72..87ffd3d732 100644
--- a/packages/frontend/src/widgets/WidgetClicker.vue
+++ b/packages/frontend/src/widgets/WidgetClicker.vue
@@ -12,8 +12,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkClickerGame from '@/components/MkClickerGame.vue';
diff --git a/packages/frontend/src/widgets/WidgetClock.vue b/packages/frontend/src/widgets/WidgetClock.vue
index b3128ef27e..826ecf6e02 100644
--- a/packages/frontend/src/widgets/WidgetClock.vue
+++ b/packages/frontend/src/widgets/WidgetClock.vue
@@ -30,12 +30,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkAnalogClock from '@/components/MkAnalogClock.vue';
import MkDigitalClock from '@/components/MkDigitalClock.vue';
-import { timezones } from '@/scripts/timezones.js';
+import { timezones } from '@/utility/timezones.js';
import { i18n } from '@/i18n.js';
const name = 'clock';
diff --git a/packages/frontend/src/widgets/WidgetDigitalClock.vue b/packages/frontend/src/widgets/WidgetDigitalClock.vue
index fa9a98d571..d79ec79d4f 100644
--- a/packages/frontend/src/widgets/WidgetDigitalClock.vue
+++ b/packages/frontend/src/widgets/WidgetDigitalClock.vue
@@ -15,9 +15,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
-import { timezones } from '@/scripts/timezones.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
+import { timezones } from '@/utility/timezones.js';
import MkDigitalClock from '@/components/MkDigitalClock.vue';
const name = 'digitalClock';
diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue
index 260dd06c13..2843aa2bae 100644
--- a/packages/frontend/src/widgets/WidgetFederation.vue
+++ b/packages/frontend/src/widgets/WidgetFederation.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="wbrkwalb">
<MkLoading v-if="fetching"/>
- <TransitionGroup v-else tag="div" :name="defaultStore.state.animation ? 'chart' : ''" class="instances">
+ <TransitionGroup v-else tag="div" :name="prefer.s.animation ? 'chart' : ''" class="instances">
<div v-for="(instance, i) in instances" :key="instance.id" class="instance">
<img :src="getInstanceIcon(instance)" alt=""/>
<div class="body">
@@ -27,15 +27,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useInterval } from '@@/js/use-interval.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkMiniChart from '@/components/MkMiniChart.vue';
-import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
-import { useInterval } from '@@/js/use-interval.js';
+import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
-import { defaultStore } from '@/store.js';
+import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
+import { prefer } from '@/preferences.js';
const name = 'federation';
diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
index d090372b9a..0c9f98f9e3 100644
--- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkContainer :naked="widgetProps.transparent" :showHeader="false" class="mkw-instance-cloud">
<div class="">
- <MkTagCloud v-if="activeInstances">
+ <MkTagCloud v-if="activeInstances" ref="cloud">
<li v-for="instance in activeInstances" :key="instance.id">
<a @click.prevent="onInstanceClick(instance)">
<img style="width: 32px;" :src="getInstanceIcon(instance)">
@@ -18,16 +18,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { shallowRef, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useInterval } from '@@/js/use-interval.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkTagCloud from '@/components/MkTagCloud.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useInterval } from '@@/js/use-interval.js';
-import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
+import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
const name = 'instanceCloud';
@@ -49,7 +50,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
emit,
);
-const cloud = shallowRef<InstanceType<typeof MkTagCloud> | null>();
+const cloud = useTemplateRef('cloud');
const activeInstances = shallowRef<Misskey.entities.FederationInstance[] | null>(null);
function onInstanceClick(i) {
diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
index b99f9bdb2b..9708b63a8d 100644
--- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
@@ -20,8 +20,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import { host } from '@@/js/config.js';
import { instance } from '@/instance.js';
diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue
index 0ee6b863dc..485e532d51 100644
--- a/packages/frontend/src/widgets/WidgetJobQueue.vue
+++ b/packages/frontend/src/widgets/WidgetJobQueue.vue
@@ -52,13 +52,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onUnmounted, reactive, ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import { useStream } from '@/stream.js';
import kmg from '@/filters/kmg.js';
-import * as sound from '@/scripts/sound.js';
-import { deepClone } from '@/scripts/clone.js';
-import { defaultStore } from '@/store.js';
+import * as sound from '@/utility/sound.js';
+import { deepClone } from '@/utility/clone.js';
+import { prefer } from '@/preferences.js';
const name = 'jobQueue';
@@ -103,7 +104,7 @@ const prev = reactive({} as typeof current);
const jammedAudioBuffer = ref<AudioBuffer | null>(null);
const jammedSoundNodePlaying = ref<boolean>(false);
-if (defaultStore.state.sound_masterVolume) {
+if (prefer.s['sound.masterVolume']) {
sound.loadAudio('/client-assets/sounds/syuilo/queue-jammed.mp3').then(buf => {
if (!buf) throw new Error('[WidgetJobQueue] Failed to initialize AudioBuffer');
jammedAudioBuffer.value = buf;
diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue
index 5addf1066d..e6e18fd974 100644
--- a/packages/frontend/src/widgets/WidgetMemo.vue
+++ b/packages/frontend/src/widgets/WidgetMemo.vue
@@ -17,10 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
const name = 'memo';
@@ -47,12 +48,12 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
emit,
);
-const text = ref<string | null>(defaultStore.state.memo);
+const text = ref<string | null>(store.s.memo);
const changed = ref(false);
let timeoutId;
const saveMemo = () => {
- defaultStore.set('memo', text.value);
+ store.set('memo', text.value);
changed.value = false;
};
@@ -62,7 +63,7 @@ const onChange = () => {
timeoutId = window.setTimeout(saveMemo, 1000);
};
-watch(() => defaultStore.reactiveState.memo, newText => {
+watch(() => store.r.memo, newText => {
text.value = newText.value;
});
diff --git a/packages/frontend/src/widgets/WidgetNotifications.vue b/packages/frontend/src/widgets/WidgetNotifications.vue
index 773c078b49..c5e1324ef5 100644
--- a/packages/frontend/src/widgets/WidgetNotifications.vue
+++ b/packages/frontend/src/widgets/WidgetNotifications.vue
@@ -17,8 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
import XNotifications from '@/components/MkNotifications.vue';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
index d8c4e259c8..f6bd4c0025 100644
--- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue
+++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
@@ -15,9 +15,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
-import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
+import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
import { i18n } from '@/i18n.js';
import number from '@/filters/number.js';
diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue
index 60a4770e40..5483d2f7cc 100644
--- a/packages/frontend/src/widgets/WidgetPhotos.vue
+++ b/packages/frontend/src/widgets/WidgetPhotos.vue
@@ -24,13 +24,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onUnmounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import { useStream } from '@/stream.js';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { getStaticImageUrl } from '@/utility/media-proxy.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
import { i18n } from '@/i18n.js';
const name = 'photos';
@@ -69,7 +70,7 @@ const onDriveFileCreated = (file) => {
};
const thumbnail = (image: Misskey.entities.DriveFile): string => {
- return defaultStore.state.disableShowingAnimatedImages
+ return prefer.s.disableShowingAnimatedImages
? getStaticImageUrl(image.url)
: image.thumbnailUrl ?? image.url;
};
diff --git a/packages/frontend/src/widgets/WidgetPostForm.vue b/packages/frontend/src/widgets/WidgetPostForm.vue
index 7f344505d8..3170eab305 100644
--- a/packages/frontend/src/widgets/WidgetPostForm.vue
+++ b/packages/frontend/src/widgets/WidgetPostForm.vue
@@ -9,8 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkPostForm from '@/components/MkPostForm.vue';
const name = 'postForm';
diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue
index ae39098305..3fe8378a39 100644
--- a/packages/frontend/src/widgets/WidgetProfile.vue
+++ b/packages/frontend/src/widgets/WidgetProfile.vue
@@ -22,9 +22,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
-import { $i } from '@/account.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
+import { $i } from '@/i.js';
import { userPage } from '@/filters/user.js';
const name = 'profile';
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index 3e43687709..132eb0a629 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="ekmkgxbj">
<MkLoading v-if="fetching"/>
<div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo">
- <img :src="infoImageUrl" class="_ghost"/>
+ <img :src="infoImageUrl" draggable="false"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
<div v-else :class="$style.feed">
@@ -25,12 +25,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch, computed } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
-import MkContainer from '@/components/MkContainer.vue';
import { url as base } from '@@/js/config.js';
-import { i18n } from '@/i18n.js';
import { useInterval } from '@@/js/use-interval.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
+import MkContainer from '@/components/MkContainer.vue';
+import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
const name = 'rss';
@@ -76,7 +77,7 @@ const fetchEndpoint = computed(() => {
const intervalClear = ref<(() => void) | undefined>();
const tick = () => {
- if (document.visibilityState === 'hidden' && rawItems.value.length !== 0) return;
+ if (window.document.visibilityState === 'hidden' && rawItems.value.length !== 0) return;
window.fetch(fetchEndpoint.value, {})
.then(res => res.json())
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index 4f594b720f..b5be4d35c2 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -29,11 +29,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch, computed } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
import MarqueeText from '@/components/MkMarquee.vue';
-import { GetFormResultType } from '@/scripts/form.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
-import { shuffle } from '@/scripts/shuffle.js';
+import { shuffle } from '@/utility/shuffle.js';
import { url as base } from '@@/js/config.js';
import { useInterval } from '@@/js/use-interval.js';
@@ -107,7 +108,7 @@ const intervalClear = ref<(() => void) | undefined>();
const key = ref(0);
const tick = () => {
- if (document.visibilityState === 'hidden' && rawItems.value.length !== 0) return;
+ if (window.document.visibilityState === 'hidden' && rawItems.value.length !== 0) return;
window.fetch(fetchEndpoint.value, {})
.then(res => res.json())
diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue
index 3fea1d7053..2ccbb7a28f 100644
--- a/packages/frontend/src/widgets/WidgetSlideshow.vue
+++ b/packages/frontend/src/widgets/WidgetSlideshow.vue
@@ -17,13 +17,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, shallowRef } from 'vue';
+import { onMounted, ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
+import * as os from '@/os.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
const name = 'slideshow';
@@ -53,8 +54,8 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
const images = ref<Misskey.entities.DriveFile[]>([]);
const fetching = ref(true);
-const slideA = shallowRef<HTMLElement>();
-const slideB = shallowRef<HTMLElement>();
+const slideA = useTemplateRef('slideA');
+const slideB = useTemplateRef('slideB');
const change = () => {
if (images.value.length === 0) return;
diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue
index a4685fd1fc..47dec05303 100644
--- a/packages/frontend/src/widgets/WidgetTimeline.vue
+++ b/packages/frontend/src/widgets/WidgetTimeline.vue
@@ -32,10 +32,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
import MkTimeline from '@/components/MkTimeline.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue
index 47a4efc106..db09031c33 100644
--- a/packages/frontend/src/widgets/WidgetTrends.vue
+++ b/packages/frontend/src/widgets/WidgetTrends.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="wbrkwala">
<MkLoading v-if="fetching"/>
- <TransitionGroup v-else tag="div" :name="defaultStore.state.animation ? 'chart' : ''" class="tags">
+ <TransitionGroup v-else tag="div" :name="prefer.s.animation ? 'chart' : ''" class="tags">
<div v-for="stat in stats" :key="stat.tag">
<div class="tag">
<MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA>
@@ -26,14 +26,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useInterval } from '@@/js/use-interval.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkMiniChart from '@/components/MkMiniChart.vue';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
-import { useInterval } from '@@/js/use-interval.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { defaultStore } from '@/store.js';
+import { prefer } from '@/preferences.js';
const name = 'hashtags';
diff --git a/packages/frontend/src/widgets/WidgetUnixClock.vue b/packages/frontend/src/widgets/WidgetUnixClock.vue
index 832cd575cc..f51ef12a2a 100644
--- a/packages/frontend/src/widgets/WidgetUnixClock.vue
+++ b/packages/frontend/src/widgets/WidgetUnixClock.vue
@@ -17,8 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onUnmounted, ref, watch } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
const name = 'unixClock';
diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue
index 72391d622e..eb86732817 100644
--- a/packages/frontend/src/widgets/WidgetUserList.vue
+++ b/packages/frontend/src/widgets/WidgetUserList.vue
@@ -26,11 +26,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/utility/form.js';
import MkContainer from '@/components/MkContainer.vue';
import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
+import { misskeyApi } from '@/utility/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts
index 37742f8c2e..047c6d7104 100644
--- a/packages/frontend/src/widgets/index.ts
+++ b/packages/frontend/src/widgets/index.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { App, defineAsyncComponent } from 'vue';
+import { defineAsyncComponent } from 'vue';
+import type { App } from 'vue';
export default function(app: App) {
app.component('WidgetProfile', defineAsyncComponent(() => import('./WidgetProfile.vue')));
diff --git a/packages/frontend/src/widgets/server-metric/index.vue b/packages/frontend/src/widgets/server-metric/index.vue
index 86d84b4f33..9026fefb20 100644
--- a/packages/frontend/src/widgets/server-metric/index.vue
+++ b/packages/frontend/src/widgets/server-metric/index.vue
@@ -22,15 +22,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onUnmounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget.js';
+import { useWidgetPropsManager } from '../widget.js';
+import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from '../widget.js';
import XCpuMemory from './cpu-mem.vue';
import XNet from './net.vue';
import XCpu from './cpu.vue';
import XMemory from './mem.vue';
import XDisk from './disk.vue';
import MkContainer from '@/components/MkContainer.vue';
-import { GetFormResultType } from '@/scripts/form.js';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import type { GetFormResultType } from '@/utility/form.js';
+import { misskeyApiGet } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/widgets/widget.ts b/packages/frontend/src/widgets/widget.ts
index bfe8067adf..de4c369cbb 100644
--- a/packages/frontend/src/widgets/widget.ts
+++ b/packages/frontend/src/widgets/widget.ts
@@ -5,9 +5,9 @@
import { reactive, watch } from 'vue';
import { throttle } from 'throttle-debounce';
-import { Form, GetFormResultType } from '@/scripts/form.js';
+import type { Form, GetFormResultType } from '@/utility/form.js';
import * as os from '@/os.js';
-import { deepClone } from '@/scripts/clone.js';
+import { deepClone } from '@/utility/clone.js';
export type Widget<P extends Record<string, unknown>> = {
id: string;
diff --git a/packages/frontend/test/aiscript/api.test.ts b/packages/frontend/test/aiscript/api.test.ts
index 2a15a74249..ad24625b96 100644
--- a/packages/frontend/test/aiscript/api.test.ts
+++ b/packages/frontend/test/aiscript/api.test.ts
@@ -4,7 +4,7 @@
*/
import { miLocalStorage } from '@/local-storage.js';
-import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
import { errors, Interpreter, Parser, values } from '@syuilo/aiscript';
import {
afterAll,
@@ -33,11 +33,11 @@ async function exe(script: string): Promise<values.Value[]> {
return outputs;
}
-let $iMock = vi.hoisted<Partial<typeof import('@/account.js').$i> | null >(
+let $iMock = vi.hoisted<Partial<typeof import('@/i.js').$i> | null >(
() => null
);
-vi.mock('@/account.js', () => {
+vi.mock('@/i.js', () => {
return {
get $i() {
return $iMock;
@@ -59,7 +59,7 @@ vi.mock('@/os.js', () => {
const misskeyApiMock = vi.hoisted(() => vi.fn());
-vi.mock('@/scripts/misskey-api.js', () => {
+vi.mock('@/utility/misskey-api.js', () => {
return { misskeyApi: misskeyApiMock };
});
diff --git a/packages/frontend/test/aiscript/common.test.ts b/packages/frontend/test/aiscript/common.test.ts
index acc48826ea..c0c978001b 100644
--- a/packages/frontend/test/aiscript/common.test.ts
+++ b/packages/frontend/test/aiscript/common.test.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { assertStringAndIsIn } from "@/scripts/aiscript/common.js";
+import { assertStringAndIsIn } from "@/aiscript/common.js";
import { values } from "@syuilo/aiscript";
import { describe, expect, test } from "vitest";
diff --git a/packages/frontend/test/aiscript/ui.test.ts b/packages/frontend/test/aiscript/ui.test.ts
index d3f930173f..4cd07724bb 100644
--- a/packages/frontend/test/aiscript/ui.test.ts
+++ b/packages/frontend/test/aiscript/ui.test.ts
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import { registerAsUiLib } from '@/aiscript/ui.js';
import { errors, Interpreter, Parser, values } from '@syuilo/aiscript';
import { describe, expect, test } from 'vitest';
import { type Ref, ref } from 'vue';
@@ -18,8 +19,7 @@ import type {
AsUiText,
AsUiTextarea,
AsUiTextInput,
-} from '@/scripts/aiscript/ui.js';
-import { registerAsUiLib } from '@/scripts/aiscript/ui.js';
+} from '@/aiscript/ui.js';
type ExeResult = {
root: AsUiRoot;
diff --git a/packages/frontend/test/autocomplete.test.ts b/packages/frontend/test/autocomplete.test.ts
index 394ac3a821..38be35813f 100644
--- a/packages/frontend/test/autocomplete.test.ts
+++ b/packages/frontend/test/autocomplete.test.ts
@@ -4,7 +4,7 @@
*/
import { assert, describe, test } from 'vitest';
-import { searchEmoji } from '@/scripts/search-emoji.js';
+import { searchEmoji } from '@/utility/search-emoji.js';
describe('emoji autocomplete', () => {
test('名前の完全一致は名前の前方一致より優先される', async () => {
diff --git a/packages/frontend/test/emoji.test.ts b/packages/frontend/test/emoji.test.ts
index cf686efd0d..ffdc858b75 100644
--- a/packages/frontend/test/emoji.test.ts
+++ b/packages/frontend/test/emoji.test.ts
@@ -5,7 +5,7 @@
import { describe, test, assert, afterEach } from 'vitest';
import { render, cleanup, type RenderResult } from '@testing-library/vue';
-import { defaultStoreState } from './init.js';
+import { preferState } from './init.js';
import { getEmojiName } from '@@/js/emojilist.js';
import { components } from '@/components/index.js';
import { directives } from '@/directives/index.js';
@@ -21,12 +21,12 @@ describe('Emoji', () => {
afterEach(() => {
cleanup();
- defaultStoreState.emojiStyle = '';
+ preferState.emojiStyle = '';
});
describe('MkEmoji', () => {
test('Should render selector-less heart with color in native mode', async () => {
- defaultStoreState.emojiStyle = 'native';
+ preferState.emojiStyle = 'native';
const mkEmoji = await renderEmoji('\u2764'); // monochrome heart
assert.ok(mkEmoji.queryByText('\u2764\uFE0F')); // colored heart
assert.ok(!mkEmoji.queryByText('\u2764'));
diff --git a/packages/frontend/test/home.test.ts b/packages/frontend/test/home.test.ts
index b3a4e8ff3a..a67ea40176 100644
--- a/packages/frontend/test/home.test.ts
+++ b/packages/frontend/test/home.test.ts
@@ -6,7 +6,7 @@
import { afterEach, assert, describe, test } from 'vitest';
import { cleanup, render, type RenderResult } from '@testing-library/vue';
import './init';
-import type * as Misskey from 'misskey-js';
+import * as Misskey from 'misskey-js';
import { directives } from '@/directives/index.js';
import { components } from '@/components/index.js';
import XHome from '@/pages/user/home.vue';
@@ -42,8 +42,6 @@ describe('XHome', () => {
const anchor = home.container.querySelector<HTMLAnchorElement>('a[href^="https://example.com/"]');
assert.exists(anchor, 'anchor to the remote exists');
assert.strictEqual(anchor?.href, 'https://example.com/@user/profile');
-
- assert.ok(anchor?.parentElement?.classList.contains('warn'), 'the parent is a warning');
});
test('The remote caution should fall back to uri if url is null', async () => {
diff --git a/packages/frontend/test/i18n.test.ts b/packages/frontend/test/i18n.test.ts
index 149815d39c..afe4128fec 100644
--- a/packages/frontend/test/i18n.test.ts
+++ b/packages/frontend/test/i18n.test.ts
@@ -5,7 +5,7 @@
import { describe, expect, it } from 'vitest';
import { I18n } from '../../frontend-shared/js/i18n.js'; // @@で参照できなかったので
-import { ParameterizedString } from '../../../locales/index.js';
+import type { ParameterizedString } from '../../../locales/index.js';
/* eslint "sharkey/locale":"off" */
diff --git a/packages/frontend/test/init.ts b/packages/frontend/test/init.ts
index 0cde571dcb..3b6b4d581b 100644
--- a/packages/frontend/test/init.ts
+++ b/packages/frontend/test/init.ts
@@ -17,7 +17,7 @@ updateI18n(locales['en-US']);
// XXX: misskey-js panics if WebSocket is not defined
vi.stubGlobal('WebSocket', class WebSocket extends EventTarget { static CLOSING = 2; });
-export const defaultStoreState: Record<string, unknown> = {
+export const preferState: Record<string, unknown> = {
// なんかtestがうまいこと動かないのでここに書く
dataSaver: {
@@ -29,11 +29,11 @@ export const defaultStoreState: Record<string, unknown> = {
};
-// XXX: defaultStore somehow becomes undefined in vitest?
-vi.mock('@/store.js', () => {
+// XXX: store somehow becomes undefined in vitest?
+vi.mock('@/preferences.js', () => {
return {
- defaultStore: {
- state: defaultStoreState,
+ prefer: {
+ s: preferState,
},
};
});
diff --git a/packages/frontend/test/intl-string.test.ts b/packages/frontend/test/intl-string.test.ts
new file mode 100644
index 0000000000..b52824db86
--- /dev/null
+++ b/packages/frontend/test/intl-string.test.ts
@@ -0,0 +1,142 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { assert, beforeEach, describe, test } from 'vitest';
+import {
+ normalizeString,
+ initIntlString,
+ normalizeStringWithHiragana,
+ compareStringEquals,
+ compareStringIncludes,
+} from '@/utility/intl-string.js';
+
+// 共通のテストを実行するヘルパー関数
+const runCommonTests = (normalizeFn: (str: string) => string) => {
+ test('全角英数字が半角の小文字になる', () => {
+ // ローマ字にならないようにする
+ const input = 'B123';
+ const expected = 'b123';
+ assert.strictEqual(normalizeFn(input), expected);
+ });
+ test('濁点・半濁点が正しく結合される', () => {
+ const input = 'か\u3099';
+ const expected = 'が';
+ assert.strictEqual(normalizeFn(input), expected);
+ });
+ test('小文字に揃う', () => {
+ // ローマ字にならないようにする
+ const input = 'tSt';
+ const expected = 'tst';
+ assert.strictEqual(normalizeFn(input), expected);
+ });
+ test('文字列の前後の空白が削除される', () => {
+ const input = ' tst ';
+ const expected = 'tst';
+ assert.strictEqual(normalizeFn(input), expected);
+ });
+};
+
+describe('normalize string', () => {
+ runCommonTests(normalizeString);
+
+ test('異体字の正規化 (ligature)', () => {
+ const input = 'fi';
+ const expected = 'fi';
+ assert.strictEqual(normalizeString(input), expected);
+ });
+
+ test('半角カタカナは全角に変換される', () => {
+ const input = 'カタカナ';
+ const expected = 'カタカナ';
+ assert.strictEqual(normalizeString(input), expected);
+ });
+});
+
+// normalizeStringWithHiraganaのテスト
+describe('normalize string with hiragana', () => {
+ beforeEach(async () => {
+ await initIntlString(true);
+ });
+
+ // 共通テスト
+ describe('共通のnormalizeStringテスト', () => {
+ runCommonTests(normalizeStringWithHiragana);
+ });
+
+ test('半角カタカナがひらがなに変換される', () => {
+ const input = 'カタカナ';
+ const expected = 'かたかな';
+ assert.strictEqual(normalizeStringWithHiragana(input), expected);
+ });
+
+ // normalizeStringWithHiragana特有のテスト
+ test('カタカナがひらがなに変換される・伸ばし棒はハイフンに変換される', () => {
+ const input = 'カタカナひーらがーな';
+ const expected = 'かたかなひ-らが-な';
+ assert.strictEqual(normalizeStringWithHiragana(input), expected);
+ });
+
+ test('ローマ字がひらがなに変換される', () => {
+ const input = 'ro-majimohiragananinarimasu';
+ const expected = 'ろ-まじもひらがなになります';
+ assert.strictEqual(normalizeStringWithHiragana(input), expected);
+ });
+});
+
+describe('compareStringEquals', () => {
+ beforeEach(async () => {
+ await initIntlString(true);
+ });
+
+ test('完全一致ならtrue', () => {
+ assert.isTrue(compareStringEquals('テスト', 'テスト'));
+ });
+
+ test('大文字・小文字の違いを無視', () => {
+ assert.isTrue(compareStringEquals('TeSt', 'test'));
+ });
+
+ test('全角・半角の違いを無視', () => {
+ assert.isTrue(compareStringEquals('ABC', 'abc'));
+ });
+
+ test('カタカナとひらがなの違いを無視', () => {
+ assert.isTrue(compareStringEquals('カタカナ', 'かたかな'));
+ });
+
+ test('ローマ字をひらがなと比較可能', () => {
+ assert.isTrue(compareStringEquals('hiragana', 'ひらがな'));
+ });
+
+ test('異なる文字列はfalse', () => {
+ assert.isFalse(compareStringEquals('テスト', 'サンプル'));
+ });
+});
+
+describe('compareStringIncludes', () => {
+ test('部分一致ならtrue', () => {
+ assert.isTrue(compareStringIncludes('これはテストです', 'テスト'));
+ });
+
+ test('大文字・小文字の違いを無視', () => {
+ assert.isTrue(compareStringIncludes('This is a Test', 'test'));
+ });
+
+ test('全角・半角の違いを無視', () => {
+ assert.isTrue(compareStringIncludes('ABCDE', 'abc'));
+ });
+
+ test('カタカナとひらがなの違いを無視', () => {
+ assert.isTrue(compareStringIncludes('カタカナのテスト', 'かたかな'));
+ });
+
+ test('ローマ字をひらがなと比較可能', () => {
+ assert.isTrue(compareStringIncludes('これはhiraganaのテスト', 'ひらがな'));
+ });
+
+ test('異なる文字列はfalse', () => {
+ assert.isFalse(compareStringIncludes('これはテストです', 'サンプル'));
+ });
+});
diff --git a/packages/frontend/test/note.test.ts b/packages/frontend/test/note.test.ts
index 7ce5f23e22..fda2d9ad7b 100644
--- a/packages/frontend/test/note.test.ts
+++ b/packages/frontend/test/note.test.ts
@@ -6,7 +6,7 @@
import { describe, test, assert, afterEach } from 'vitest';
import { render, cleanup, type RenderResult } from '@testing-library/vue';
import './init';
-import type * as Misskey from 'misskey-js';
+import * as Misskey from 'misskey-js';
import { components } from '@/components/index.js';
import { directives } from '@/directives/index.js';
import MkMediaImage from '@/components/MkMediaImage.vue';
diff --git a/packages/frontend/test/scroll.test.ts b/packages/frontend/test/scroll.test.ts
index 32a5a1c558..34e7e64313 100644
--- a/packages/frontend/test/scroll.test.ts
+++ b/packages/frontend/test/scroll.test.ts
@@ -12,10 +12,10 @@ describe('Scroll', () => {
/* 動作しない(happy-domのバグ?)
test('Initial onScrollTop callback for connected elements', () => {
const { document } = new Window();
- const div = document.createElement('div');
+ const div = window.document.createElement('div');
assert.strictEqual(div.scrollTop, 0);
- document.body.append(div);
+ window.document.body.append(div);
let called = false;
onScrollTop(div as any as HTMLElement, () => called = true);
@@ -26,7 +26,7 @@ describe('Scroll', () => {
test('No onScrollTop callback for disconnected elements', () => {
const { document } = new Window();
- const div = document.createElement('div');
+ const div = window.document.createElement('div');
assert.strictEqual(div.scrollTop, 0);
let called = false;
@@ -40,10 +40,10 @@ describe('Scroll', () => {
/* 動作しない(happy-domのバグ?)
test('Initial onScrollBottom callback for connected elements', () => {
const { document } = new Window();
- const div = document.createElement('div');
+ const div = window.document.createElement('div');
assert.strictEqual(div.scrollTop, 0);
- document.body.append(div);
+ window.document.body.append(div);
let called = false;
onScrollBottom(div as any as HTMLElement, () => called = true);
@@ -54,7 +54,7 @@ describe('Scroll', () => {
test('No onScrollBottom callback for disconnected elements', () => {
const { document } = new Window();
- const div = document.createElement('div');
+ const div = window.document.createElement('div');
assert.strictEqual(div.scrollTop, 0);
let called = false;
diff --git a/packages/frontend/test/url-preview.test.ts b/packages/frontend/test/url-preview.test.ts
index 4b79d33348..943cf2ac7a 100644
--- a/packages/frontend/test/url-preview.test.ts
+++ b/packages/frontend/test/url-preview.test.ts
@@ -24,7 +24,10 @@ describe('MkUrlPreview', () => {
};
}
- fetchMock.mockOnceIf(/^\/url?/, () => {
+ fetchMock.mockOnceIf((req) => {
+ const url = new URL(req.url);
+ return url.pathname === '/url';
+ }, () => {
return {
status: 200,
body: JSON.stringify(summary),
@@ -98,7 +101,7 @@ describe('MkUrlPreview', () => {
assert.strictEqual(iframe?.src, 'https://example.local/player?autoplay=1&auto_play=1');
assert.strictEqual(
iframe?.sandbox.toString(),
- 'allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin',
+ 'allow-popups allow-popups-to-escape-sandbox allow-scripts allow-storage-access-by-user-activation allow-same-origin',
);
});
diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json
index 169bd5029c..3c7e5e1da3 100644
--- a/packages/frontend/tsconfig.json
+++ b/packages/frontend/tsconfig.json
@@ -21,6 +21,7 @@
"allowSyntheticDefaultImports": true,
"isolatedModules": true,
"useDefineForClassFields": true,
+ "verbatimModuleSyntax": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
diff --git a/packages/frontend/vite-node.config.ts b/packages/frontend/vite-node.config.ts
new file mode 100644
index 0000000000..c049f46e10
--- /dev/null
+++ b/packages/frontend/vite-node.config.ts
@@ -0,0 +1,3 @@
+import { defineConfig } from 'vite';
+
+export default defineConfig({});
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 5ff07d05c9..9596e2a81e 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -1,19 +1,33 @@
import path from 'path';
import pluginReplace from '@rollup/plugin-replace';
import pluginVue from '@vitejs/plugin-vue';
-import { type UserConfig, defineConfig } from 'vite';
-import { localesVersion } from '../../locales/version.js';
+import { defineConfig } from 'vite';
+import type { UserConfig } from 'vite';
+import * as yaml from 'js-yaml';
+import { promises as fsp } from 'fs';
import locales from '../../locales/index.js';
+import { localesVersion } from '../../locales/version.js';
import meta from '../../package.json';
import packageInfo from './package.json' with { type: 'json' };
import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
import pluginJson5 from './vite.json5.js';
+import pluginCreateSearchIndex from './lib/vite-plugin-create-search-index.js';
+import type { Options as SearchIndexOptions } from './lib/vite-plugin-create-search-index.js';
import { pluginReplaceIcons } from './vite.replaceIcons.js';
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue', '.wasm'];
/**
+ * 検索インデックスの生成設定
+ */
+export const searchIndexes = [{
+ targetFilePaths: ['src/pages/settings/*.vue'],
+ exportFilePath: './src/utility/autogen/settings-search-index.ts',
+ verbose: process.env.FRONTEND_SEARCH_INDEX_VERBOSE === 'true',
+}] satisfies SearchIndexOptions[];
+
+/**
* Misskeyのフロントエンドにバンドルせず、CDNなどから別途読み込むリソースを記述する。
* CDNを使わずにバンドルしたい場合、以下の配列から該当要素を削除orコメントアウトすればOK
*/
@@ -42,7 +56,7 @@ const externalPackages = [
},
];
-const hash = (str: string, seed = 0): number => {
+export const hash = (str: string, seed = 0): number => {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
@@ -57,9 +71,9 @@ const hash = (str: string, seed = 0): number => {
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
-const BASE62_DIGITS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+export const BASE62_DIGITS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
-function toBase62(n: number): string {
+export function toBase62(n: number): string {
if (n === 0) {
return '0';
}
@@ -90,6 +104,7 @@ export function getConfig(): UserConfig {
},
plugins: [
+ ...searchIndexes.map(options => pluginCreateSearchIndex(options)),
pluginVue(),
pluginUnwindCssModuleClassName(),
pluginJson5(),
diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json
index a2484d5f6a..464f22bddb 100644
--- a/packages/misskey-bubble-game/package.json
+++ b/packages/misskey-bubble-game/package.json
@@ -22,23 +22,23 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
- "@types/matter-js": "0.19.7",
+ "@types/matter-js": "0.19.8",
"@types/seedrandom": "3.0.8",
- "@types/node": "22.9.0",
- "@typescript-eslint/eslint-plugin": "7.1.0",
- "@typescript-eslint/parser": "7.1.0",
- "nodemon": "3.1.7",
- "execa": "8.0.1",
- "typescript": "5.6.3",
- "esbuild": "0.24.0",
- "glob": "11.0.0"
+ "@types/node": "22.13.11",
+ "@typescript-eslint/eslint-plugin": "8.27.0",
+ "@typescript-eslint/parser": "8.27.0",
+ "nodemon": "3.1.9",
+ "execa": "9.5.2",
+ "typescript": "5.8.2",
+ "esbuild": "0.25.1",
+ "glob": "11.0.1"
},
"files": [
"built"
],
"dependencies": {
"eventemitter3": "5.0.1",
- "matter-js": "0.19.0",
+ "matter-js": "0.20.0",
"seedrandom": "3.0.5"
}
}
diff --git a/packages/misskey-js/.swcrc b/packages/misskey-js/.swcrc
index 0504a2d389..6fd7486370 100644
--- a/packages/misskey-js/.swcrc
+++ b/packages/misskey-js/.swcrc
@@ -1,5 +1,5 @@
{
- "$schema": "https://json.schemastore.org/swcrc",
+ "$schema": "https://swc.rs/schema.json",
"jsc": {
"parser": {
"syntax": "typescript",
diff --git a/packages/misskey-js/README.md b/packages/misskey-js/README.md
index 4753e2434b..5ab2787c47 100644
--- a/packages/misskey-js/README.md
+++ b/packages/misskey-js/README.md
@@ -83,8 +83,8 @@ const mainChannel = stream.useChannel('main');
``` ts
const stream = new Misskey.Stream('https://misskey.test', { token: 'TOKEN' });
-const messagingChannel = stream.useChannel('messaging', {
- otherparty: 'xxxxxxxxxx',
+const chatChannel = stream.useChannel('chat', {
+ other: 'xxxxxxxxxx',
});
```
@@ -115,11 +115,11 @@ mainChannel.on('notification', notification => {
``` ts
const stream = new Misskey.Stream('https://misskey.test', { token: 'TOKEN' });
-const messagingChannel = stream.useChannel('messaging', {
- otherparty: 'xxxxxxxxxx',
+const chatChannel = stream.useChannel('chat', {
+ other: 'xxxxxxxxxx',
});
-messagingChannel.send('read', {
+chatChannel.send('read', {
id: 'xxxxxxxxxx'
});
```
diff --git a/packages/misskey-js/eslint.config.js b/packages/misskey-js/eslint.config.js
index f33c4c4d25..bbfc5c50c6 100644
--- a/packages/misskey-js/eslint.config.js
+++ b/packages/misskey-js/eslint.config.js
@@ -13,6 +13,11 @@ export default [
'test',
'test-d',
'generator',
+ '**/lib/',
+ '**/temp/',
+ '**/built/',
+ '**/coverage/',
+ '**/node_modules/',
],
},
{
@@ -27,12 +32,9 @@ export default [
},
},
{
- ignores: [
- "**/lib/",
- "**/temp/",
- "**/built/",
- "**/coverage/",
- "**/node_modules/",
- ]
+ files: ['src/autogen/**/*.ts', 'src/autogen/**/*.tsx'],
+ rules: {
+ '@stylistic/indent': 'off',
+ },
},
];
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index e5d4bb6143..e79cd794a6 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -7,7 +7,7 @@
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
import { EventEmitter } from 'eventemitter3';
import { Options } from 'reconnecting-websocket';
-import type { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
+import type { PublicKeyCredentialRequestOptionsJSON as PublicKeyCredentialRequestOptionsJSON_2 } 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
@@ -120,9 +120,6 @@ type AdminAnnouncementsListResponse = operations['admin___announcements___list']
type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
// @public (undocumented)
-type AdminApproveUserRequest = operations['admin___approve-user']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -147,12 +144,6 @@ type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['resp
type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json'];
// @public (undocumented)
-type AdminCwUserRequest = operations['admin___cw-user']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
-type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -264,9 +255,6 @@ type AdminInviteListResponse = operations['admin___invite___list']['responses'][
type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type AdminNsfwUserRequest = operations['admin___nsfw-user']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -282,9 +270,6 @@ type AdminQueuePromoteRequest = operations['admin___queue___promote']['requestBo
type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type AdminRejectQuotesRequest = operations['admin___reject-quotes']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type AdminRelaysAddRequest = operations['admin___relays___add']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -366,9 +351,6 @@ type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['co
type AdminShowUsersResponse = operations['admin___show-users']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type AdminSilenceUserRequest = operations['admin___silence-user']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -402,18 +384,12 @@ type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___upda
type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type AdminUnnsfwUserRequest = operations['admin___unnsfw-user']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json'];
// @public (undocumented)
-type AdminUnsilenceUserRequest = operations['admin___unsilence-user']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -423,6 +399,12 @@ type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-r
type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
// @public (undocumented)
+type AdminUpdateProxyAccountRequest = operations['admin___update-proxy-account']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type AdminUpdateProxyAccountResponse = operations['admin___update-proxy-account']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -637,13 +619,9 @@ export type Channels = {
}) => void;
readAllNotifications: () => void;
unreadNotification: (payload: Notification_2) => void;
- unreadMention: (payload: Note['id']) => void;
- readAllUnreadMentions: () => void;
notificationFlushed: () => void;
- unreadSpecifiedNote: (payload: Note['id']) => void;
- readAllUnreadSpecifiedNotes: () => void;
- readAllAntennas: () => void;
unreadAntenna: (payload: Antenna) => void;
+ newChatMessage: (payload: ChatMessage) => void;
readAllAnnouncements: () => void;
myTokenRegenerated: () => void;
signin: (payload: Signin) => void;
@@ -656,7 +634,6 @@ export type Channels = {
readAntenna: (payload: Antenna) => void;
receiveFollowRequest: (payload: User) => void;
announcementCreated: (payload: AnnouncementCreated) => void;
- edited: (payload: Note) => void;
};
receives: null;
};
@@ -664,7 +641,6 @@ export type Channels = {
params: {
withRenotes?: boolean;
withFiles?: boolean;
- withBots?: boolean;
};
events: {
note: (payload: Note) => void;
@@ -676,7 +652,6 @@ export type Channels = {
withRenotes?: boolean;
withReplies?: boolean;
withFiles?: boolean;
- withBots?: boolean;
};
events: {
note: (payload: Note) => void;
@@ -688,7 +663,6 @@ export type Channels = {
withRenotes?: boolean;
withReplies?: boolean;
withFiles?: boolean;
- withBots?: boolean;
};
events: {
note: (payload: Note) => void;
@@ -699,18 +673,6 @@ export type Channels = {
params: {
withRenotes?: boolean;
withFiles?: boolean;
- withBots?: boolean;
- };
- events: {
- note: (payload: Note) => void;
- };
- receives: null;
- };
- bubbleTimeline: {
- params: {
- withRenotes?: boolean;
- withFiles?: boolean;
- withBots?: boolean;
};
events: {
note: (payload: Note) => void;
@@ -986,6 +948,153 @@ type ChartsUsersRequest = operations['charts___users']['requestBody']['content']
type ChartsUsersResponse = operations['charts___users']['responses']['200']['content']['application/json'];
// @public (undocumented)
+type ChatHistoryRequest = operations['chat___history']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatHistoryResponse = operations['chat___history']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessage = components['schemas']['ChatMessage'];
+
+// @public (undocumented)
+type ChatMessageLite = components['schemas']['ChatMessageLite'];
+
+// @public (undocumented)
+type ChatMessagesCreateToRoomRequest = operations['chat___messages___create-to-room']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesCreateToRoomResponse = operations['chat___messages___create-to-room']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesCreateToUserRequest = operations['chat___messages___create-to-user']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesCreateToUserResponse = operations['chat___messages___create-to-user']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesReactRequest = operations['chat___messages___react']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesReactResponse = operations['chat___messages___react']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesRoomTimelineRequest = operations['chat___messages___room-timeline']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesRoomTimelineResponse = operations['chat___messages___room-timeline']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesSearchRequest = operations['chat___messages___search']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesSearchResponse = operations['chat___messages___search']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesShowRequest = operations['chat___messages___show']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesShowResponse = operations['chat___messages___show']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesUserTimelineRequest = operations['chat___messages___user-timeline']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatMessagesUserTimelineResponse = operations['chat___messages___user-timeline']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoom = components['schemas']['ChatRoom'];
+
+// @public (undocumented)
+type ChatRoomInvitation = components['schemas']['ChatRoomInvitation'];
+
+// @public (undocumented)
+type ChatRoomMembership = components['schemas']['ChatRoomMembership'];
+
+// @public (undocumented)
+type ChatRoomsCreateRequest = operations['chat___rooms___create']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsCreateResponse = operations['chat___rooms___create']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsDeleteRequest = operations['chat___rooms___delete']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsDeleteResponse = operations['chat___rooms___delete']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsInvitationsCreateRequest = operations['chat___rooms___invitations___create']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsInvitationsCreateResponse = operations['chat___rooms___invitations___create']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsInvitationsIgnoreRequest = operations['chat___rooms___invitations___ignore']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsInvitationsIgnoreResponse = operations['chat___rooms___invitations___ignore']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations___inbox']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsInvitationsInboxResponse = operations['chat___rooms___invitations___inbox']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsJoiningRequest = operations['chat___rooms___joining']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsJoiningResponse = operations['chat___rooms___joining']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsJoinResponse = operations['chat___rooms___join']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsLeaveRequest = operations['chat___rooms___leave']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsLeaveResponse = operations['chat___rooms___leave']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsMembersRequest = operations['chat___rooms___members']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsMembersResponse = operations['chat___rooms___members']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsMuteRequest = operations['chat___rooms___mute']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsMuteResponse = operations['chat___rooms___mute']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsOwnedRequest = operations['chat___rooms___owned']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsOwnedResponse = operations['chat___rooms___owned']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsShowRequest = operations['chat___rooms___show']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsShowResponse = operations['chat___rooms___show']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsUpdateRequest = operations['chat___rooms___update']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ChatRoomsUpdateResponse = operations['chat___rooms___update']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
type Clip = components['schemas']['Clip'];
// @public (undocumented)
@@ -1304,7 +1413,6 @@ declare namespace entities {
AdminAnnouncementsListRequest,
AdminAnnouncementsListResponse,
AdminAnnouncementsUpdateRequest,
- AdminApproveUserRequest,
AdminAvatarDecorationsCreateRequest,
AdminAvatarDecorationsCreateResponse,
AdminAvatarDecorationsDeleteRequest,
@@ -1313,8 +1421,6 @@ declare namespace entities {
AdminAvatarDecorationsUpdateRequest,
AdminCaptchaCurrentResponse,
AdminCaptchaSaveRequest,
- AdminCwUserRequest,
- AdminDeclineUserRequest,
AdminDeleteAccountRequest,
AdminDeleteAllFilesOfAUserRequest,
AdminDriveFilesRequest,
@@ -1352,13 +1458,11 @@ declare namespace entities {
AdminInviteListRequest,
AdminInviteListResponse,
AdminMetaResponse,
- AdminNsfwUserRequest,
AdminPromoCreateRequest,
AdminQueueDeliverDelayedResponse,
AdminQueueInboxDelayedResponse,
AdminQueuePromoteRequest,
AdminQueueStatsResponse,
- AdminRejectQuotesRequest,
AdminRelaysAddRequest,
AdminRelaysAddResponse,
AdminRelaysListResponse,
@@ -1386,7 +1490,6 @@ declare namespace entities {
AdminShowUserResponse,
AdminShowUsersRequest,
AdminShowUsersResponse,
- AdminSilenceUserRequest,
AdminSuspendUserRequest,
AdminSystemWebhookCreateRequest,
AdminSystemWebhookCreateResponse,
@@ -1398,13 +1501,13 @@ declare namespace entities {
AdminSystemWebhookTestRequest,
AdminSystemWebhookUpdateRequest,
AdminSystemWebhookUpdateResponse,
- AdminUnnsfwUserRequest,
AdminUnsetUserAvatarRequest,
AdminUnsetUserBannerRequest,
- AdminUnsilenceUserRequest,
AdminUnsuspendUserRequest,
AdminUpdateAbuseUserReportRequest,
AdminUpdateMetaRequest,
+ AdminUpdateProxyAccountRequest,
+ AdminUpdateProxyAccountResponse,
AdminUpdateUserNoteRequest,
AnnouncementsRequest,
AnnouncementsResponse,
@@ -1488,6 +1591,50 @@ declare namespace entities {
ChartsUserReactionsResponse,
ChartsUsersRequest,
ChartsUsersResponse,
+ ChatHistoryRequest,
+ ChatHistoryResponse,
+ ChatMessagesCreateToRoomRequest,
+ ChatMessagesCreateToRoomResponse,
+ ChatMessagesCreateToUserRequest,
+ ChatMessagesCreateToUserResponse,
+ ChatMessagesDeleteRequest,
+ ChatMessagesDeleteResponse,
+ ChatMessagesReactRequest,
+ ChatMessagesReactResponse,
+ ChatMessagesRoomTimelineRequest,
+ ChatMessagesRoomTimelineResponse,
+ ChatMessagesSearchRequest,
+ ChatMessagesSearchResponse,
+ ChatMessagesShowRequest,
+ ChatMessagesShowResponse,
+ ChatMessagesUserTimelineRequest,
+ ChatMessagesUserTimelineResponse,
+ ChatRoomsCreateRequest,
+ ChatRoomsCreateResponse,
+ ChatRoomsDeleteRequest,
+ ChatRoomsDeleteResponse,
+ ChatRoomsInvitationsCreateRequest,
+ ChatRoomsInvitationsCreateResponse,
+ ChatRoomsInvitationsIgnoreRequest,
+ ChatRoomsInvitationsIgnoreResponse,
+ ChatRoomsInvitationsInboxRequest,
+ ChatRoomsInvitationsInboxResponse,
+ ChatRoomsJoinRequest,
+ ChatRoomsJoinResponse,
+ ChatRoomsJoiningRequest,
+ ChatRoomsJoiningResponse,
+ ChatRoomsLeaveRequest,
+ ChatRoomsLeaveResponse,
+ ChatRoomsMembersRequest,
+ ChatRoomsMembersResponse,
+ ChatRoomsMuteRequest,
+ ChatRoomsMuteResponse,
+ ChatRoomsOwnedRequest,
+ ChatRoomsOwnedResponse,
+ ChatRoomsShowRequest,
+ ChatRoomsShowResponse,
+ ChatRoomsUpdateRequest,
+ ChatRoomsUpdateResponse,
ClipsAddNoteRequest,
ClipsCreateRequest,
ClipsCreateResponse,
@@ -1647,7 +1794,6 @@ declare namespace entities {
IImportBlockingRequest,
IImportFollowingRequest,
IImportMutingRequest,
- IImportNotesRequest,
IImportUserListsRequest,
IMoveRequest,
IMoveResponse,
@@ -1669,7 +1815,6 @@ declare namespace entities {
IRegistryGetAllResponse,
IRegistryGetDetailRequest,
IRegistryGetDetailResponse,
- IRegistryGetUnsecureRequest,
IRegistryKeysRequest,
IRegistryKeysResponse,
IRegistryKeysWithTypeRequest,
@@ -1711,8 +1856,6 @@ declare namespace entities {
MyAppsResponse,
NotesRequest,
NotesResponse,
- NotesBubbleTimelineRequest,
- NotesBubbleTimelineResponse,
NotesChildrenRequest,
NotesChildrenResponse,
NotesClipsRequest,
@@ -1722,26 +1865,20 @@ declare namespace entities {
NotesCreateRequest,
NotesCreateResponse,
NotesDeleteRequest,
- NotesEditRequest,
- NotesEditResponse,
NotesFavoritesCreateRequest,
NotesFavoritesDeleteRequest,
NotesFeaturedRequest,
NotesFeaturedResponse,
- NotesFollowingRequest,
- NotesFollowingResponse,
NotesGlobalTimelineRequest,
NotesGlobalTimelineResponse,
NotesHybridTimelineRequest,
NotesHybridTimelineResponse,
- NotesLikeRequest,
NotesLocalTimelineRequest,
NotesLocalTimelineResponse,
NotesMentionsRequest,
NotesMentionsResponse,
NotesPollsRecommendationRequest,
NotesPollsRecommendationResponse,
- NotesPollsRefreshRequest,
NotesPollsVoteRequest,
NotesReactionsRequest,
NotesReactionsResponse,
@@ -1751,10 +1888,6 @@ declare namespace entities {
NotesRenotesResponse,
NotesRepliesRequest,
NotesRepliesResponse,
- NotesScheduleCreateRequest,
- NotesScheduleDeleteRequest,
- NotesScheduleListRequest,
- NotesScheduleListResponse,
NotesSearchRequest,
NotesSearchResponse,
NotesSearchByTagRequest,
@@ -1772,8 +1905,6 @@ declare namespace entities {
NotesUnrenoteRequest,
NotesUserListTimelineRequest,
NotesUserListTimelineResponse,
- NotesVersionsRequest,
- NotesVersionsResponse,
NotificationsCreateRequest,
PagePushRequest,
PagesCreateRequest,
@@ -1814,7 +1945,6 @@ declare namespace entities {
RolesUsersRequest,
RolesUsersResponse,
ServerInfoResponse,
- SponsorsRequest,
StatsResponse,
SwRegisterRequest,
SwRegisterResponse,
@@ -1937,7 +2067,12 @@ declare namespace entities {
MetaDetailedOnly,
MetaDetailed,
SystemWebhook,
- AbuseReportNotificationRecipient
+ AbuseReportNotificationRecipient,
+ ChatMessage,
+ ChatMessageLite,
+ ChatRoom,
+ ChatRoomInvitation,
+ ChatRoomMembership
}
}
export { entities }
@@ -2311,9 +2446,6 @@ type IImportFollowingRequest = operations['i___import-following']['requestBody']
type IImportMutingRequest = operations['i___import-muting']['requestBody']['content']['application/json'];
// @public (undocumented)
-type IImportNotesRequest = operations['i___import-notes']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type IImportUserListsRequest = operations['i___import-user-lists']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -2395,9 +2527,6 @@ type IRegistryGetRequest = operations['i___registry___get']['requestBody']['cont
type IRegistryGetResponse = operations['i___registry___get']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type IRegistryGetUnsecureRequest = operations['i___registry___get-unsecure']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type IRegistryKeysRequest = operations['i___registry___keys']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -2549,30 +2678,12 @@ type ModerationLog = {
type: 'updateServerSettings';
info: ModerationLogPayloads['updateServerSettings'];
} | {
- type: 'approve';
- info: ModerationLogPayloads['approve'];
-} | {
- type: 'decline';
- info: ModerationLogPayloads['decline'];
-} | {
type: 'suspend';
info: ModerationLogPayloads['suspend'];
} | {
type: 'unsuspend';
info: ModerationLogPayloads['unsuspend'];
} | {
- type: 'acceptQuotesUser';
- info: ModerationLogPayloads['acceptQuotesUser'];
-} | {
- type: 'rejectQuotesUser';
- info: ModerationLogPayloads['rejectQuotesUser'];
-} | {
- type: 'acceptQuotesInstance';
- info: ModerationLogPayloads['acceptQuotesInstance'];
-} | {
- type: 'rejectQuotesInstance';
- info: ModerationLogPayloads['rejectQuotesInstance'];
-} | {
type: 'updateUserNote';
info: ModerationLogPayloads['updateUserNote'];
} | {
@@ -2630,15 +2741,6 @@ type ModerationLog = {
type: 'deleteUserAnnouncement';
info: ModerationLogPayloads['deleteUserAnnouncement'];
} | {
- type: 'setMandatoryCW';
- info: ModerationLogPayloads['setMandatoryCW'];
-} | {
- type: 'setRemoteInstanceNSFW';
- info: ModerationLogPayloads['setRemoteInstanceNSFW'];
-} | {
- type: 'unsetRemoteInstanceNSFW';
- info: ModerationLogPayloads['unsetRemoteInstanceNSFW'];
-} | {
type: 'resetPassword';
info: ModerationLogPayloads['resetPassword'];
} | {
@@ -2648,12 +2750,6 @@ type ModerationLog = {
type: 'unsuspendRemoteInstance';
info: ModerationLogPayloads['unsuspendRemoteInstance'];
} | {
- type: 'rejectRemoteInstanceReports';
- info: ModerationLogPayloads['rejectRemoteInstanceReports'];
-} | {
- type: 'acceptRemoteInstanceReports';
- info: ModerationLogPayloads['acceptRemoteInstanceReports'];
-} | {
type: 'updateRemoteInstanceNote';
info: ModerationLogPayloads['updateRemoteInstanceNote'];
} | {
@@ -2728,55 +2824,10 @@ type ModerationLog = {
} | {
type: 'deleteGalleryPost';
info: ModerationLogPayloads['deleteGalleryPost'];
-} | {
- type: 'clearUserFiles';
- info: ModerationLogPayloads['clearUserFiles'];
-} | {
- type: 'nsfwUser';
- info: ModerationLogPayloads['nsfwUser'];
-} | {
- type: 'unNsfwUser';
- info: ModerationLogPayloads['unNsfwUser'];
-} | {
- type: 'silenceUser';
- info: ModerationLogPayloads['silenceUser'];
-} | {
- type: 'unSilenceUser';
- info: ModerationLogPayloads['unSilenceUser'];
-} | {
- type: 'createAccount';
- info: ModerationLogPayloads['createAccount'];
-} | {
- type: 'clearRemoteFiles';
- info: ModerationLogPayloads['clearRemoteFiles'];
-} | {
- type: 'clearOwnerlessFiles';
- info: ModerationLogPayloads['clearOwnerlessFiles'];
-} | {
- type: 'updateCustomEmojis';
- info: ModerationLogPayloads['updateCustomEmojis'];
-} | {
- type: 'importCustomEmojis';
- info: ModerationLogPayloads['importCustomEmojis'];
-} | {
- type: 'clearInstanceFiles';
- info: ModerationLogPayloads['clearInstanceFiles'];
-} | {
- type: 'severFollowRelations';
- info: ModerationLogPayloads['severFollowRelations'];
-} | {
- type: 'createPromo';
- info: ModerationLogPayloads['createPromo'];
-} | {
- type: 'addRelay';
- info: ModerationLogPayloads['addRelay'];
-} | {
- type: 'removeRelay';
- info: ModerationLogPayloads['removeRelay'];
});
// @public (undocumented)
-export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "decline", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "setMandatoryCW", "setRemoteInstanceNSFW", "unsetRemoteInstanceNSFW", "suspendRemoteInstance", "unsuspendRemoteInstance", "rejectRemoteInstanceReports", "acceptRemoteInstanceReports", "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"];
+export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "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'];
@@ -2819,12 +2870,6 @@ type NoteFavorite = components['schemas']['NoteFavorite'];
type NoteReaction = components['schemas']['NoteReaction'];
// @public (undocumented)
-type NotesBubbleTimelineRequest = operations['notes___bubble-timeline']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
-type NotesBubbleTimelineResponse = operations['notes___bubble-timeline']['responses']['200']['content']['application/json'];
-
-// @public (undocumented)
type NotesChildrenRequest = operations['notes___children']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -2852,12 +2897,6 @@ type NotesCreateResponse = operations['notes___create']['responses']['200']['con
type NotesDeleteRequest = operations['notes___delete']['requestBody']['content']['application/json'];
// @public (undocumented)
-type NotesEditRequest = operations['notes___edit']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
-type NotesEditResponse = operations['notes___edit']['responses']['200']['content']['application/json'];
-
-// @public (undocumented)
type NotesFavoritesCreateRequest = operations['notes___favorites___create']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -2870,12 +2909,6 @@ type NotesFeaturedRequest = operations['notes___featured']['requestBody']['conte
type NotesFeaturedResponse = operations['notes___featured']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type NotesFollowingRequest = operations['notes___following']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
-type NotesFollowingResponse = operations['notes___following']['responses']['200']['content']['application/json'];
-
-// @public (undocumented)
type NotesGlobalTimelineRequest = operations['notes___global-timeline']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -2888,9 +2921,6 @@ type NotesHybridTimelineRequest = operations['notes___hybrid-timeline']['request
type NotesHybridTimelineResponse = operations['notes___hybrid-timeline']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type NotesLikeRequest = operations['notes___like']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type NotesLocalTimelineRequest = operations['notes___local-timeline']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -2909,9 +2939,6 @@ type NotesPollsRecommendationRequest = operations['notes___polls___recommendatio
type NotesPollsRecommendationResponse = operations['notes___polls___recommendation']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type NotesPollsRefreshRequest = operations['notes___polls___refresh']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type NotesPollsVoteRequest = operations['notes___polls___vote']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -2945,18 +2972,6 @@ type NotesRequest = operations['notes']['requestBody']['content']['application/j
type NotesResponse = operations['notes']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type NotesScheduleCreateRequest = operations['notes___schedule___create']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
-type NotesScheduleDeleteRequest = operations['notes___schedule___delete']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
-type NotesScheduleListRequest = operations['notes___schedule___list']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
-type NotesScheduleListResponse = operations['notes___schedule___list']['responses']['200']['content']['application/json'];
-
-// @public (undocumented)
type NotesSearchByTagRequest = operations['notes___search-by-tag']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -3008,12 +3023,6 @@ type NotesUserListTimelineRequest = operations['notes___user-list-timeline']['re
type NotesUserListTimelineResponse = operations['notes___user-list-timeline']['responses']['200']['content']['application/json'];
// @public (undocumented)
-type NotesVersionsRequest = operations['notes___versions']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
-type NotesVersionsResponse = operations['notes___versions']['responses']['200']['content']['application/json'];
-
-// @public (undocumented)
export const noteVisibilities: readonly ["public", "home", "followers", "specified"];
// @public (undocumented)
@@ -3023,7 +3032,7 @@ type Notification_2 = components['schemas']['Notification'];
type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json'];
// @public (undocumented)
-export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned", "edited", "scheduledNoteFailed", "scheduledNotePosted"];
+export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"];
// @public (undocumented)
export function nyaize(text: string): string;
@@ -3086,7 +3095,7 @@ type PartialRolePolicyOverride = Partial<{
}>;
// @public (undocumented)
-export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notes-schedule", "write:notes-schedule", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:decline-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:cw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:reject-quotes", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
+export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse", "write:chat", "read:chat"];
// @public (undocumented)
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
@@ -3297,12 +3306,12 @@ type SigninFlowResponse = {
} | {
finished: false;
next: 'passkey';
- authRequest: PublicKeyCredentialRequestOptionsJSON;
+ authRequest: PublicKeyCredentialRequestOptionsJSON_2;
};
// @public (undocumented)
type SigninWithPasskeyInitResponse = {
- option: PublicKeyCredentialRequestOptionsJSON;
+ option: PublicKeyCredentialRequestOptionsJSON_2;
context: string;
};
@@ -3349,9 +3358,6 @@ type SignupResponse = MeDetailed & {
};
// @public (undocumented)
-type SponsorsRequest = operations['sponsors']['requestBody']['content']['application/json'];
-
-// @public (undocumented)
type StatsResponse = operations['stats']['responses']['200']['content']['application/json'];
// @public (undocumented)
@@ -3630,8 +3636,8 @@ type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['respons
//
// src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
// src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" 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
+// src/streaming.types.ts:217:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
+// src/streaming.types.ts:227:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
diff --git a/packages/misskey-js/generator/eslint.config.js b/packages/misskey-js/generator/eslint.config.js
index 4bf78c3b91..7139f88f5b 100644
--- a/packages/misskey-js/generator/eslint.config.js
+++ b/packages/misskey-js/generator/eslint.config.js
@@ -14,4 +14,10 @@ export default [
},
},
},
+ {
+ files: ['built/autogen/**.ts'],
+ rules: {
+ '@stylistic/indent': 'off',
+ },
+ },
];
diff --git a/packages/misskey-js/generator/package.json b/packages/misskey-js/generator/package.json
index 9ed5b8470f..649135afc3 100644
--- a/packages/misskey-js/generator/package.json
+++ b/packages/misskey-js/generator/package.json
@@ -7,16 +7,16 @@
"generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix --cache"
},
"devDependencies": {
- "@readme/openapi-parser": "2.6.0",
- "@types/node": "22.9.0",
- "@typescript-eslint/eslint-plugin": "7.17.0",
- "@typescript-eslint/parser": "7.17.0",
+ "@readme/openapi-parser": "2.7.0",
+ "@types/node": "22.13.9",
+ "@typescript-eslint/eslint-plugin": "8.26.0",
+ "@typescript-eslint/parser": "8.26.0",
"eslint": "9.14.0",
"openapi-types": "12.1.3",
- "openapi-typescript": "6.7.3",
+ "openapi-typescript": "6.7.6",
"ts-case-convert": "2.1.0",
- "tsx": "4.4.0",
- "typescript": "5.6.3"
+ "tsx": "4.19.3",
+ "typescript": "5.8.2"
},
"files": [
"built"
diff --git a/packages/misskey-js/generator/src/generator.ts b/packages/misskey-js/generator/src/generator.ts
index 88f2ae9ee9..889051f3e8 100644
--- a/packages/misskey-js/generator/src/generator.ts
+++ b/packages/misskey-js/generator/src/generator.ts
@@ -1,8 +1,9 @@
-import { mkdir, writeFile } from 'fs/promises';
+import assert from 'assert';
+import { mkdir, readFile, writeFile } from 'fs/promises';
import { OpenAPIV3_1 } from 'openapi-types';
import { toPascal } from 'ts-case-convert';
import OpenAPIParser from '@readme/openapi-parser';
-import openapiTS from 'openapi-typescript';
+import openapiTS, { OpenAPI3, OperationObject, PathItemObject } from 'openapi-typescript';
async function generateBaseTypes(
openApiDocs: OpenAPIV3_1.Document,
@@ -20,7 +21,29 @@ async function generateBaseTypes(
}
lines.push('');
- const generatedTypes = await openapiTS(openApiJsonPath, {
+ // NOTE: Align `operationId` of GET and POST to avoid duplication of type definitions
+ const openApi = JSON.parse(await readFile(openApiJsonPath, 'utf8')) as OpenAPI3;
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ for (const [key, item] of Object.entries(openApi.paths!)) {
+ assert('post' in item);
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ openApi.paths![key] = {
+ ...('get' in item ? {
+ get: {
+ ...item.get,
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ operationId: ((item as PathItemObject).get as OperationObject).operationId!.replaceAll('get___', ''),
+ },
+ } : {}),
+ post: {
+ ...item.post,
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ operationId: ((item as PathItemObject).post as OperationObject).operationId!.replaceAll('post___', ''),
+ },
+ };
+ }
+
+ const generatedTypes = await openapiTS(openApi, {
exportType: true,
transform(schemaObject) {
if ('format' in schemaObject && schemaObject.format === 'binary') {
@@ -78,7 +101,7 @@ async function generateEndpoints(
for (const operation of postPathItems) {
const path = operation._path_;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const operationId = operation.operationId!;
+ const operationId = operation.operationId!.replaceAll('get___', '').replaceAll('post___', '');
const endpoint = new Endpoint(path);
endpoints.push(endpoint);
@@ -150,7 +173,7 @@ async function generateEndpoints(
endpointOutputLine.push(
...endpoints.map(it => '\t' + it.toLine()),
);
- endpointOutputLine.push('}');
+ endpointOutputLine.push('};');
endpointOutputLine.push('');
function generateEndpointReqMediaTypesType() {
@@ -195,7 +218,7 @@ async function generateApiClientJSDoc(
for (const operation of postPathItems) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const operationId = operation.operationId!;
+ const operationId = operation.operationId!.replaceAll('get___', '').replaceAll('post___', '');
if (operation.description) {
endpoints.push({
diff --git a/packages/misskey-js/generator/tsconfig.json b/packages/misskey-js/generator/tsconfig.json
index c814df612e..d65042dc6d 100644
--- a/packages/misskey-js/generator/tsconfig.json
+++ b/packages/misskey-js/generator/tsconfig.json
@@ -3,7 +3,7 @@
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
- "moduleResolution": "nodenext",
+ "moduleResolution": "node16",
"strict": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 2a3458b4c9..05b3f11ac2 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
- "version": "2025.3.0-dev",
+ "version": "2025.3.2-beta.10",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
@@ -35,29 +35,29 @@
"directory": "packages/misskey-js"
},
"devDependencies": {
- "@microsoft/api-extractor": "7.47.11",
+ "@microsoft/api-extractor": "7.51.1",
"@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",
+ "@types/node": "22.13.9",
+ "@typescript-eslint/eslint-plugin": "8.26.0",
+ "@typescript-eslint/parser": "8.26.0",
"jest": "29.7.0",
"jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.5.0",
"mock-socket": "9.3.1",
"ncp": "2.0.0",
- "nodemon": "3.1.7",
+ "nodemon": "3.1.9",
"execa": "8.0.1",
"tsd": "0.31.2",
- "typescript": "5.6.3",
- "esbuild": "0.24.0",
- "glob": "11.0.0"
+ "typescript": "5.8.2",
+ "esbuild": "0.25.0",
+ "glob": "11.0.1"
},
"files": [
"built"
],
"dependencies": {
- "@simplewebauthn/types": "11.0.0",
+ "@simplewebauthn/types": "12.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 838949f8e1..fa803194bf 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -33,14 +33,15 @@ type IsCaseMatched<E extends keyof Endpoints, P extends Endpoints[E]['req'], C e
Endpoints[E]['res'] extends SwitchCase
// eslint-disable-next-line @typescript-eslint/no-explicit-any
? IsNeverType<StrictExtract<Endpoints[E]['res']['$switch']['$cases'][C], [P, any]>> extends false ? true : false
- : false
+ : false;
type GetCaseResult<E extends keyof Endpoints, P extends Endpoints[E]['req'], C extends number> =
Endpoints[E]['res'] extends SwitchCase
// eslint-disable-next-line @typescript-eslint/no-explicit-any
? StrictExtract<Endpoints[E]['res']['$switch']['$cases'][C], [P, any]>[1]
- : never
+ : never;
+/* eslint-disable @stylistic/indent */
export type SwitchCaseResponseType<E extends keyof Endpoints, P extends Endpoints[E]['req']> = Endpoints[E]['res'] extends SwitchCase
? IsCaseMatched<E, P, 0> extends true ? GetCaseResult<E, P, 0> :
IsCaseMatched<E, P, 1> extends true ? GetCaseResult<E, P, 1> :
@@ -53,6 +54,7 @@ export type SwitchCaseResponseType<E extends keyof Endpoints, P extends Endpoint
IsCaseMatched<E, P, 8> extends true ? GetCaseResult<E, P, 8> :
IsCaseMatched<E, P, 9> extends true ? GetCaseResult<E, P, 9> :
Endpoints[E]['res']['$switch']['$default'] : Endpoints[E]['res'];
+/* eslint-enable @stylistic/indent */
export type Endpoints = Overwrite<
Gen,
@@ -106,4 +108,4 @@ export type Endpoints = Overwrite<
res: AdminRolesCreateResponse;
}
}
->
+>;
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index 2f3cd3b2ad..53e18b8501 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1107,6 +1107,17 @@ declare module '../api.js' {
/**
* No description provided.
*
+ * **Credential required**: *Yes* / **Permission**: *write:admin:account*
+ */
+ request<E extends 'admin/update-proxy-account', 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:user-note*
*/
request<E extends 'admin/update-user-note', P extends Endpoints[E]['req']>(
@@ -1625,6 +1636,248 @@ declare module '../api.js' {
/**
* No description provided.
*
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/history', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/messages/create-to-room', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/messages/create-to-user', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/messages/delete', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/messages/react', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/messages/room-timeline', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/messages/search', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/messages/show', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/messages/user-timeline', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/create', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/delete', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/invitations/create', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/invitations/ignore', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/rooms/invitations/inbox', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/join', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/rooms/joining', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/leave', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/members', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/mute', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/rooms/owned', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ request<E extends 'chat/rooms/show', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ request<E extends 'chat/rooms/update', P extends Endpoints[E]['req']>(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise<SwitchCaseResponseType<E, P>>;
+
+ /**
+ * No description provided.
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'clips/add-note', P extends Endpoints[E]['req']>(
@@ -2944,17 +3197,6 @@ declare module '../api.js' {
*
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
- request<E extends 'i/read-all-unread-notes', P extends Endpoints[E]['req']>(
- endpoint: E,
- params: P,
- credential?: string | null,
- ): Promise<SwitchCaseResponseType<E, P>>;
-
- /**
- * No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:account*
- */
request<E extends 'i/read-announcement', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 0ef2db70f6..ddae16072c 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -130,6 +130,8 @@ import type {
AdminUnsuspendUserRequest,
AdminUpdateAbuseUserReportRequest,
AdminUpdateMetaRequest,
+ AdminUpdateProxyAccountRequest,
+ AdminUpdateProxyAccountResponse,
AdminUpdateUserNoteRequest,
AnnouncementsRequest,
AnnouncementsResponse,
@@ -213,6 +215,50 @@ import type {
ChartsUserReactionsResponse,
ChartsUsersRequest,
ChartsUsersResponse,
+ ChatHistoryRequest,
+ ChatHistoryResponse,
+ ChatMessagesCreateToRoomRequest,
+ ChatMessagesCreateToRoomResponse,
+ ChatMessagesCreateToUserRequest,
+ ChatMessagesCreateToUserResponse,
+ ChatMessagesDeleteRequest,
+ ChatMessagesDeleteResponse,
+ ChatMessagesReactRequest,
+ ChatMessagesReactResponse,
+ ChatMessagesRoomTimelineRequest,
+ ChatMessagesRoomTimelineResponse,
+ ChatMessagesSearchRequest,
+ ChatMessagesSearchResponse,
+ ChatMessagesShowRequest,
+ ChatMessagesShowResponse,
+ ChatMessagesUserTimelineRequest,
+ ChatMessagesUserTimelineResponse,
+ ChatRoomsCreateRequest,
+ ChatRoomsCreateResponse,
+ ChatRoomsDeleteRequest,
+ ChatRoomsDeleteResponse,
+ ChatRoomsInvitationsCreateRequest,
+ ChatRoomsInvitationsCreateResponse,
+ ChatRoomsInvitationsIgnoreRequest,
+ ChatRoomsInvitationsIgnoreResponse,
+ ChatRoomsInvitationsInboxRequest,
+ ChatRoomsInvitationsInboxResponse,
+ ChatRoomsJoinRequest,
+ ChatRoomsJoinResponse,
+ ChatRoomsJoiningRequest,
+ ChatRoomsJoiningResponse,
+ ChatRoomsLeaveRequest,
+ ChatRoomsLeaveResponse,
+ ChatRoomsMembersRequest,
+ ChatRoomsMembersResponse,
+ ChatRoomsMuteRequest,
+ ChatRoomsMuteResponse,
+ ChatRoomsOwnedRequest,
+ ChatRoomsOwnedResponse,
+ ChatRoomsShowRequest,
+ ChatRoomsShowResponse,
+ ChatRoomsUpdateRequest,
+ ChatRoomsUpdateResponse,
ClipsAddNoteRequest,
ClipsCreateRequest,
ClipsCreateResponse,
@@ -710,6 +756,7 @@ export type Endpoints = {
'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse };
'admin/update-abuse-user-report': { req: AdminUpdateAbuseUserReportRequest; res: EmptyResponse };
'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse };
+ 'admin/update-proxy-account': { req: AdminUpdateProxyAccountRequest; res: AdminUpdateProxyAccountResponse };
'admin/update-user-note': { req: AdminUpdateUserNoteRequest; res: EmptyResponse };
'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse };
'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse };
@@ -757,6 +804,28 @@ export type Endpoints = {
'charts/user/pv': { req: ChartsUserPvRequest; res: ChartsUserPvResponse };
'charts/user/reactions': { req: ChartsUserReactionsRequest; res: ChartsUserReactionsResponse };
'charts/users': { req: ChartsUsersRequest; res: ChartsUsersResponse };
+ 'chat/history': { req: ChatHistoryRequest; res: ChatHistoryResponse };
+ 'chat/messages/create-to-room': { req: ChatMessagesCreateToRoomRequest; res: ChatMessagesCreateToRoomResponse };
+ 'chat/messages/create-to-user': { req: ChatMessagesCreateToUserRequest; res: ChatMessagesCreateToUserResponse };
+ 'chat/messages/delete': { req: ChatMessagesDeleteRequest; res: ChatMessagesDeleteResponse };
+ 'chat/messages/react': { req: ChatMessagesReactRequest; res: ChatMessagesReactResponse };
+ 'chat/messages/room-timeline': { req: ChatMessagesRoomTimelineRequest; res: ChatMessagesRoomTimelineResponse };
+ 'chat/messages/search': { req: ChatMessagesSearchRequest; res: ChatMessagesSearchResponse };
+ 'chat/messages/show': { req: ChatMessagesShowRequest; res: ChatMessagesShowResponse };
+ 'chat/messages/user-timeline': { req: ChatMessagesUserTimelineRequest; res: ChatMessagesUserTimelineResponse };
+ 'chat/rooms/create': { req: ChatRoomsCreateRequest; res: ChatRoomsCreateResponse };
+ 'chat/rooms/delete': { req: ChatRoomsDeleteRequest; res: ChatRoomsDeleteResponse };
+ 'chat/rooms/invitations/create': { req: ChatRoomsInvitationsCreateRequest; res: ChatRoomsInvitationsCreateResponse };
+ 'chat/rooms/invitations/ignore': { req: ChatRoomsInvitationsIgnoreRequest; res: ChatRoomsInvitationsIgnoreResponse };
+ 'chat/rooms/invitations/inbox': { req: ChatRoomsInvitationsInboxRequest; res: ChatRoomsInvitationsInboxResponse };
+ 'chat/rooms/join': { req: ChatRoomsJoinRequest; res: ChatRoomsJoinResponse };
+ 'chat/rooms/joining': { req: ChatRoomsJoiningRequest; res: ChatRoomsJoiningResponse };
+ 'chat/rooms/leave': { req: ChatRoomsLeaveRequest; res: ChatRoomsLeaveResponse };
+ 'chat/rooms/members': { req: ChatRoomsMembersRequest; res: ChatRoomsMembersResponse };
+ 'chat/rooms/mute': { req: ChatRoomsMuteRequest; res: ChatRoomsMuteResponse };
+ 'chat/rooms/owned': { req: ChatRoomsOwnedRequest; res: ChatRoomsOwnedResponse };
+ 'chat/rooms/show': { req: ChatRoomsShowRequest; res: ChatRoomsShowResponse };
+ 'chat/rooms/update': { req: ChatRoomsUpdateRequest; res: ChatRoomsUpdateResponse };
'clips/add-note': { req: ClipsAddNoteRequest; res: EmptyResponse };
'clips/create': { req: ClipsCreateRequest; res: ClipsCreateResponse };
'clips/delete': { req: ClipsDeleteRequest; res: EmptyResponse };
@@ -874,7 +943,6 @@ export type Endpoints = {
'i/page-likes': { req: IPageLikesRequest; res: IPageLikesResponse };
'i/pages': { req: IPagesRequest; res: IPagesResponse };
'i/pin': { req: IPinRequest; res: IPinResponse };
- 'i/read-all-unread-notes': { req: EmptyRequest; res: EmptyResponse };
'i/read-announcement': { req: IReadAnnouncementRequest; res: EmptyResponse };
'i/regenerate-token': { req: IRegenerateTokenRequest; res: EmptyResponse };
'i/registry/get': { req: IRegistryGetRequest; res: IRegistryGetResponse };
@@ -1020,7 +1088,7 @@ export type Endpoints = {
'users/show': { req: UsersShowRequest; res: UsersShowResponse };
'users/update-memo': { req: UsersUpdateMemoRequest; res: EmptyResponse };
'v2/admin/emoji/list': { req: V2AdminEmojiListRequest; res: V2AdminEmojiListResponse };
-}
+};
/**
* NOTE: The content-type for all endpoints not listed here is application/json.
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index df411153fd..7340a4424d 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -133,6 +133,8 @@ export type AdminUnsilenceUserRequest = operations['admin___unsilence-user']['re
export type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
export type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json'];
export type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
+export type AdminUpdateProxyAccountRequest = operations['admin___update-proxy-account']['requestBody']['content']['application/json'];
+export type AdminUpdateProxyAccountResponse = operations['admin___update-proxy-account']['responses']['200']['content']['application/json'];
export type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json'];
export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json'];
export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json'];
@@ -216,6 +218,50 @@ export type ChartsUserReactionsRequest = operations['charts___user___reactions']
export type ChartsUserReactionsResponse = operations['charts___user___reactions']['responses']['200']['content']['application/json'];
export type ChartsUsersRequest = operations['charts___users']['requestBody']['content']['application/json'];
export type ChartsUsersResponse = operations['charts___users']['responses']['200']['content']['application/json'];
+export type ChatHistoryRequest = operations['chat___history']['requestBody']['content']['application/json'];
+export type ChatHistoryResponse = operations['chat___history']['responses']['200']['content']['application/json'];
+export type ChatMessagesCreateToRoomRequest = operations['chat___messages___create-to-room']['requestBody']['content']['application/json'];
+export type ChatMessagesCreateToRoomResponse = operations['chat___messages___create-to-room']['responses']['200']['content']['application/json'];
+export type ChatMessagesCreateToUserRequest = operations['chat___messages___create-to-user']['requestBody']['content']['application/json'];
+export type ChatMessagesCreateToUserResponse = operations['chat___messages___create-to-user']['responses']['200']['content']['application/json'];
+export type ChatMessagesDeleteRequest = operations['chat___messages___delete']['requestBody']['content']['application/json'];
+export type ChatMessagesDeleteResponse = operations['chat___messages___delete']['responses']['200']['content']['application/json'];
+export type ChatMessagesReactRequest = operations['chat___messages___react']['requestBody']['content']['application/json'];
+export type ChatMessagesReactResponse = operations['chat___messages___react']['responses']['200']['content']['application/json'];
+export type ChatMessagesRoomTimelineRequest = operations['chat___messages___room-timeline']['requestBody']['content']['application/json'];
+export type ChatMessagesRoomTimelineResponse = operations['chat___messages___room-timeline']['responses']['200']['content']['application/json'];
+export type ChatMessagesSearchRequest = operations['chat___messages___search']['requestBody']['content']['application/json'];
+export type ChatMessagesSearchResponse = operations['chat___messages___search']['responses']['200']['content']['application/json'];
+export type ChatMessagesShowRequest = operations['chat___messages___show']['requestBody']['content']['application/json'];
+export type ChatMessagesShowResponse = operations['chat___messages___show']['responses']['200']['content']['application/json'];
+export type ChatMessagesUserTimelineRequest = operations['chat___messages___user-timeline']['requestBody']['content']['application/json'];
+export type ChatMessagesUserTimelineResponse = operations['chat___messages___user-timeline']['responses']['200']['content']['application/json'];
+export type ChatRoomsCreateRequest = operations['chat___rooms___create']['requestBody']['content']['application/json'];
+export type ChatRoomsCreateResponse = operations['chat___rooms___create']['responses']['200']['content']['application/json'];
+export type ChatRoomsDeleteRequest = operations['chat___rooms___delete']['requestBody']['content']['application/json'];
+export type ChatRoomsDeleteResponse = operations['chat___rooms___delete']['responses']['200']['content']['application/json'];
+export type ChatRoomsInvitationsCreateRequest = operations['chat___rooms___invitations___create']['requestBody']['content']['application/json'];
+export type ChatRoomsInvitationsCreateResponse = operations['chat___rooms___invitations___create']['responses']['200']['content']['application/json'];
+export type ChatRoomsInvitationsIgnoreRequest = operations['chat___rooms___invitations___ignore']['requestBody']['content']['application/json'];
+export type ChatRoomsInvitationsIgnoreResponse = operations['chat___rooms___invitations___ignore']['responses']['200']['content']['application/json'];
+export type ChatRoomsInvitationsInboxRequest = operations['chat___rooms___invitations___inbox']['requestBody']['content']['application/json'];
+export type ChatRoomsInvitationsInboxResponse = operations['chat___rooms___invitations___inbox']['responses']['200']['content']['application/json'];
+export type ChatRoomsJoinRequest = operations['chat___rooms___join']['requestBody']['content']['application/json'];
+export type ChatRoomsJoinResponse = operations['chat___rooms___join']['responses']['200']['content']['application/json'];
+export type ChatRoomsJoiningRequest = operations['chat___rooms___joining']['requestBody']['content']['application/json'];
+export type ChatRoomsJoiningResponse = operations['chat___rooms___joining']['responses']['200']['content']['application/json'];
+export type ChatRoomsLeaveRequest = operations['chat___rooms___leave']['requestBody']['content']['application/json'];
+export type ChatRoomsLeaveResponse = operations['chat___rooms___leave']['responses']['200']['content']['application/json'];
+export type ChatRoomsMembersRequest = operations['chat___rooms___members']['requestBody']['content']['application/json'];
+export type ChatRoomsMembersResponse = operations['chat___rooms___members']['responses']['200']['content']['application/json'];
+export type ChatRoomsMuteRequest = operations['chat___rooms___mute']['requestBody']['content']['application/json'];
+export type ChatRoomsMuteResponse = operations['chat___rooms___mute']['responses']['200']['content']['application/json'];
+export type ChatRoomsOwnedRequest = operations['chat___rooms___owned']['requestBody']['content']['application/json'];
+export type ChatRoomsOwnedResponse = operations['chat___rooms___owned']['responses']['200']['content']['application/json'];
+export type ChatRoomsShowRequest = operations['chat___rooms___show']['requestBody']['content']['application/json'];
+export type ChatRoomsShowResponse = operations['chat___rooms___show']['responses']['200']['content']['application/json'];
+export type ChatRoomsUpdateRequest = operations['chat___rooms___update']['requestBody']['content']['application/json'];
+export type ChatRoomsUpdateResponse = operations['chat___rooms___update']['responses']['200']['content']['application/json'];
export type ClipsAddNoteRequest = operations['clips___add-note']['requestBody']['content']['application/json'];
export type ClipsCreateRequest = operations['clips___create']['requestBody']['content']['application/json'];
export type ClipsCreateResponse = operations['clips___create']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index 1a30da4437..0ff9749602 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -54,3 +54,8 @@ export type MetaDetailedOnly = components['schemas']['MetaDetailedOnly'];
export type MetaDetailed = components['schemas']['MetaDetailed'];
export type SystemWebhook = components['schemas']['SystemWebhook'];
export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient'];
+export type ChatMessage = components['schemas']['ChatMessage'];
+export type ChatMessageLite = components['schemas']['ChatMessageLite'];
+export type ChatRoom = components['schemas']['ChatRoom'];
+export type ChatRoomInvitation = components['schemas']['ChatRoomInvitation'];
+export type ChatRoomMembership = components['schemas']['ChatRoomMembership'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 8ddd20ab63..31bc34e473 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -170,15 +170,6 @@ export type paths = {
*/
post: operations['admin___announcements___update'];
};
- '/admin/approve-user': {
- /**
- * admin/approve-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:approve-user*
- */
- post: operations['admin___approve-user'];
- };
'/admin/avatar-decorations/create': {
/**
* admin/avatar-decorations/create
@@ -233,24 +224,6 @@ export type paths = {
*/
post: operations['admin___captcha___save'];
};
- '/admin/cw-user': {
- /**
- * admin/cw-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:cw-user*
- */
- post: operations['admin___cw-user'];
- };
- '/admin/decline-user': {
- /**
- * admin/decline-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:decline-user*
- */
- post: operations['admin___decline-user'];
- };
'/admin/delete-account': {
/**
* admin/delete-account
@@ -468,15 +441,6 @@ export type paths = {
*/
post: operations['admin___forward-abuse-user-report'];
};
- '/admin/gen-vapid-keys': {
- /**
- * admin/gen-vapid-keys
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:meta*
- */
- post: operations['admin___gen-vapid-keys'];
- };
'/admin/get-index-stats': {
/**
* admin/get-index-stats
@@ -531,15 +495,6 @@ export type paths = {
*/
post: operations['admin___meta'];
};
- '/admin/nsfw-user': {
- /**
- * admin/nsfw-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user*
- */
- post: operations['admin___nsfw-user'];
- };
'/admin/promo/create': {
/**
* admin/promo/create
@@ -594,15 +549,6 @@ export type paths = {
*/
post: operations['admin___queue___stats'];
};
- '/admin/reject-quotes': {
- /**
- * admin/reject-quotes
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:reject-quotes*
- */
- post: operations['admin___reject-quotes'];
- };
'/admin/relays/add': {
/**
* admin/relays/add
@@ -774,15 +720,6 @@ export type paths = {
*/
post: operations['admin___show-users'];
};
- '/admin/silence-user': {
- /**
- * admin/silence-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:silence-user*
- */
- post: operations['admin___silence-user'];
- };
'/admin/suspend-user': {
/**
* admin/suspend-user
@@ -852,15 +789,6 @@ export type paths = {
*/
post: operations['admin___system-webhook___update'];
};
- '/admin/unnsfw-user': {
- /**
- * admin/unnsfw-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user*
- */
- post: operations['admin___unnsfw-user'];
- };
'/admin/unset-user-avatar': {
/**
* admin/unset-user-avatar
@@ -879,15 +807,6 @@ export type paths = {
*/
post: operations['admin___unset-user-banner'];
};
- '/admin/unsilence-user': {
- /**
- * admin/unsilence-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user*
- */
- post: operations['admin___unsilence-user'];
- };
'/admin/unsuspend-user': {
/**
* admin/unsuspend-user
@@ -915,6 +834,15 @@ export type paths = {
*/
post: operations['admin___update-meta'];
};
+ '/admin/update-proxy-account': {
+ /**
+ * admin/update-proxy-account
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:account*
+ */
+ post: operations['admin___update-proxy-account'];
+ };
'/admin/update-user-note': {
/**
* admin/update-user-note
@@ -1430,6 +1358,204 @@ export type paths = {
*/
post: operations['charts___users'];
};
+ '/chat/history': {
+ /**
+ * chat/history
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___history'];
+ };
+ '/chat/messages/create-to-room': {
+ /**
+ * chat/messages/create-to-room
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___messages___create-to-room'];
+ };
+ '/chat/messages/create-to-user': {
+ /**
+ * chat/messages/create-to-user
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___messages___create-to-user'];
+ };
+ '/chat/messages/delete': {
+ /**
+ * chat/messages/delete
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___messages___delete'];
+ };
+ '/chat/messages/react': {
+ /**
+ * chat/messages/react
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___messages___react'];
+ };
+ '/chat/messages/room-timeline': {
+ /**
+ * chat/messages/room-timeline
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___messages___room-timeline'];
+ };
+ '/chat/messages/search': {
+ /**
+ * chat/messages/search
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___messages___search'];
+ };
+ '/chat/messages/show': {
+ /**
+ * chat/messages/show
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___messages___show'];
+ };
+ '/chat/messages/user-timeline': {
+ /**
+ * chat/messages/user-timeline
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___messages___user-timeline'];
+ };
+ '/chat/rooms/create': {
+ /**
+ * chat/rooms/create
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___create'];
+ };
+ '/chat/rooms/delete': {
+ /**
+ * chat/rooms/delete
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___delete'];
+ };
+ '/chat/rooms/invitations/create': {
+ /**
+ * chat/rooms/invitations/create
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___invitations___create'];
+ };
+ '/chat/rooms/invitations/ignore': {
+ /**
+ * chat/rooms/invitations/ignore
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___invitations___ignore'];
+ };
+ '/chat/rooms/invitations/inbox': {
+ /**
+ * chat/rooms/invitations/inbox
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___rooms___invitations___inbox'];
+ };
+ '/chat/rooms/join': {
+ /**
+ * chat/rooms/join
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___join'];
+ };
+ '/chat/rooms/joining': {
+ /**
+ * chat/rooms/joining
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___rooms___joining'];
+ };
+ '/chat/rooms/leave': {
+ /**
+ * chat/rooms/leave
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___leave'];
+ };
+ '/chat/rooms/members': {
+ /**
+ * chat/rooms/members
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___members'];
+ };
+ '/chat/rooms/mute': {
+ /**
+ * chat/rooms/mute
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___mute'];
+ };
+ '/chat/rooms/owned': {
+ /**
+ * chat/rooms/owned
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___rooms___owned'];
+ };
+ '/chat/rooms/show': {
+ /**
+ * chat/rooms/show
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ post: operations['chat___rooms___show'];
+ };
+ '/chat/rooms/update': {
+ /**
+ * chat/rooms/update
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ post: operations['chat___rooms___update'];
+ };
'/clips/add-note': {
/**
* clips/add-note
@@ -1765,7 +1891,7 @@ export type paths = {
* federation/followers
* @description No description provided.
*
- * **Credential required**: *Yes* / **Permission**: *read:account*
+ * **Credential required**: *No*
*/
post: operations['federation___followers'];
};
@@ -1774,7 +1900,7 @@ export type paths = {
* federation/following
* @description No description provided.
*
- * **Credential required**: *Yes* / **Permission**: *read:account*
+ * **Credential required**: *No*
*/
post: operations['federation___following'];
};
@@ -2360,16 +2486,6 @@ export type paths = {
*/
post: operations['i___export-clips'];
};
- '/i/export-data': {
- /**
- * i/export-data
- * @description No description provided.
- *
- * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
- * **Credential required**: *Yes*
- */
- post: operations['i___export-data'];
- };
'/i/export-favorites': {
/**
* i/export-favorites
@@ -2487,16 +2603,6 @@ export type paths = {
*/
post: operations['i___import-muting'];
};
- '/i/import-notes': {
- /**
- * i/import-notes
- * @description No description provided.
- *
- * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
- * **Credential required**: *Yes*
- */
- post: operations['i___import-notes'];
- };
'/i/import-user-lists': {
/**
* i/import-user-lists
@@ -2562,15 +2668,6 @@ export type paths = {
*/
post: operations['i___pin'];
};
- '/i/read-all-unread-notes': {
- /**
- * i/read-all-unread-notes
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:account*
- */
- post: operations['i___read-all-unread-notes'];
- };
'/i/read-announcement': {
/**
* i/read-announcement
@@ -2617,15 +2714,6 @@ export type paths = {
*/
post: operations['i___registry___get-detail'];
};
- '/i/registry/get-unsecure': {
- /**
- * i/registry/get-unsecure
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:account*
- */
- post: operations['i___registry___get-unsecure'];
- };
'/i/registry/keys': {
/**
* i/registry/keys
@@ -2875,15 +2963,6 @@ export type paths = {
*/
post: operations['notes'];
};
- '/notes/bubble-timeline': {
- /**
- * notes/bubble-timeline
- * @description No description provided.
- *
- * **Credential required**: *No*
- */
- post: operations['notes___bubble-timeline'];
- };
'/notes/children': {
/**
* notes/children
@@ -2929,15 +3008,6 @@ export type paths = {
*/
post: operations['notes___delete'];
};
- '/notes/edit': {
- /**
- * notes/edit
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:notes*
- */
- post: operations['notes___edit'];
- };
'/notes/favorites/create': {
/**
* notes/favorites/create
@@ -2972,22 +3042,6 @@ export type paths = {
*/
post: operations['notes___featured'];
};
- '/notes/following': {
- /**
- * notes/following
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:account*
- */
- get: operations['notes___following'];
- /**
- * notes/following
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:account*
- */
- post: operations['notes___following'];
- };
'/notes/global-timeline': {
/**
* notes/global-timeline
@@ -3006,15 +3060,6 @@ export type paths = {
*/
post: operations['notes___hybrid-timeline'];
};
- '/notes/like': {
- /**
- * notes/like
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:reactions*
- */
- post: operations['notes___like'];
- };
'/notes/local-timeline': {
/**
* notes/local-timeline
@@ -3042,15 +3087,6 @@ export type paths = {
*/
post: operations['notes___polls___recommendation'];
};
- '/notes/polls/refresh': {
- /**
- * notes/polls/refresh
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:federation*
- */
- post: operations['notes___polls___refresh'];
- };
'/notes/polls/vote': {
/**
* notes/polls/vote
@@ -3112,33 +3148,6 @@ export type paths = {
*/
post: operations['notes___replies'];
};
- '/notes/schedule/create': {
- /**
- * notes/schedule/create
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
- */
- post: operations['notes___schedule___create'];
- };
- '/notes/schedule/delete': {
- /**
- * notes/schedule/delete
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
- */
- post: operations['notes___schedule___delete'];
- };
- '/notes/schedule/list': {
- /**
- * notes/schedule/list
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:notes-schedule*
- */
- post: operations['notes___schedule___list'];
- };
'/notes/search': {
/**
* notes/search
@@ -3229,15 +3238,6 @@ export type paths = {
*/
post: operations['notes___user-list-timeline'];
};
- '/notes/versions': {
- /**
- * notes/versions
- * @description No description provided.
- *
- * **Credential required**: *No*
- */
- post: operations['notes___versions'];
- };
'/notifications/create': {
/**
* notifications/create
@@ -3559,15 +3559,6 @@ export type paths = {
*/
post: operations['server-info'];
};
- '/sponsors': {
- /**
- * sponsors
- * @description Get Sharkey Sponsors or Instance Sponsors
- *
- * **Credential required**: *No*
- */
- post: operations['sponsors'];
- };
'/stats': {
/**
* stats
@@ -3976,22 +3967,9 @@ export type components = {
url: string;
offsetX?: number;
offsetY?: number;
- showBelow?: boolean;
}[];
- /** @default false */
- isAdmin?: boolean;
- /** @default false */
- isModerator?: boolean;
- /** @default false */
- isSystem?: boolean;
- noindex: boolean;
- enableRss: boolean;
- mandatoryCW: string | null;
- rejectQuotes?: boolean;
isBot?: boolean;
isCat?: boolean;
- speakAsCat?: boolean;
- isSilenced: boolean;
requireSigninToViewContents?: boolean;
makeNotesFollowersOnlyBefore?: number | null;
makeNotesHiddenBefore?: number | null;
@@ -4031,9 +4009,6 @@ export type components = {
/** Format: url */
bannerUrl: string | null;
bannerBlurhash: string | null;
- /** Format: url */
- backgroundUrl: string | null;
- backgroundBlurhash: string | null;
isLocked: boolean;
isSilenced: boolean;
/** @example false */
@@ -4043,8 +4018,6 @@ export type components = {
location: string | null;
/** @example 2018-03-12 */
birthday: string | null;
- /** @example Steve */
- listenbrainz: string | null;
/** @example ja-JP */
lang: string | null;
fields: {
@@ -4064,6 +4037,8 @@ export type components = {
followingVisibility: 'public' | 'followers' | 'private';
/** @enum {string} */
followersVisibility: 'public' | 'followers' | 'private';
+ /** @enum {string} */
+ chatScope: 'everyone' | 'following' | 'followers' | 'mutual' | 'none';
roles: components['schemas']['RoleLite'][];
followedMessage?: string | null;
memo: string | null;
@@ -4088,15 +4063,12 @@ export type components = {
avatarId: string | null;
/** Format: id */
bannerId: string | null;
- /** Format: id */
- backgroundId: string | null;
followedMessage: string | null;
isModerator: boolean | null;
isAdmin: boolean | null;
injectFeaturedNote: boolean;
receiveAnnouncementEmail: boolean;
alwaysMarkNsfw: boolean;
- defaultSensitive: boolean;
autoSensitive: boolean;
carefulBot: boolean;
autoAcceptFollowed: boolean;
@@ -4113,9 +4085,9 @@ export type components = {
unreadAnnouncements: components['schemas']['Announcement'][];
hasUnreadAntenna: boolean;
hasUnreadChannel: boolean;
+ hasUnreadChatMessages: boolean;
hasUnreadNotification: boolean;
hasPendingReceivedFollowRequest: boolean;
- hasPendingSentFollowRequest: boolean;
unreadNotificationsCount: number;
mutedWords: string[][];
hardMutedWords: string[][];
@@ -4273,11 +4245,6 @@ export type components = {
/** Format: date-time */
lastUsed: string;
}[];
- defaultCW: string | null;
- /** @enum {string} */
- defaultCWPriority: 'default' | 'parent' | 'defaultParent' | 'parentDefault';
- /** @enum {string} */
- allowUnsignedFetch: 'never' | 'always' | 'essential' | 'staff';
};
UserDetailedNotMe: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'];
MeDetailed: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'] & components['schemas']['MeDetailedOnly'];
@@ -4352,8 +4319,6 @@ export type components = {
/** Format: date-time */
createdAt: string;
/** Format: date-time */
- updatedAt?: string;
- /** Format: date-time */
deletedAt?: string | null;
text: string | null;
cw?: string | null;
@@ -4422,7 +4387,6 @@ export type components = {
url?: string;
reactionAndUserPairCache?: string[];
clippedCount?: number;
- processErrors?: string[] | null;
myReaction?: string | null;
};
NoteReaction: {
@@ -4592,47 +4556,24 @@ export type components = {
createdAt: string;
/** @enum {string} */
type: 'login';
- } | ({
- /** Format: id */
- id: string;
- /** Format: date-time */
- createdAt: string;
- /** @enum {string} */
- type: 'app';
- body: string;
- header: string | null;
- icon: string | null;
- }) | {
- /** Format: id */
- id: string;
- /** Format: date-time */
- createdAt: string;
- /** @enum {string} */
- type: 'edited';
- user: components['schemas']['UserLite'];
- /** Format: id */
- userId: string;
- note: components['schemas']['Note'];
} | {
/** Format: id */
id: string;
/** Format: date-time */
createdAt: string;
/** @enum {string} */
- type: 'scheduledNoteFailed';
- reason: string;
- } | {
+ type: 'createToken';
+ } | ({
/** Format: id */
id: string;
/** Format: date-time */
createdAt: string;
/** @enum {string} */
- type: 'scheduledNotePosted';
- user: components['schemas']['UserLite'];
- /** Format: id */
- userId: string;
- note: components['schemas']['Note'];
- } | {
+ type: 'app';
+ body: string;
+ header: string | null;
+ icon: string | null;
+ }) | {
/** Format: id */
id: string;
/** Format: date-time */
@@ -4978,9 +4919,6 @@ export type components = {
infoUpdatedAt: string | null;
/** Format: date-time */
latestRequestReceivedAt: string | null;
- isNSFW: boolean;
- rejectReports: boolean;
- rejectQuotes: boolean;
moderationNote?: string | null;
};
GalleryPost: {
@@ -5172,7 +5110,6 @@ export type components = {
RolePolicies: {
gtlAvailable: boolean;
ltlAvailable: boolean;
- btlAvailable: boolean;
canPublicNote: boolean;
mentionLimit: number;
canInvite: boolean;
@@ -5202,7 +5139,7 @@ export type components = {
canImportFollowing: boolean;
canImportMuting: boolean;
canImportUserLists: boolean;
- scheduleNoteMax: number;
+ canChat: boolean;
};
ReversiGameLite: {
/** Format: id */
@@ -5293,13 +5230,10 @@ export type components = {
repositoryUrl: string | null;
/** @default https://github.com/misskey-dev/misskey/issues/new */
feedbackUrl: string | null;
- donationUrl: string | null;
defaultDarkTheme: string | null;
defaultLightTheme: string | null;
- defaultLike: string | null;
disableRegistration: boolean;
emailRequiredForSignup: boolean;
- approvalRequiredForSignup: boolean;
enableHcaptcha: boolean;
hcaptchaSiteKey: string | null;
enableMcaptcha: boolean;
@@ -5309,11 +5243,8 @@ export type components = {
recaptchaSiteKey: string | null;
enableTurnstile: boolean;
turnstileSiteKey: string | null;
- enableFC: boolean;
- fcSiteKey: string | null;
- enableAchievements: boolean | null;
- robotsTxt: string | null;
enableTestcaptcha: boolean;
+ googleAnalyticsMeasurementId: string | null;
swPublickey: string | null;
/** @default /assets/ai.png */
mascotImageUrl: string;
@@ -5322,13 +5253,7 @@ export type components = {
infoImageUrl: string | null;
notFoundImageUrl: string | null;
iconUrl: string | null;
- sidebarLogoUrl: string | null;
maxNoteTextLength: number;
- maxRemoteNoteTextLength: number;
- maxCwLength: number;
- maxRemoteCwLength: number;
- maxAltTextLength: number;
- maxRemoteAltTextLength: number;
ads: {
/**
* Format: id
@@ -5363,7 +5288,6 @@ export type components = {
* @enum {string}
*/
noteSearchableScope: 'local' | 'global';
- trustedLinkUrlPatterns: string[];
maxFileSize: number;
/** @enum {string} */
federation: 'all' | 'specified' | 'none';
@@ -5387,8 +5311,6 @@ export type components = {
requireSetup: boolean;
cacheRemoteFiles: boolean;
cacheRemoteSensitiveFiles: boolean;
- /** @enum {string} */
- allowUnsignedFetch: 'never' | 'always' | 'essential';
};
MetaDetailed: components['schemas']['MetaLite'] & components['schemas']['MetaDetailedOnly'];
SystemWebhook: {
@@ -5417,6 +5339,69 @@ export type components = {
systemWebhookId?: string;
systemWebhook?: components['schemas']['SystemWebhook'];
};
+ ChatMessage: {
+ id: string;
+ /** Format: date-time */
+ createdAt: string;
+ fromUserId: string;
+ fromUser: components['schemas']['UserLite'];
+ toUserId?: string | null;
+ toUser?: components['schemas']['UserLite'] | null;
+ toRoomId?: string | null;
+ toRoom?: components['schemas']['ChatRoom'] | null;
+ text?: string | null;
+ fileId?: string | null;
+ file?: components['schemas']['DriveFile'] | null;
+ isRead?: boolean;
+ reactions: ({
+ reaction: string;
+ user?: components['schemas']['UserLite'] | null;
+ })[];
+ };
+ ChatMessageLite: {
+ id: string;
+ /** Format: date-time */
+ createdAt: string;
+ fromUserId: string;
+ fromUser?: components['schemas']['UserLite'];
+ toUserId?: string | null;
+ toRoomId?: string | null;
+ text?: string | null;
+ fileId?: string | null;
+ file?: components['schemas']['DriveFile'] | null;
+ reactions: ({
+ reaction: string;
+ user?: components['schemas']['UserLite'] | null;
+ })[];
+ };
+ ChatRoom: {
+ id: string;
+ /** Format: date-time */
+ createdAt: string;
+ ownerId: string;
+ owner: components['schemas']['UserLite'];
+ name: string;
+ description: string;
+ isMuted?: boolean;
+ };
+ ChatRoomInvitation: {
+ id: string;
+ /** Format: date-time */
+ createdAt: string;
+ userId: string;
+ user: components['schemas']['UserLite'];
+ roomId: string;
+ room: components['schemas']['ChatRoom'];
+ };
+ ChatRoomMembership: {
+ id: string;
+ /** Format: date-time */
+ createdAt: string;
+ userId: string;
+ user?: components['schemas']['UserLite'];
+ roomId: string;
+ room?: components['schemas']['ChatRoom'];
+ };
};
responses: never;
parameters: never;
@@ -6489,58 +6474,6 @@ export type operations = {
};
};
/**
- * admin/approve-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:approve-user*
- */
- 'admin___approve-user': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- userId: 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/avatar-decorations/create
* @description No description provided.
*
@@ -6802,7 +6735,7 @@ export type operations = {
content: {
'application/json': {
/** @enum {string} */
- provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'fc' | 'testcaptcha';
+ provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'testcaptcha';
hcaptcha: {
siteKey: string | null;
secretKey: string | null;
@@ -6820,10 +6753,6 @@ export type operations = {
siteKey: string | null;
secretKey: string | null;
};
- fc: {
- siteKey: string | null;
- secretKey: string | null;
- };
};
};
};
@@ -6870,7 +6799,7 @@ export type operations = {
content: {
'application/json': {
/** @enum {string} */
- provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'fc' | 'testcaptcha';
+ provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'testcaptcha';
captchaResult?: string | null;
sitekey?: string | null;
secret?: string | null;
@@ -6916,111 +6845,6 @@ export type operations = {
};
};
/**
- * admin/cw-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:cw-user*
- */
- 'admin___cw-user': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- userId: string;
- cw: string | 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/decline-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:decline-user*
- */
- 'admin___decline-user': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- userId: 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/delete-account
* @description No description provided.
*
@@ -7733,8 +7557,6 @@ export type operations = {
query?: string | null;
/** @default 10 */
limit?: number;
- /** @default null */
- offset?: number | null;
/** Format: misskey:id */
sinceId?: string;
/** Format: misskey:id */
@@ -8301,10 +8123,7 @@ export type operations = {
'application/json': {
host: string;
isSuspended?: boolean;
- isNSFW?: boolean;
- rejectReports?: boolean;
moderationNote?: string;
- rejectQuotes?: boolean;
};
};
};
@@ -8398,50 +8217,6 @@ export type operations = {
};
};
/**
- * admin/gen-vapid-keys
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:meta*
- */
- 'admin___gen-vapid-keys': {
- 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/get-index-stats
* @description No description provided.
*
@@ -8732,7 +8507,6 @@ export type operations = {
cacheRemoteFiles: boolean;
cacheRemoteSensitiveFiles: boolean;
emailRequiredForSignup: boolean;
- approvalRequiredForSignup: boolean;
enableHcaptcha: boolean;
hcaptchaSiteKey: string | null;
enableMcaptcha: boolean;
@@ -8742,9 +8516,8 @@ export type operations = {
recaptchaSiteKey: string | null;
enableTurnstile: boolean;
turnstileSiteKey: string | null;
- enableFC: boolean;
- fcSiteKey: string | null;
enableTestcaptcha: boolean;
+ googleAnalyticsMeasurementId: string | null;
swPublickey: string | null;
/** @default /assets/ai.png */
mascotImageUrl: string | null;
@@ -8755,11 +8528,10 @@ export type operations = {
iconUrl: string | null;
app192IconUrl: string | null;
app512IconUrl: string | null;
- sidebarLogoUrl: string | null;
enableEmail: boolean;
enableServiceWorker: boolean;
translatorAvailable: boolean;
- silencedHosts: string[];
+ silencedHosts?: string[];
mediaSilencedHosts: string[];
pinnedUsers: string[];
hiddenTags: string[];
@@ -8769,19 +8541,16 @@ export type operations = {
prohibitedWordsForNameOfUser: string[];
bannedEmailDomains?: string[];
preservedUsernames: string[];
- bubbleInstances: string[];
hcaptchaSecretKey: string | null;
mcaptchaSecretKey: string | null;
recaptchaSecretKey: string | null;
turnstileSecretKey: string | null;
- fcSecretKey: string | null;
sensitiveMediaDetection: string;
sensitiveMediaDetectionSensitivity: string;
setSensitiveFlagAutomatically: boolean;
enableSensitiveMediaDetectionForVideos: boolean;
- enableBotTrending: boolean;
/** Format: id */
- proxyAccountId: string | null;
+ proxyAccountId: string;
email: string | null;
smtpSecure: boolean;
smtpHost: string | null;
@@ -8812,8 +8581,6 @@ export type operations = {
enableChartsForFederatedInstances: boolean;
enableStatsForFederatedInstances: boolean;
enableServerMachineStats: boolean;
- enableAchievements: boolean;
- robotsTxt: string | null;
enableIdenticonGeneration: boolean;
manifestJsonOverride: string;
policies: Record<string, never>;
@@ -8828,16 +8595,11 @@ export type operations = {
backgroundImageUrl: string | null;
deeplAuthKey: string | null;
deeplIsPro: boolean;
- deeplFreeMode: boolean;
- deeplFreeInstance: string | null;
- libreTranslateURL: string | null;
- libreTranslateKey: string | null;
defaultDarkTheme: string | null;
defaultLightTheme: string | null;
description: string | null;
disableRegistration: boolean;
impressumUrl: string | null;
- donationUrl: string | null;
maintainerEmail: string | null;
maintainerName: string | null;
name: string | null;
@@ -8861,12 +8623,9 @@ export type operations = {
urlPreviewRequireContentLength: boolean;
urlPreviewUserAgent: string | null;
urlPreviewSummaryProxyUrl: string | null;
- trustedLinkUrlPatterns: string[];
- federation: string;
- federationHosts: string[];
- hasLegacyAuthFetchSetting: boolean;
/** @enum {string} */
- allowUnsignedFetch: 'never' | 'always' | 'essential';
+ federation: 'all' | 'specified' | 'none';
+ federationHosts: string[];
};
};
};
@@ -8903,58 +8662,6 @@ export type operations = {
};
};
/**
- * admin/nsfw-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user*
- */
- 'admin___nsfw-user': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- userId: 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/promo/create
* @description No description provided.
*
@@ -9247,59 +8954,6 @@ export type operations = {
};
};
/**
- * admin/reject-quotes
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:reject-quotes*
- */
- 'admin___reject-quotes': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- userId: string;
- rejectQuotes: boolean;
- };
- };
- };
- 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/relays/add
* @description No description provided.
*
@@ -10310,7 +9964,6 @@ export type operations = {
'application/json': {
email: string | null;
emailVerified: boolean;
- approved: boolean;
followedMessage: string | null;
autoAcceptFollowed: boolean;
noCrawle: boolean;
@@ -10451,7 +10104,6 @@ export type operations = {
}]>;
};
isModerator: boolean;
- isSystem: boolean;
isSilenced: boolean;
isSuspended: boolean;
isHibernated: boolean;
@@ -10520,7 +10172,7 @@ export type operations = {
* @default all
* @enum {string}
*/
- state?: 'all' | 'alive' | 'available' | 'admin' | 'moderator' | 'adminOrModerator' | 'suspended' | 'approved';
+ state?: 'all' | 'alive' | 'available' | 'admin' | 'moderator' | 'adminOrModerator' | 'suspended';
/**
* @default combined
* @enum {string}
@@ -10576,58 +10228,6 @@ export type operations = {
};
};
/**
- * admin/silence-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:silence-user*
- */
- 'admin___silence-user': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- userId: 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/suspend-user
* @description No description provided.
*
@@ -11026,58 +10626,6 @@ export type operations = {
};
};
/**
- * admin/unnsfw-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user*
- */
- 'admin___unnsfw-user': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- userId: 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/unset-user-avatar
* @description No description provided.
*
@@ -11182,58 +10730,6 @@ export type operations = {
};
};
/**
- * admin/unsilence-user
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user*
- */
- 'admin___unsilence-user': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- userId: 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/unsuspend-user
* @description No description provided.
*
@@ -11364,7 +10860,6 @@ export type operations = {
iconUrl?: string | null;
app192IconUrl?: string | null;
app512IconUrl?: string | null;
- sidebarLogoUrl?: string | null;
backgroundImageUrl?: string | null;
logoImageUrl?: string | null;
name?: string | null;
@@ -11372,11 +10867,9 @@ export type operations = {
description?: string | null;
defaultLightTheme?: string | null;
defaultDarkTheme?: string | null;
- defaultLike?: string | null;
cacheRemoteFiles?: boolean;
cacheRemoteSensitiveFiles?: boolean;
emailRequiredForSignup?: boolean;
- approvalRequiredForSignup?: boolean;
enableHcaptcha?: boolean;
hcaptchaSiteKey?: string | null;
hcaptchaSecretKey?: string | null;
@@ -11390,28 +10883,19 @@ export type operations = {
enableTurnstile?: boolean;
turnstileSiteKey?: string | null;
turnstileSecretKey?: string | null;
- enableFC?: boolean;
- fcSiteKey?: string | null;
- fcSecretKey?: string | null;
enableTestcaptcha?: boolean;
+ googleAnalyticsMeasurementId?: string | null;
/** @enum {string} */
sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote';
/** @enum {string} */
sensitiveMediaDetectionSensitivity?: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh';
setSensitiveFlagAutomatically?: boolean;
enableSensitiveMediaDetectionForVideos?: boolean;
- enableBotTrending?: boolean;
- /** Format: misskey:id */
- proxyAccountId?: string | null;
maintainerName?: string | null;
maintainerEmail?: string | null;
langs?: string[];
deeplAuthKey?: string | null;
deeplIsPro?: boolean;
- deeplFreeMode?: boolean;
- deeplFreeInstance?: string | null;
- libreTranslateURL?: string | null;
- libreTranslateKey?: string | null;
enableEmail?: boolean;
email?: string | null;
smtpSecure?: boolean;
@@ -11426,7 +10910,6 @@ export type operations = {
repositoryUrl?: string | null;
feedbackUrl?: string | null;
impressumUrl?: string | null;
- donationUrl?: string | null;
privacyPolicyUrl?: string | null;
inquiryUrl?: string | null;
useObjectStorage?: boolean;
@@ -11453,13 +10936,10 @@ export type operations = {
enableChartsForFederatedInstances?: boolean;
enableStatsForFederatedInstances?: boolean;
enableServerMachineStats?: boolean;
- enableAchievements?: boolean;
- robotsTxt?: string | null;
enableIdenticonGeneration?: boolean;
serverRules?: string[];
bannedEmailDomains?: string[];
preservedUsernames?: string[];
- bubbleInstances?: string[];
manifestJsonOverride?: string;
enableFanoutTimeline?: boolean;
enableFanoutTimelineDbFallback?: boolean;
@@ -11479,12 +10959,9 @@ export type operations = {
urlPreviewRequireContentLength?: boolean;
urlPreviewUserAgent?: string | null;
urlPreviewSummaryProxyUrl?: string | null;
- trustedLinkUrlPatterns?: string[] | null;
/** @enum {string} */
federation?: 'all' | 'none' | 'specified';
federationHosts?: string[];
- /** @enum {string} */
- allowUnsignedFetch?: 'never' | 'always' | 'essential';
};
};
};
@@ -11526,6 +11003,59 @@ export type operations = {
};
};
/**
+ * admin/update-proxy-account
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:account*
+ */
+ 'admin___update-proxy-account': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ description?: string | null;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['UserDetailed'];
+ };
+ };
+ /** @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-user-note
* @description No description provided.
*
@@ -11630,12 +11160,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -11690,12 +11214,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -11761,12 +11279,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -11819,12 +11331,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -11871,12 +11377,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -11939,12 +11439,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -11999,12 +11493,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12072,12 +11560,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12260,12 +11742,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12320,12 +11796,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12378,12 +11848,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12441,12 +11905,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12505,12 +11963,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12568,12 +12020,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12752,12 +12198,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -12816,12 +12256,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13000,12 +12434,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13052,12 +12480,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13110,12 +12532,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13174,12 +12590,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13226,12 +12636,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13290,12 +12694,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13360,12 +12758,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13420,12 +12812,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13456,13 +12842,6 @@ export type operations = {
untilDate?: number;
/** @default false */
allowPartial?: boolean;
- /** @default true */
- withRenotes?: boolean;
- /**
- * @description Only show notes that have attached files.
- * @default false
- */
- withFiles?: boolean;
};
};
};
@@ -13497,12 +12876,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13555,12 +12928,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13613,12 +12980,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13682,12 +13043,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13756,12 +13111,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13824,12 +13173,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13901,12 +13244,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -13974,12 +13311,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14078,12 +13409,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14165,12 +13490,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14238,12 +13557,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14329,12 +13642,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14405,12 +13712,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14480,12 +13781,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14553,12 +13848,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14628,6 +13917,700 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/history
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ chat___history: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @default 10 */
+ limit?: number;
+ /** @default false */
+ room?: boolean;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatMessage'][];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/messages/create-to-room
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ 'chat___messages___create-to-room': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ text?: string | null;
+ /** Format: misskey:id */
+ fileId?: string;
+ /** Format: misskey:id */
+ toRoomId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatMessageLite'];
+ };
+ };
+ /** @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 Too many requests */
+ 429: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/messages/create-to-user
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ 'chat___messages___create-to-user': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ text?: string | null;
+ /** Format: misskey:id */
+ fileId?: string;
+ /** Format: misskey:id */
+ toUserId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatMessageLite'];
+ };
+ };
+ /** @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 Too many requests */
+ 429: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/messages/delete
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___messages___delete: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ messageId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': unknown;
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/messages/react
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___messages___react: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ messageId: string;
+ reaction: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': unknown;
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/messages/room-timeline
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ 'chat___messages___room-timeline': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @default 10 */
+ limit?: number;
+ /** Format: misskey:id */
+ sinceId?: string;
+ /** Format: misskey:id */
+ untilId?: string;
+ /** Format: misskey:id */
+ roomId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatMessageLite'][];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/messages/search
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ chat___messages___search: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ query: string;
+ /** @default 10 */
+ limit?: number;
+ /** Format: misskey:id */
+ userId?: string | null;
+ /** Format: misskey:id */
+ roomId?: string | null;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatMessage'][];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/messages/show
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ chat___messages___show: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ messageId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatMessage'];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/messages/user-timeline
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ 'chat___messages___user-timeline': {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @default 10 */
+ limit?: number;
+ /** Format: misskey:id */
+ sinceId?: string;
+ /** Format: misskey:id */
+ untilId?: string;
+ /** Format: misskey:id */
+ userId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatMessageLite'][];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/create
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___create: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ name: string;
+ description?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatRoom'];
+ };
+ };
+ /** @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 Too many requests */
+ 429: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ content: {
+ 'application/json': components['schemas']['Error'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/delete
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___delete: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': unknown;
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/invitations/create
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___invitations___create: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ /** Format: misskey:id */
+ userId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatRoomInvitation'];
+ };
+ };
+ /** @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 Too many requests */
429: {
content: {
@@ -14643,6 +14626,567 @@ export type operations = {
};
};
/**
+ * chat/rooms/invitations/ignore
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___invitations___ignore: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': unknown;
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/invitations/inbox
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ chat___rooms___invitations___inbox: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @default 30 */
+ limit?: number;
+ /** Format: misskey:id */
+ sinceId?: string;
+ /** Format: misskey:id */
+ untilId?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatRoomInvitation'][];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/join
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___join: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': unknown;
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/joining
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ chat___rooms___joining: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @default 30 */
+ limit?: number;
+ /** Format: misskey:id */
+ sinceId?: string;
+ /** Format: misskey:id */
+ untilId?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatRoomMembership'][];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/leave
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___leave: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': unknown;
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/members
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___members: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ /** @default 30 */
+ limit?: number;
+ /** Format: misskey:id */
+ sinceId?: string;
+ /** Format: misskey:id */
+ untilId?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatRoomMembership'][];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/mute
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___mute: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ mute: boolean;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': unknown;
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/owned
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ chat___rooms___owned: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** @default 30 */
+ limit?: number;
+ /** Format: misskey:id */
+ sinceId?: string;
+ /** Format: misskey:id */
+ untilId?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatRoom'][];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/show
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:chat*
+ */
+ chat___rooms___show: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatRoom'];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
+ * chat/rooms/update
+ * @description No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:chat*
+ */
+ chat___rooms___update: {
+ requestBody: {
+ content: {
+ 'application/json': {
+ /** Format: misskey:id */
+ roomId: string;
+ name?: string;
+ description?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['ChatRoom'];
+ };
+ };
+ /** @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'];
+ };
+ };
+ };
+ };
+ /**
* clips/add-note
* @description No description provided.
*
@@ -14750,12 +15294,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14808,12 +15346,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14866,12 +15398,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14918,12 +15444,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -14970,12 +15490,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15036,12 +15550,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15096,12 +15604,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15156,12 +15658,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15214,12 +15710,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15277,12 +15767,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15332,12 +15816,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15370,8 +15848,6 @@ export type operations = {
type?: string | null;
/** @enum {string|null} */
sort?: '+createdAt' | '-createdAt' | '+name' | '-name' | '+size' | '-size' | null;
- /** @default */
- searchQuery?: string;
};
};
};
@@ -15406,12 +15882,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15472,12 +15942,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15531,12 +15995,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15665,12 +16123,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15729,12 +16181,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15788,12 +16234,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15849,12 +16289,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -15914,12 +16348,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16019,8 +16447,6 @@ export type operations = {
* @default null
*/
folderId?: string | null;
- /** @default */
- searchQuery?: string;
};
};
};
@@ -16055,12 +16481,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16175,12 +16595,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16239,12 +16653,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16299,12 +16707,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16362,12 +16764,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16427,12 +16823,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16489,12 +16879,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16548,12 +16932,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16602,12 +16980,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16670,12 +17042,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16722,12 +17088,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16791,7 +17151,7 @@ export type operations = {
* federation/followers
* @description No description provided.
*
- * **Credential required**: *Yes* / **Permission**: *read:account*
+ * **Credential required**: *No*
*/
federation___followers: {
requestBody: {
@@ -16804,10 +17164,6 @@ export type operations = {
untilId?: string;
/** @default 10 */
limit?: number;
- /** @default false */
- includeFollower?: boolean;
- /** @default true */
- includeFollowee?: boolean;
};
};
};
@@ -16842,12 +17198,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16860,7 +17210,7 @@ export type operations = {
* federation/following
* @description No description provided.
*
- * **Credential required**: *Yes* / **Permission**: *read:account*
+ * **Credential required**: *No*
*/
federation___following: {
requestBody: {
@@ -16873,10 +17223,6 @@ export type operations = {
untilId?: string;
/** @default 10 */
limit?: number;
- /** @default false */
- includeFollower?: boolean;
- /** @default true */
- includeFollowee?: boolean;
};
};
};
@@ -16911,12 +17257,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -16944,8 +17284,6 @@ export type operations = {
federating?: boolean | null;
subscribing?: boolean | null;
publishing?: boolean | null;
- nsfw?: boolean | null;
- bubble?: boolean | null;
/** @default 30 */
limit?: number;
/** @default 0 */
@@ -16986,12 +17324,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17049,12 +17381,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17114,12 +17440,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17172,12 +17492,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17237,12 +17551,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17407,12 +17715,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17532,12 +17834,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17594,12 +17890,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17652,12 +17942,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17716,12 +18000,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17784,12 +18062,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17844,12 +18116,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -17902,12 +18168,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18205,12 +18465,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18265,12 +18519,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18334,12 +18582,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18392,12 +18634,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18461,12 +18697,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18645,12 +18875,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18697,12 +18921,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18761,12 +18979,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18882,12 +19094,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -18940,12 +19146,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19000,12 +19200,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19058,12 +19252,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19185,12 +19373,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19239,12 +19421,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19307,12 +19483,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19370,12 +19540,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19429,12 +19593,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19485,12 +19643,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19558,12 +19710,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19610,12 +19756,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19672,12 +19812,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19738,12 +19872,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19796,12 +19924,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19863,12 +19985,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -19958,12 +20074,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20018,12 +20128,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20077,12 +20181,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20136,12 +20234,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20206,12 +20298,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20281,12 +20367,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20341,12 +20421,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20399,12 +20473,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20458,12 +20526,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -20626,57 +20688,6 @@ export type operations = {
};
};
/**
- * i/export-data
- * @description No description provided.
- *
- * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
- * **Credential required**: *Yes*
- */
- 'i___export-data': {
- 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
* i/export-favorites
* @description No description provided.
*
@@ -20991,12 +21002,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -21059,12 +21064,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -21123,12 +21122,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -21375,66 +21368,6 @@ export type operations = {
};
};
/**
- * i/import-notes
- * @description No description provided.
- *
- * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
- * **Credential required**: *Yes*
- */
- 'i___import-notes': {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- fileId: string;
- type?: string | 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
* i/import-user-lists
* @description No description provided.
*
@@ -21571,8 +21504,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' | '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')[];
+ includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+ excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
};
};
};
@@ -21639,8 +21572,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' | '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')[];
+ includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
+ excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'createToken' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
};
};
};
@@ -21743,12 +21676,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -21807,12 +21734,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -21867,62 +21788,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * i/read-all-unread-notes
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:account*
- */
- 'i___read-all-unread-notes': {
- 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -21975,12 +21840,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22033,12 +21892,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22095,12 +21948,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22156,12 +22003,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22221,71 +22062,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * i/registry/get-unsecure
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:account*
- */
- 'i___registry___get-unsecure': {
- requestBody: {
- content: {
- 'application/json': {
- key: string;
- /** @default [] */
- scope?: 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22341,12 +22117,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22404,12 +22174,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22464,12 +22228,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22520,12 +22278,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22581,12 +22333,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22641,12 +22387,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22706,12 +22446,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22766,12 +22500,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -22795,7 +22523,6 @@ export type operations = {
followedMessage?: string | null;
location?: string | null;
birthday?: string | null;
- listenbrainz?: string | null;
/** @enum {string|null} */
lang?: null | 'ach' | 'ady' | 'af' | 'af-NA' | 'af-ZA' | 'ak' | 'ar' | 'ar-AR' | 'ar-MA' | 'ar-SA' | 'ay-BO' | 'az' | 'az-AZ' | 'be-BY' | 'bg' | 'bg-BG' | 'bn' | 'bn-IN' | 'bn-BD' | 'br' | 'bs-BA' | 'ca' | 'ca-ES' | 'cak' | 'ck-US' | 'cs' | 'cs-CZ' | 'cy' | 'cy-GB' | 'da' | 'da-DK' | 'de' | 'de-AT' | 'de-DE' | 'de-CH' | 'dsb' | 'el' | 'el-GR' | 'en' | 'en-GB' | 'en-AU' | 'en-CA' | 'en-IE' | 'en-IN' | 'en-PI' | 'en-SG' | 'en-UD' | 'en-US' | 'en-ZA' | 'en@pirate' | 'eo' | 'eo-EO' | 'es' | 'es-AR' | 'es-419' | 'es-CL' | 'es-CO' | 'es-EC' | 'es-ES' | 'es-LA' | 'es-NI' | 'es-MX' | 'es-US' | 'es-VE' | 'et' | 'et-EE' | 'eu' | 'eu-ES' | 'fa' | 'fa-IR' | 'fb-LT' | 'ff' | 'fi' | 'fi-FI' | 'fo' | 'fo-FO' | 'fr' | 'fr-CA' | 'fr-FR' | 'fr-BE' | 'fr-CH' | 'fy-NL' | 'ga' | 'ga-IE' | 'gd' | 'gl' | 'gl-ES' | 'gn-PY' | 'gu-IN' | 'gv' | 'gx-GR' | 'he' | 'he-IL' | 'hi' | 'hi-IN' | 'hr' | 'hr-HR' | 'hsb' | 'ht' | 'hu' | 'hu-HU' | 'hy' | 'hy-AM' | 'id' | 'id-ID' | 'is' | 'is-IS' | 'it' | 'it-IT' | 'ja' | 'ja-JP' | 'jv-ID' | 'ka-GE' | 'kk-KZ' | 'km' | 'kl' | 'km-KH' | 'kab' | 'kn' | 'kn-IN' | 'ko' | 'ko-KR' | 'ku-TR' | 'kw' | 'la' | 'la-VA' | 'lb' | 'li-NL' | 'lt' | 'lt-LT' | 'lv' | 'lv-LV' | 'mai' | 'mg-MG' | 'mk' | 'mk-MK' | 'ml' | 'ml-IN' | 'mn-MN' | 'mr' | 'mr-IN' | 'ms' | 'ms-MY' | 'mt' | 'mt-MT' | 'my' | 'no' | 'nb' | 'nb-NO' | 'ne' | 'ne-NP' | 'nl' | 'nl-BE' | 'nl-NL' | 'nn-NO' | 'oc' | 'or-IN' | 'pa' | 'pa-IN' | 'pl' | 'pl-PL' | 'ps-AF' | 'pt' | 'pt-BR' | 'pt-PT' | 'qu-PE' | 'rm-CH' | 'ro' | 'ro-RO' | 'ru' | 'ru-RU' | 'sa-IN' | 'se-NO' | 'sh' | 'si-LK' | 'sk' | 'sk-SK' | 'sl' | 'sl-SI' | 'so-SO' | 'sq' | 'sq-AL' | 'sr' | 'sr-RS' | 'su' | 'sv' | 'sv-SE' | 'sw' | 'sw-KE' | 'ta' | 'ta-IN' | 'te' | 'te-IN' | 'tg' | 'tg-TJ' | 'th' | 'th-TH' | 'fil' | 'tlh' | 'tr' | 'tr-TR' | 'tt-RU' | 'uk' | 'uk-UA' | 'ur' | 'ur-PK' | 'uz' | 'uz-UZ' | 'vi' | 'vi-VN' | 'xh-ZA' | 'yi' | 'yi-DE' | 'zh' | 'zh-Hans' | 'zh-Hant' | 'zh-CN' | 'zh-HK' | 'zh-SG' | 'zh-TW' | 'zu-ZA';
/** Format: misskey:id */
@@ -22807,12 +22534,9 @@ export type operations = {
flipH?: boolean | null;
offsetX?: number | null;
offsetY?: number | null;
- showBelow?: boolean | null;
})[];
/** Format: misskey:id */
bannerId?: string | null;
- /** Format: misskey:id */
- backgroundId?: string | null;
fields?: {
name: string;
value: string;
@@ -22825,23 +22549,21 @@ export type operations = {
autoAcceptFollowed?: boolean;
noCrawle?: boolean;
preventAiLearning?: boolean;
- noindex?: boolean;
requireSigninToViewContents?: boolean;
makeNotesFollowersOnlyBefore?: number | null;
makeNotesHiddenBefore?: number | null;
- enableRss?: boolean;
isBot?: boolean;
isCat?: boolean;
- speakAsCat?: boolean;
injectFeaturedNote?: boolean;
receiveAnnouncementEmail?: boolean;
alwaysMarkNsfw?: boolean;
- defaultSensitive?: boolean;
autoSensitive?: boolean;
/** @enum {string} */
followingVisibility?: 'public' | 'followers' | 'private';
/** @enum {string} */
followersVisibility?: 'public' | 'followers' | 'private';
+ /** @enum {string} */
+ chatScope?: 'everyone' | 'followers' | 'following' | 'mutual' | 'none';
/** Format: misskey:id */
pinnedPageId?: string | null;
mutedWords?: (string[] | string)[];
@@ -22977,11 +22699,6 @@ export type operations = {
};
emailNotificationTypes?: string[];
alsoKnownAs?: string[];
- defaultCW?: string | null;
- /** @enum {string} */
- defaultCWPriority?: 'default' | 'parent' | 'defaultParent' | 'parentDefault';
- /** @enum {string} */
- allowUnsignedFetch?: 'never' | 'always' | 'essential' | 'staff';
};
};
};
@@ -23106,7 +22823,7 @@ export type operations = {
url: string;
/** @default */
secret?: string;
- on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
+ on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
};
};
};
@@ -23120,7 +22837,7 @@ export type operations = {
/** Format: misskey:id */
userId: string;
name: string;
- on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
+ on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
url: string;
secret: string;
active: boolean;
@@ -23154,12 +22871,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23212,12 +22923,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23243,7 +22948,7 @@ export type operations = {
/** Format: misskey:id */
userId: string;
name: string;
- on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
+ on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
url: string;
secret: string;
active: boolean;
@@ -23277,12 +22982,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23316,7 +23015,7 @@ export type operations = {
/** Format: misskey:id */
userId: string;
name: string;
- on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
+ on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
url: string;
secret: string;
active: boolean;
@@ -23350,12 +23049,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23378,7 +23071,7 @@ export type operations = {
/** Format: misskey:id */
webhookId: string;
/** @enum {string} */
- type: 'mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited';
+ type: 'mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction';
override?: {
url?: string;
secret?: string;
@@ -23444,7 +23137,7 @@ export type operations = {
name?: string;
url?: string;
secret?: string | null;
- on?: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
+ on?: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
active?: boolean;
};
};
@@ -23478,12 +23171,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23530,12 +23217,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23588,12 +23269,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23642,12 +23317,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23706,12 +23375,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23766,12 +23429,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23832,12 +23489,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -23950,12 +23601,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24014,12 +23659,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24076,12 +23715,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24146,84 +23779,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * notes/bubble-timeline
- * @description No description provided.
- *
- * **Credential required**: *No*
- */
- 'notes___bubble-timeline': {
- requestBody: {
- content: {
- 'application/json': {
- /** @default false */
- withFiles?: boolean;
- /** @default true */
- withBots?: boolean;
- /** @default true */
- withRenotes?: boolean;
- /** @default 10 */
- limit?: number;
- /** Format: misskey:id */
- sinceId?: string;
- /** Format: misskey:id */
- untilId?: string;
- sinceDate?: number;
- untilDate?: number;
- };
- };
- };
- responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': components['schemas']['Note'][];
- };
- };
- /** @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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24250,8 +23805,6 @@ export type operations = {
sinceId?: string;
/** Format: misskey:id */
untilId?: string;
- /** @default true */
- showQuotes?: boolean;
};
};
};
@@ -24286,12 +23839,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24346,12 +23893,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24410,12 +23951,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24578,103 +24113,6 @@ export type operations = {
};
};
/**
- * notes/edit
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:notes*
- */
- notes___edit: {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- editId?: string;
- /**
- * @default public
- * @enum {string}
- */
- visibility?: 'public' | 'home' | 'followers' | 'specified';
- visibleUserIds?: string[];
- cw?: string | null;
- /** @default false */
- localOnly?: boolean;
- /**
- * @default null
- * @enum {string|null}
- */
- reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
- /** @default false */
- noExtractMentions?: boolean;
- /** @default false */
- noExtractHashtags?: boolean;
- /** @default false */
- noExtractEmojis?: boolean;
- /** Format: misskey:id */
- replyId?: string | null;
- /** Format: misskey:id */
- renoteId?: string | null;
- /** Format: misskey:id */
- channelId?: string | null;
- text?: string | null;
- fileIds?: string[];
- mediaIds?: string[];
- poll?: ({
- choices: string[];
- multiple?: boolean;
- expiresAt?: number | null;
- expiredAfter?: number | null;
- }) | null;
- };
- };
- };
- responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': {
- createdNote: components['schemas']['Note'];
- };
- };
- };
- /** @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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
* notes/favorites/create
* @description No description provided.
*
@@ -24776,12 +24214,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24840,93 +24272,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * notes/following
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:account*
- */
- notes___following: {
- requestBody: {
- content: {
- 'application/json': {
- /**
- * @default following
- * @enum {string}
- */
- list?: 'following' | 'followers' | 'mutuals';
- /** @default false */
- filesOnly?: boolean;
- /** @default false */
- includeNonPublic?: boolean;
- /** @default false */
- includeReplies?: boolean;
- /** @default false */
- includeQuotes?: boolean;
- /** @default true */
- includeBots?: boolean;
- /** @default 10 */
- limit?: number;
- /** Format: misskey:id */
- sinceId?: string;
- /** Format: misskey:id */
- untilId?: string;
- sinceDate?: number;
- untilDate?: number;
- };
- };
- };
- responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': components['schemas']['Note'][];
- };
- };
- /** @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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -24948,8 +24293,6 @@ export type operations = {
/** @default false */
withFiles?: boolean;
/** @default true */
- withBots?: boolean;
- /** @default true */
withRenotes?: boolean;
/** @default 10 */
limit?: number;
@@ -24993,12 +24336,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25039,8 +24376,6 @@ export type operations = {
withRenotes?: boolean;
/** @default false */
withReplies?: boolean;
- /** @default true */
- withBots?: boolean;
};
};
};
@@ -25075,71 +24410,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * notes/like
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:reactions*
- */
- notes___like: {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- noteId: string;
- override?: string | 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25164,8 +24434,6 @@ export type operations = {
withRenotes?: boolean;
/** @default false */
withReplies?: boolean;
- /** @default true */
- withBots?: boolean;
/** @default 10 */
limit?: number;
/** Format: misskey:id */
@@ -25210,12 +24478,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25277,12 +24539,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25341,70 +24597,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * notes/polls/refresh
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:federation*
- */
- notes___polls___refresh: {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- noteId: 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25458,12 +24650,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25525,12 +24711,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25584,12 +24764,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25668,16 +24842,12 @@ export type operations = {
'application/json': {
/** Format: misskey:id */
noteId: string;
- /** Format: misskey:id */
- userId?: string;
/** @default 10 */
limit?: number;
/** Format: misskey:id */
sinceId?: string;
/** Format: misskey:id */
untilId?: string;
- /** @default false */
- quote?: boolean;
};
};
};
@@ -25712,12 +24882,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -25778,245 +24942,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * notes/schedule/create
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
- */
- notes___schedule___create: {
- requestBody: {
- content: {
- 'application/json': {
- /**
- * @default public
- * @enum {string}
- */
- visibility?: 'public' | 'home' | 'followers' | 'specified';
- visibleUserIds?: string[];
- cw?: string | null;
- /**
- * @default null
- * @enum {string|null}
- */
- reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
- /** @default false */
- noExtractMentions?: boolean;
- /** @default false */
- noExtractHashtags?: boolean;
- /** @default false */
- noExtractEmojis?: boolean;
- /** Format: misskey:id */
- replyId?: string | null;
- /** Format: misskey:id */
- renoteId?: string | null;
- text?: string | null;
- fileIds?: string[];
- mediaIds?: string[];
- poll?: ({
- choices: string[];
- multiple?: boolean;
- expiresAt?: number | null;
- expiredAfter?: number | null;
- }) | null;
- scheduleNote: {
- scheduledAt?: number;
- };
- };
- };
- };
- 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * notes/schedule/delete
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
- */
- notes___schedule___delete: {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- noteId: 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * notes/schedule/list
- * @description No description provided.
- *
- * **Credential required**: *Yes* / **Permission**: *read:notes-schedule*
- */
- notes___schedule___list: {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- sinceId?: string;
- /** Format: misskey:id */
- untilId?: string;
- /** @default 10 */
- limit?: number;
- };
- };
- };
- responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': ({
- /** Format: misskey:id */
- id: string;
- note: {
- createdAt: string;
- text?: string;
- cw?: string | null;
- fileIds: string[];
- /** @enum {string} */
- visibility: 'public' | 'home' | 'followers' | 'specified';
- visibleUsers: components['schemas']['UserLite'][];
- user: components['schemas']['User'];
- /**
- * @default null
- * @enum {string|null}
- */
- reactionAcceptance: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
- isSchedule: boolean;
- };
- userId: string;
- scheduledAt: string;
- })[];
- };
- };
- /** @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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26046,8 +24971,6 @@ export type operations = {
offset?: number;
/** @description The local host is represented with `.`. */
host?: string;
- /** @enum {string|null} */
- filetype?: 'image' | 'video' | 'audio' | 'module' | 'flash' | null;
/**
* Format: misskey:id
* @default null
@@ -26058,7 +24981,6 @@ export type operations = {
* @default null
*/
channelId?: string | null;
- order?: string;
};
};
};
@@ -26093,12 +25015,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26171,12 +25087,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26231,12 +25141,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26294,12 +25198,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26410,12 +25308,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26454,8 +25346,6 @@ export type operations = {
withFiles?: boolean;
/** @default true */
withRenotes?: boolean;
- /** @default true */
- withBots?: boolean;
};
};
};
@@ -26490,12 +25380,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26558,12 +25442,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26584,8 +25462,6 @@ export type operations = {
'application/json': {
/** Format: misskey:id */
noteId: string;
- /** @default false */
- quote?: boolean;
};
};
};
@@ -26701,72 +25577,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * notes/versions
- * @description No description provided.
- *
- * **Credential required**: *No*
- */
- notes___versions: {
- requestBody: {
- content: {
- 'application/json': {
- /** Format: misskey:id */
- noteId: string;
- };
- };
- };
- responses: {
- /** @description OK (with results) */
- 200: {
- content: {
- 'application/json': Record<string, 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26870,12 +25680,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -26920,12 +25724,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27031,12 +25829,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27168,12 +25960,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27220,12 +26006,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27278,12 +26058,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27340,12 +26114,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27398,12 +26166,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27526,12 +26288,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27578,12 +26334,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27636,12 +26386,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27752,12 +26496,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27816,12 +26554,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27924,12 +26656,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -27982,12 +26708,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28041,12 +26761,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28099,12 +26813,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28165,12 +26873,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28217,12 +26919,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28285,12 +26981,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28345,12 +27035,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28403,12 +27087,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28467,12 +27145,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28519,12 +27191,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28587,12 +27253,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28647,12 +27307,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28717,12 +27371,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28782,72 +27430,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- /** @description Internal server error */
- 500: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
- };
- };
- /**
- * sponsors
- * @description Get Sharkey Sponsors or Instance Sponsors
- *
- * **Credential required**: *No*
- */
- sponsors: {
- requestBody: {
- content: {
- 'application/json': {
- /** @default false */
- forceUpdate?: boolean;
- /** @default false */
- instance?: boolean;
- };
- };
- };
- 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 Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28902,12 +27484,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -28973,12 +27549,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29041,12 +27611,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29098,12 +27662,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29163,12 +27721,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29237,12 +27789,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29298,12 +27844,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29377,12 +27917,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29440,12 +27974,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29506,12 +28034,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29570,12 +28092,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29636,12 +28152,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29705,12 +28215,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29775,12 +28279,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29841,12 +28339,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29906,12 +28398,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -29965,12 +28451,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30026,12 +28506,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30084,12 +28558,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30142,12 +28610,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30219,12 +28681,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30279,12 +28735,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30339,12 +28789,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30461,12 +28905,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30519,12 +28957,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30581,12 +29013,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30642,12 +29068,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30671,15 +29091,7 @@ export type operations = {
/** @default false */
withReplies?: boolean;
/** @default true */
- withRepliesToSelf?: boolean;
- /** @default true */
- withQuotes?: boolean;
- /** @default true */
withRenotes?: boolean;
- /** @default true */
- withBots?: boolean;
- /** @default true */
- withNonPublic?: boolean;
/** @default false */
withChannelNotes?: boolean;
/** @default 10 */
@@ -30728,12 +29140,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30794,12 +29200,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30862,12 +29262,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30924,12 +29318,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -30967,9 +29355,7 @@ export type operations = {
isBlocked: boolean;
isMuted: boolean;
isRenoteMuted: boolean;
- isInstanceMuted?: boolean;
- memo?: string | null;
- }, ({
+ }, {
/** Format: id */
id: string;
isFollowing: boolean;
@@ -30980,9 +29366,7 @@ export type operations = {
isBlocked: boolean;
isMuted: boolean;
isRenoteMuted: boolean;
- isInstanceMuted?: boolean;
- memo?: string | null;
- })[]]>;
+ }[]]>;
};
};
/** @description Client error */
@@ -31009,12 +29393,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -31068,12 +29446,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -31138,12 +29510,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -31202,12 +29568,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -31266,12 +29626,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
@@ -31326,12 +29680,6 @@ export type operations = {
'application/json': components['schemas']['Error'];
};
};
- /** @description Too many requests */
- 429: {
- content: {
- 'application/json': components['schemas']['Error'];
- };
- };
/** @description Internal server error */
500: {
content: {
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index 0ce066182d..f669da6eb5 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -33,8 +33,8 @@ export const permissions = [
'write:favorites',
'read:following',
'write:following',
- 'read:messaging',
- 'write:messaging',
+ 'read:messaging', // deprecated
+ 'write:messaging', // deprecated
'read:mutes',
'write:mutes',
'write:notes',
@@ -116,6 +116,8 @@ export const permissions = [
'read:clip-favorite',
'read:federation',
'write:report-abuse',
+ 'write:chat',
+ 'read:chat',
] as const;
export const moderationLogTypes = [
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 3b31a6e531..22efe0c4cd 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -336,11 +336,11 @@ export type SignupRequest = {
'g-recaptcha-response'?: string | null;
'turnstile-response'?: string | null;
'm-captcha-response'?: string | null;
-}
+};
export type SignupResponse = MeDetailed & {
token: string;
-}
+};
export type SignupPendingRequest = {
code: string;
@@ -391,4 +391,4 @@ export type SigninWithPasskeyResponse = {
type Values<T extends Record<PropertyKey, unknown>> = T[keyof T];
-export type PartialRolePolicyOverride = Partial<{[k in keyof RolePolicies]: Omit<Values<Role['policies']>, 'value'> & { value: RolePolicies[k] }}>;
+export type PartialRolePolicyOverride = Partial<{ [k in keyof RolePolicies]: Omit<Values<Role['policies']>, 'value'> & { value: RolePolicies[k] } }>;
diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts
index 0ef2d1e7a1..371f3f1d13 100644
--- a/packages/misskey-js/src/streaming.ts
+++ b/packages/misskey-js/src/streaming.ts
@@ -197,9 +197,9 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
* Send a message to connection
* ! ストリーム上のやり取りはすべてJSONで行われます !
*/
- public send(typeOrPayload: string): void
- public send(typeOrPayload: string, payload: unknown): void
- public send(typeOrPayload: Record<string, unknown> | unknown[]): void
+ public send(typeOrPayload: string): void;
+ public send(typeOrPayload: string, payload: unknown): void;
+ public send(typeOrPayload: Record<string, unknown> | unknown[]): void;
public send(typeOrPayload: string | Record<string, unknown> | unknown[], payload?: unknown): void {
if (typeof typeOrPayload === 'string') {
this.stream.send(JSON.stringify({
diff --git a/packages/misskey-js/src/streaming.types.ts b/packages/misskey-js/src/streaming.types.ts
index 186db05d61..005c90b702 100644
--- a/packages/misskey-js/src/streaming.types.ts
+++ b/packages/misskey-js/src/streaming.types.ts
@@ -1,5 +1,6 @@
import {
Antenna,
+ ChatMessage,
DriveFile,
DriveFolder,
Note,
@@ -46,13 +47,9 @@ export type Channels = {
urlUploadFinished: (payload: { marker: string; file: DriveFile; }) => void;
readAllNotifications: () => void;
unreadNotification: (payload: Notification) => void;
- unreadMention: (payload: Note['id']) => void;
- readAllUnreadMentions: () => void;
notificationFlushed: () => void;
- unreadSpecifiedNote: (payload: Note['id']) => void;
- readAllUnreadSpecifiedNotes: () => void;
- readAllAntennas: () => void;
unreadAntenna: (payload: Antenna) => void;
+ newChatMessage: (payload: ChatMessage) => void;
readAllAnnouncements: () => void;
myTokenRegenerated: () => void;
signin: (payload: Signin) => void;
diff --git a/packages/misskey-js/test/streaming.ts b/packages/misskey-js/test/streaming.ts
index 06b55cd8af..7e784cd20c 100644
--- a/packages/misskey-js/test/streaming.ts
+++ b/packages/misskey-js/test/streaming.ts
@@ -42,26 +42,26 @@ describe('Streaming', () => {
test('useChannel with parameters', async () => {
const server = new WS('wss://misskey.test/streaming');
const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
- const messagingChannelReceived: any[] = [];
- const messaging = stream.useChannel('messaging', { otherparty: 'aaa' });
- messaging.on('message', payload => {
- messagingChannelReceived.push(payload);
+ const chatChannelReceived: any[] = [];
+ const chat = stream.useChannel('chat', { other: 'aaa' });
+ chat.on('message', payload => {
+ chatChannelReceived.push(payload);
});
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
const msg = JSON.parse(await server.nextMessage as string);
- const messagingChannelId = msg.body.id;
+ const chatChannelId = msg.body.id;
expect(msg.type).toEqual('connect');
- expect(msg.body.channel).toEqual('messaging');
- expect(msg.body.params).toEqual({ otherparty: 'aaa' });
- expect(messagingChannelId != null).toEqual(true);
+ expect(msg.body.channel).toEqual('chat');
+ expect(msg.body.params).toEqual({ other: 'aaa' });
+ expect(chatChannelId != null).toEqual(true);
server.send(JSON.stringify({
type: 'channel',
body: {
- id: messagingChannelId,
+ id: chatChannelId,
type: 'message',
body: {
id: 'foo'
@@ -69,7 +69,7 @@ describe('Streaming', () => {
}
}));
- expect(messagingChannelReceived[0]).toEqual({
+ expect(chatChannelReceived[0]).toEqual({
id: 'foo'
});
@@ -81,20 +81,20 @@ describe('Streaming', () => {
const server = new WS('wss://misskey.test/streaming');
const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
- stream.useChannel('messaging', { otherparty: 'aaa' });
- stream.useChannel('messaging', { otherparty: 'bbb' });
+ stream.useChannel('chat', { other: 'aaa' });
+ stream.useChannel('chat', { other: 'bbb' });
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
const msg = JSON.parse(await server.nextMessage as string);
- const messagingChannelId = msg.body.id;
+ const chatChannelId = msg.body.id;
const msg2 = JSON.parse(await server.nextMessage as string);
- const messagingChannelId2 = msg2.body.id;
+ const chatChannelId2 = msg2.body.id;
- expect(messagingChannelId != null).toEqual(true);
- expect(messagingChannelId2 != null).toEqual(true);
- expect(messagingChannelId).not.toEqual(messagingChannelId2);
+ expect(chatChannelId != null).toEqual(true);
+ expect(chatChannelId2 != null).toEqual(true);
+ expect(chatChannelId).not.toEqual(chatChannelId2);
stream.close();
server.close();
@@ -104,8 +104,8 @@ describe('Streaming', () => {
const server = new WS('wss://misskey.test/streaming');
const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
- const messaging = stream.useChannel('messaging', { otherparty: 'aaa' });
- messaging.send('read', { id: 'aaa' });
+ const chat = stream.useChannel('chat', { other: 'aaa' });
+ chat.send('read', { id: 'aaa' });
const ws = await server.connected;
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json
index 3a468a9369..2bf6319430 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": "22.9.0",
- "@typescript-eslint/eslint-plugin": "7.1.0",
- "@typescript-eslint/parser": "7.1.0",
- "execa": "8.0.1",
- "nodemon": "3.1.7",
- "typescript": "5.6.3",
- "esbuild": "0.24.0",
- "glob": "11.0.0"
+ "@types/node": "22.13.11",
+ "@typescript-eslint/eslint-plugin": "8.27.0",
+ "@typescript-eslint/parser": "8.27.0",
+ "execa": "9.5.2",
+ "nodemon": "3.1.9",
+ "typescript": "5.8.2",
+ "esbuild": "0.25.1",
+ "glob": "11.0.1"
},
"files": [
"built"
diff --git a/packages/sw/package.json b/packages/sw/package.json
index d8c7f3618c..aa5cfd1e62 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -9,16 +9,16 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"dependencies": {
- "esbuild": "0.24.0",
+ "esbuild": "0.25.1",
"idb-keyval": "6.2.1",
"misskey-js": "workspace:*"
},
"devDependencies": {
- "@typescript-eslint/parser": "7.17.0",
- "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
- "eslint-plugin-import": "2.30.0",
- "nodemon": "3.1.7",
- "typescript": "5.6.3"
+ "@typescript-eslint/parser": "8.27.0",
+ "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.74",
+ "eslint-plugin-import": "2.31.0",
+ "nodemon": "3.1.9",
+ "typescript": "5.8.2"
},
"type": "module"
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4e6dc7690e..4507cb2c71 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,7 +5,7 @@ settings:
excludeLinksFromLockfile: false
overrides:
- chokidar: 3.5.3
+ chokidar: 4.0.3
lodash: 4.17.21
importers:
@@ -13,154 +13,148 @@ importers:
.:
dependencies:
cssnano:
- specifier: 6.1.2
- version: 6.1.2(postcss@8.4.49)
+ specifier: 7.0.6
+ version: 7.0.6(postcss@8.5.3)
esbuild:
- specifier: 0.24.0
- version: 0.24.0
+ specifier: 0.25.0
+ version: 0.25.0
execa:
- specifier: 8.0.1
- version: 8.0.1
+ specifier: 9.5.2
+ version: 9.5.2
fast-glob:
- specifier: 3.3.2
- version: 3.3.2
+ specifier: 3.3.3
+ version: 3.3.3
glob:
- specifier: 11.0.0
- version: 11.0.0
+ specifier: 11.0.1
+ version: 11.0.1
ignore-walk:
- specifier: 6.0.5
- version: 6.0.5
+ specifier: 7.0.0
+ version: 7.0.0
js-yaml:
specifier: 4.1.0
version: 4.1.0
postcss:
- specifier: 8.4.49
- version: 8.4.49
+ specifier: 8.5.3
+ version: 8.5.3
tar:
- specifier: 6.2.1
- version: 6.2.1
+ specifier: 7.4.3
+ version: 7.4.3
terser:
- specifier: 5.36.0
- version: 5.36.0
+ specifier: 5.39.0
+ version: 5.39.0
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
optionalDependencies:
cypress:
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.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)
+ specifier: 2.1.0
+ version: 2.1.0(@eslint/compat@1.1.1)(@stylistic/eslint-plugin@4.2.0(eslint@9.22.0)(typescript@5.8.2))(@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2))(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0))(eslint@9.22.0)(globals@16.0.0)
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.10
+ version: 22.13.10
'@typescript-eslint/eslint-plugin':
- specifier: 7.17.0
- 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)
+ specifier: 8.26.0
+ version: 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.17.0
- version: 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.26.0
+ version: 8.26.0(eslint@9.22.0)(typescript@5.8.2)
cross-env:
specifier: 7.0.3
version: 7.0.3
eslint:
- specifier: 9.14.0
- version: 9.14.0
+ specifier: 9.22.0
+ version: 9.22.0
globals:
- specifier: 15.12.0
- version: 15.12.0
+ specifier: 16.0.0
+ version: 16.0.0
ncp:
specifier: 2.0.0
version: 2.0.0
+ pnpm:
+ specifier: 10.6.1
+ version: 10.6.1
start-server-and-test:
- specifier: 2.0.8
- version: 2.0.8
+ specifier: 2.0.10
+ version: 2.0.10
packages/backend:
dependencies:
'@aws-sdk/client-s3':
- specifier: 3.620.0
- version: 3.620.0
+ specifier: 3.772.0
+ version: 3.772.0
'@aws-sdk/lib-storage':
- specifier: 3.620.0
- version: 3.620.0(@aws-sdk/client-s3@3.620.0)
- '@bull-board/api':
- specifier: 6.5.0
- version: 6.5.0(@bull-board/ui@6.5.0)
- '@bull-board/fastify':
- specifier: 6.5.0
- version: 6.5.0
- '@bull-board/ui':
- specifier: 6.5.0
- version: 6.5.0
+ specifier: 3.772.0
+ version: 3.772.0(@aws-sdk/client-s3@3.772.0)
'@discordapp/twemoji':
specifier: 15.1.0
version: 15.1.0
'@fastify/accepts':
- specifier: 5.0.1
- version: 5.0.1
+ specifier: 5.0.2
+ version: 5.0.2
'@fastify/cookie':
- specifier: 11.0.1
- version: 11.0.1
+ specifier: 11.0.2
+ version: 11.0.2
'@fastify/cors':
- specifier: 10.0.1
- version: 10.0.1
+ specifier: 10.1.0
+ version: 10.1.0
'@fastify/express':
- specifier: 4.0.1
- version: 4.0.1
+ specifier: 4.0.2
+ version: 4.0.2
'@fastify/http-proxy':
- specifier: 10.0.1
- version: 10.0.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+ specifier: 10.0.2
+ version: 10.0.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)
'@fastify/multipart':
- specifier: 9.0.1
- version: 9.0.1
+ specifier: 9.0.3
+ version: 9.0.3
'@fastify/static':
- specifier: 8.0.2
- version: 8.0.2
+ specifier: 8.1.1
+ version: 8.1.1
'@fastify/view':
- specifier: 10.0.1
- version: 10.0.1
+ specifier: 10.0.2
+ version: 10.0.2
'@misskey-dev/sharp-read-bmp':
specifier: 1.2.0
version: 1.2.0
'@misskey-dev/summaly':
- specifier: 5.1.0
- version: 5.1.0
+ specifier: 5.2.0
+ version: 5.2.0
'@nestjs/common':
- specifier: 10.4.7
- version: 10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1)
+ specifier: 11.0.12
+ version: 11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core':
- 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)
+ specifier: 11.0.12
+ version: 11.0.12(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/testing':
- 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))
+ specifier: 11.0.12
+ version: 11.0.12(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12))
'@peertube/http-signature':
specifier: 1.7.0
version: 1.7.0
'@sentry/node':
- specifier: 8.38.0
- version: 8.38.0
+ specifier: 8.55.0
+ version: 8.55.0
'@sentry/profiling-node':
- specifier: 8.38.0
- version: 8.38.0
+ specifier: 8.55.0
+ version: 8.55.0
'@simplewebauthn/server':
- specifier: 10.0.1
- version: 10.0.1(encoding@0.1.13)
+ specifier: 12.0.0
+ version: 12.0.0(encoding@0.1.13)
'@sinonjs/fake-timers':
- specifier: 11.2.2
- version: 11.2.2
+ specifier: 11.3.1
+ version: 11.3.1
'@smithy/node-http-handler':
specifier: 2.5.0
version: 2.5.0
'@swc/cli':
- specifier: 0.3.12
- version: 0.3.12(@swc/core@1.9.2)(chokidar@3.5.3)
+ specifier: 0.6.0
+ version: 0.6.0(@swc/core@1.11.11)(chokidar@4.0.3)
'@swc/core':
- specifier: 1.9.2
- version: 1.9.2
+ specifier: 1.11.11
+ version: 1.11.11
'@transfem-org/sfm-js':
specifier: 0.24.6
version: 0.24.6
@@ -195,8 +189,8 @@ importers:
specifier: 1.20.3
version: 1.20.3
bullmq:
- specifier: 5.26.1
- version: 5.26.1
+ specifier: 5.44.1
+ version: 5.44.1
cacheable-lookup:
specifier: 7.0.0
version: 7.0.0
@@ -207,14 +201,14 @@ importers:
specifier: 9.0.2
version: 9.0.2
chalk:
- specifier: 5.3.0
- version: 5.3.0
+ specifier: 5.4.1
+ version: 5.4.1
chalk-template:
specifier: 1.1.0
version: 1.1.0
chokidar:
- specifier: 3.5.3
- version: 3.5.3
+ specifier: 4.0.3
+ version: 4.0.3
cli-highlight:
specifier: 2.1.11
version: 2.1.11
@@ -234,8 +228,8 @@ importers:
specifier: 4.4.1
version: 4.4.1
fastify:
- specifier: 5.0.0
- version: 5.0.0
+ specifier: 5.2.1
+ version: 5.2.1
fastify-multer:
specifier: ^2.0.3
version: 2.0.3
@@ -252,17 +246,17 @@ importers:
specifier: 2.1.3
version: 2.1.3
form-data:
- specifier: 4.0.1
- version: 4.0.1
+ specifier: 4.0.2
+ version: 4.0.2
glob:
specifier: 11.0.0
version: 11.0.0
got:
- specifier: 14.4.4
- version: 14.4.4
+ specifier: 14.4.6
+ version: 14.4.6
happy-dom:
- specifier: 15.11.4
- version: 15.11.4
+ specifier: 16.8.1
+ version: 16.8.1
hpagent:
specifier: 1.2.0
version: 1.2.0
@@ -273,8 +267,8 @@ importers:
specifier: 1.1.3
version: 1.1.3
ioredis:
- specifier: 5.4.1
- version: 5.4.1
+ specifier: 5.6.0
+ version: 5.6.0
ip-cidr:
specifier: 4.0.2
version: 4.0.2
@@ -288,26 +282,26 @@ importers:
specifier: 4.1.0
version: 4.1.0
jsdom:
- specifier: 24.1.1
- version: 24.1.1(bufferutil@4.0.7)(canvas@3.1.0)(utf-8-validate@6.0.3)
+ specifier: 26.0.0
+ version: 26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5)
json5:
specifier: 2.2.3
version: 2.2.3
jsonld:
- specifier: 8.3.2
- version: 8.3.2(web-streams-polyfill@4.0.0)
+ specifier: 8.3.3
+ version: 8.3.3(web-streams-polyfill@4.0.0)
jsrsasign:
specifier: 11.1.0
version: 11.1.0
juice:
- specifier: 11.0.0
- version: 11.0.0
+ specifier: 11.0.1
+ version: 11.0.1
megalodon:
specifier: workspace:*
version: link:../megalodon
meilisearch:
- specifier: 0.45.0
- version: 0.45.0
+ specifier: 0.49.0
+ version: 0.49.0
microformats-parser:
specifier: 2.0.2
version: 2.0.2
@@ -327,8 +321,8 @@ importers:
specifier: 3.0.0-canary.1
version: 3.0.0-canary.1
nanoid:
- specifier: 5.0.8
- version: 5.0.8
+ specifier: 5.1.5
+ version: 5.1.5
nested-property:
specifier: 4.0.0
version: 4.0.0
@@ -336,11 +330,11 @@ importers:
specifier: 3.3.2
version: 3.3.2
nodemailer:
- specifier: 6.9.16
- version: 6.9.16
+ specifier: 6.10.0
+ version: 6.10.0
oauth:
- specifier: 0.10.0
- version: 0.10.0
+ specifier: 0.10.2
+ version: 0.10.2
oauth2orize:
specifier: 1.12.0
version: 1.12.0
@@ -351,14 +345,14 @@ importers:
specifier: 0.0.14
version: 0.0.14
otpauth:
- specifier: 9.3.4
- version: 9.3.4
+ specifier: 9.3.6
+ version: 9.3.6
parse5:
specifier: 7.2.1
version: 7.2.1
pg:
- specifier: 8.13.1
- version: 8.13.1
+ specifier: 8.14.1
+ version: 8.14.1
pkce-challenge:
specifier: 4.1.0
version: 4.1.0
@@ -402,14 +396,14 @@ importers:
specifier: 3.13.0
version: 3.13.0
rxjs:
- specifier: 7.8.1
- version: 7.8.1
+ specifier: 7.8.2
+ version: 7.8.2
sanitize-html:
- specifier: 2.13.1
- version: 2.13.1
+ specifier: 2.15.0
+ version: 2.15.0
secure-json-parse:
- specifier: 2.7.0
- version: 2.7.0
+ specifier: 3.0.2
+ version: 3.0.2
sharp:
specifier: 0.33.5
version: 0.33.5
@@ -423,8 +417,8 @@ importers:
specifier: 2.1.0
version: 2.1.0
systeminformation:
- specifier: 5.23.5
- version: 5.23.5
+ specifier: 5.25.11
+ version: 5.25.11
tinycolor2:
specifier: 1.6.0
version: 1.6.0
@@ -432,20 +426,20 @@ importers:
specifier: 0.2.3
version: 0.2.3
tsc-alias:
- specifier: 1.8.10
- version: 1.8.10
+ specifier: 1.8.11
+ version: 1.8.11
tsconfig-paths:
specifier: 4.2.0
version: 4.2.0
typeorm:
- specifier: 0.3.20
- version: 0.3.20(ioredis@5.4.1)(pg@8.13.1)
+ specifier: 0.3.21
+ version: 0.3.21(ioredis@5.6.0)(pg@8.14.1)(reflect-metadata@0.2.2)
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
ulid:
- specifier: 2.3.0
- version: 2.3.0
+ specifier: 2.4.0
+ version: 2.4.0
uuid:
specifier: ^9.0.1
version: 9.0.1
@@ -456,8 +450,8 @@ importers:
specifier: 3.6.7
version: 3.6.7
ws:
- specifier: 8.18.0
- version: 8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+ specifier: 8.18.1
+ version: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)
xev:
specifier: 3.0.2
version: 3.0.2
@@ -466,41 +460,41 @@ importers:
specifier: 1.3.11
version: 1.3.11
'@swc/core-darwin-arm64':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-darwin-x64':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-freebsd-x64':
specifier: 1.3.11
version: 1.3.11
'@swc/core-linux-arm-gnueabihf':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-linux-arm64-gnu':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-linux-arm64-musl':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-linux-x64-gnu':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-linux-x64-musl':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-win32-arm64-msvc':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-win32-ia32-msvc':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
'@swc/core-win32-x64-msvc':
- specifier: 1.3.56
- version: 1.3.56
+ specifier: 1.11.11
+ version: 1.11.11
bufferutil:
- specifier: 4.0.7
- version: 4.0.7
+ specifier: 4.0.9
+ version: 4.0.9
slacc-android-arm-eabi:
specifier: 0.0.10
version: 0.0.10
@@ -541,21 +535,21 @@ importers:
specifier: 0.0.10
version: 0.0.10
utf-8-validate:
- specifier: 6.0.3
- version: 6.0.3
+ specifier: 6.0.5
+ version: 6.0.5
devDependencies:
'@jest/globals':
specifier: 29.7.0
version: 29.7.0
'@nestjs/platform-express':
- 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)
+ specifier: 10.4.15
+ version: 10.4.15(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12)
'@simplewebauthn/types':
- specifier: 10.0.0
- version: 10.0.0
+ specifier: 12.0.0
+ version: 12.0.0
'@swc/jest':
specifier: 0.2.37
- version: 0.2.37(@swc/core@1.9.2)
+ version: 0.2.37(@swc/core@1.11.11)
'@types/accepts':
specifier: 1.3.7
version: 1.3.7
@@ -596,8 +590,8 @@ importers:
specifier: 1.5.15
version: 1.5.15
'@types/jsrsasign':
- specifier: 10.5.14
- version: 10.5.14
+ specifier: 10.5.15
+ version: 10.5.15
'@types/mime-types':
specifier: 2.1.4
version: 2.1.4
@@ -605,11 +599,11 @@ importers:
specifier: 0.7.34
version: 0.7.34
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.10
+ version: 22.13.10
'@types/nodemailer':
- specifier: 6.4.16
- version: 6.4.16
+ specifier: 6.4.17
+ version: 6.4.17
'@types/oauth':
specifier: 0.9.6
version: 0.9.6
@@ -620,8 +614,8 @@ importers:
specifier: 0.1.2
version: 0.1.2
'@types/pg':
- specifier: 8.11.10
- version: 8.11.10
+ specifier: 8.11.11
+ version: 8.11.11
'@types/proxy-addr':
specifier: ^2.0.3
version: 2.0.3
@@ -668,23 +662,23 @@ importers:
specifier: 3.6.4
version: 3.6.4
'@types/ws':
- specifier: 8.5.13
- version: 8.5.13
+ specifier: 8.18.0
+ version: 8.18.0
'@typescript-eslint/eslint-plugin':
- specifier: 7.17.0
- 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)
+ specifier: 8.27.0
+ version: 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.17.0
- version: 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.27.0
+ version: 8.27.0(eslint@9.22.0)(typescript@5.8.2)
aws-sdk-client-mock:
- specifier: 4.0.1
- version: 4.0.1
+ specifier: 4.1.0
+ version: 4.1.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.14.0)(typescript@5.6.3))(eslint@9.14.0)
+ specifier: 2.31.0
+ version: 2.31.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)
execa:
specifier: 8.0.1
version: 8.0.1
@@ -693,22 +687,25 @@ importers:
version: 9.0.0
jest:
specifier: 29.7.0
- version: 29.7.0(@types/node@22.9.0)
+ version: 29.7.0(@types/node@22.13.10)
jest-mock:
specifier: 29.7.0
version: 29.7.0
nodemon:
- specifier: 3.1.7
- version: 3.1.7
+ specifier: 3.1.9
+ version: 3.1.9
pid-port:
- specifier: 1.0.0
- version: 1.0.0
+ specifier: 1.0.2
+ version: 1.0.2
simple-oauth2:
specifier: 5.1.0
version: 5.1.0
packages/frontend:
dependencies:
+ '@analytics/google-analytics':
+ specifier: 1.1.0
+ version: 1.1.0
'@discordapp/twemoji':
specifier: 15.1.0
version: 15.1.0
@@ -726,13 +723,13 @@ importers:
version: 2.1.1
'@rollup/plugin-json':
specifier: 6.1.0
- version: 6.1.0(rollup@4.26.0)
+ version: 6.1.0(rollup@4.36.0)
'@rollup/plugin-replace':
- specifier: 5.0.7
- version: 5.0.7(rollup@4.26.0)
+ specifier: 6.0.2
+ version: 6.0.2(rollup@4.36.0)
'@rollup/pluginutils':
- specifier: 5.1.3
- version: 5.1.3(rollup@4.26.0)
+ specifier: 5.1.4
+ version: 5.1.4(rollup@4.36.0)
'@ruffle-rs/ruffle':
specifier: 0.1.0-nightly.2024.10.15
version: 0.1.0-nightly.2024.10.15
@@ -746,14 +743,17 @@ importers:
specifier: 15.1.1
version: 15.1.1
'@vitejs/plugin-vue':
- 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))
+ specifier: 5.2.3
+ version: 5.2.3(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))
'@vue/compiler-sfc':
- specifier: 3.5.12
- version: 3.5.12
+ specifier: 3.5.13
+ version: 3.5.13
aiscript-vscode:
specifier: github:aiscript-dev/aiscript-vscode#v0.1.15
version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7
+ analytics:
+ specifier: 0.8.16
+ version: 0.8.16(@types/dlv@1.1.5)
astring:
specifier: 1.9.0
version: 1.9.0
@@ -767,32 +767,32 @@ importers:
specifier: 1.9.3
version: 1.9.3
chart.js:
- specifier: 4.4.6
- version: 4.4.6
+ specifier: 4.4.8
+ version: 4.4.8
chartjs-adapter-date-fns:
specifier: 3.0.0
- version: 3.0.0(chart.js@4.4.6)(date-fns@2.30.0)
+ version: 3.0.0(chart.js@4.4.8)(date-fns@4.1.0)
chartjs-chart-matrix:
- specifier: 2.0.1
- version: 2.0.1(chart.js@4.4.6)
+ specifier: 2.1.1
+ version: 2.1.1(chart.js@4.4.8)
chartjs-plugin-gradient:
specifier: 0.6.1
- version: 0.6.1(chart.js@4.4.6)
+ version: 0.6.1(chart.js@4.4.8)
chartjs-plugin-zoom:
- specifier: 2.0.1
- version: 2.0.1(chart.js@4.4.6)
+ specifier: 2.2.0
+ version: 2.2.0(chart.js@4.4.8)
chromatic:
- specifier: 11.18.1
- version: 11.18.1
+ specifier: 11.27.0
+ version: 11.27.0
compare-versions:
specifier: 6.1.1
version: 6.1.1
cropperjs:
- specifier: 2.0.0-rc.2
- version: 2.0.0-rc.2
+ specifier: 2.0.0
+ version: 2.0.0
date-fns:
- specifier: 2.30.0
- version: 2.30.0
+ specifier: 4.1.0
+ version: 4.1.0
estree-walker:
specifier: 3.0.3
version: 3.0.3
@@ -817,6 +817,9 @@ importers:
katex:
specifier: 0.16.10
version: 0.16.10
+ magic-string:
+ specifier: 0.30.17
+ version: 0.30.17
matter-js:
specifier: 0.20.0
version: 0.20.0
@@ -839,17 +842,17 @@ importers:
specifier: 2.3.1
version: 2.3.1
rollup:
- specifier: 4.26.0
- version: 4.26.0
+ specifier: 4.36.0
+ version: 4.36.0
sanitize-html:
- specifier: 2.13.1
- version: 2.13.1
+ specifier: 2.15.0
+ version: 2.15.0
sass:
- specifier: 1.79.3
- version: 1.79.3
+ specifier: 1.86.0
+ version: 1.86.0
shiki:
- specifier: 1.22.2
- version: 1.22.2
+ specifier: 3.2.1
+ version: 3.2.1
strict-event-emitter-types:
specifier: 2.0.0
version: 2.0.0
@@ -857,8 +860,8 @@ importers:
specifier: 3.1.0
version: 3.1.0
three:
- specifier: 0.169.0
- version: 0.169.0
+ specifier: 0.174.0
+ version: 0.174.0
throttle-debounce:
specifier: 5.0.2
version: 5.0.2
@@ -866,97 +869,100 @@ importers:
specifier: 1.6.0
version: 1.6.0
tsc-alias:
- specifier: 1.8.10
- version: 1.8.10
+ specifier: 1.8.11
+ version: 1.8.11
tsconfig-paths:
specifier: 4.2.0
version: 4.2.0
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
uuid:
- specifier: 10.0.0
- version: 10.0.0
+ specifier: 11.1.0
+ version: 11.1.0
v-code-diff:
specifier: 1.13.1
- version: 1.13.1(vue@3.5.12(typescript@5.6.3))
+ version: 1.13.1(vue@3.5.13(typescript@5.8.2))
vite:
- specifier: 5.4.11
- version: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0)
+ specifier: 6.2.2
+ version: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
vue:
- specifier: 3.5.12
- version: 3.5.12(typescript@5.6.3)
+ specifier: 3.5.13
+ version: 3.5.13(typescript@5.8.2)
vuedraggable:
specifier: next
- version: 4.1.0(vue@3.5.12(typescript@5.6.3))
+ version: 4.1.0(vue@3.5.13(typescript@5.8.2))
+ wanakana:
+ specifier: 5.3.1
+ version: 5.3.1
optionalDependencies:
cypress:
specifier: 13.15.2
version: 13.15.2
devDependencies:
'@misskey-dev/summaly':
- specifier: 5.1.0
- version: 5.1.0
+ specifier: 5.2.0
+ version: 5.2.0
'@storybook/addon-actions':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/addon-essentials':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(@types/react@18.0.28)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/addon-interactions':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/addon-links':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/addon-mdx-gfm':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/addon-storysource':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/blocks':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/components':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/core-events':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/manager-api':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/preview-api':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/react':
- 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)
+ specifier: 8.6.7
+ version: 8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)
'@storybook/react-vite':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.36.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
'@storybook/test':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/theming':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/types':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/vue3':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vue@3.5.13(typescript@5.8.2))
'@storybook/vue3-vite':
- 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))
+ specifier: 8.6.7
+ version: 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))
'@testing-library/vue':
specifier: 8.1.0
- 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))
+ version: 8.1.0(@vue/compiler-sfc@3.5.13)(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))
'@types/canvas-confetti':
- specifier: ^1.6.4
- version: 1.6.4
+ specifier: 1.9.0
+ version: 1.9.0
'@types/estree':
specifier: 1.0.6
version: 1.0.6
@@ -964,14 +970,14 @@ importers:
specifier: ^0.16.7
version: 0.16.7
'@types/matter-js':
- specifier: 0.19.7
- version: 0.19.7
+ specifier: 0.19.8
+ version: 0.19.8
'@types/micromatch':
specifier: 4.0.9
version: 4.0.9
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.11
+ version: 22.13.11
'@types/punycode.js':
specifier: npm:@types/punycode@2.1.4
version: '@types/punycode@2.1.4'
@@ -987,42 +993,39 @@ importers:
'@types/tinycolor2':
specifier: 1.4.6
version: 1.4.6
- '@types/uuid':
- specifier: 10.0.0
- version: 10.0.0
'@types/ws':
- specifier: 8.5.13
- version: 8.5.13
+ specifier: 8.18.0
+ version: 8.18.0
'@typescript-eslint/eslint-plugin':
- specifier: 7.17.0
- 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)
+ specifier: 8.27.0
+ version: 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.17.0
- version: 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.27.0
+ version: 8.27.0(eslint@9.22.0)(typescript@5.8.2)
'@vitest/coverage-v8':
- specifier: 1.6.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))
+ specifier: 3.0.9
+ version: 3.0.9(vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
'@vue/runtime-core':
- specifier: 3.5.12
- version: 3.5.12
+ specifier: 3.5.13
+ version: 3.5.13
acorn:
- specifier: 8.14.0
- version: 8.14.0
+ specifier: 8.14.1
+ version: 8.14.1
cross-env:
specifier: 7.0.3
version: 7.0.3
eslint-plugin-import:
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)
+ version: 2.31.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)
eslint-plugin-vue:
- specifier: 9.31.0
- version: 9.31.0(eslint@9.14.0)
+ specifier: 10.0.0
+ version: 10.0.0(eslint@9.22.0)(vue-eslint-parser@10.1.1(eslint@9.22.0))
fast-glob:
- specifier: 3.3.2
- version: 3.3.2
+ specifier: 3.3.3
+ version: 3.3.3
happy-dom:
- specifier: 10.0.3
- version: 10.0.3
+ specifier: 17.4.4
+ version: 17.4.4
intersection-observer:
specifier: 0.12.2
version: 0.12.2
@@ -1030,53 +1033,56 @@ importers:
specifier: 4.0.8
version: 4.0.8
msw:
- specifier: 2.6.4
- version: 2.6.4(@types/node@22.9.0)(typescript@5.6.3)
+ specifier: 2.7.3
+ version: 2.7.3(@types/node@22.13.11)(typescript@5.8.2)
msw-storybook-addon:
specifier: 2.0.4
- version: 2.0.4(msw@2.6.4(@types/node@22.9.0)(typescript@5.6.3))
+ version: 2.0.4(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))
nodemon:
- specifier: 3.1.7
- version: 3.1.7
+ specifier: 3.1.9
+ version: 3.1.9
prettier:
- specifier: 3.3.3
- version: 3.3.3
+ specifier: 3.5.3
+ version: 3.5.3
react:
- specifier: 18.3.1
- version: 18.3.1
+ specifier: 19.0.0
+ version: 19.0.0
react-dom:
- specifier: 18.3.1
- version: 18.3.1(react@18.3.1)
+ specifier: 19.0.0
+ version: 19.0.0(react@19.0.0)
seedrandom:
specifier: 3.0.5
version: 3.0.5
start-server-and-test:
- specifier: 2.0.8
- version: 2.0.8
+ specifier: 2.0.11
+ version: 2.0.11
storybook:
- specifier: 8.4.4
- version: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ specifier: 8.6.7
+ version: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
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.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)
+ version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/components@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/core-events@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/manager-api@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/preview-api@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/theming@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/types@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ vite-node:
+ specifier: 3.0.9
+ version: 3.0.9(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
vite-plugin-turbosnap:
specifier: 1.0.3
version: 1.0.3
vitest:
- specifier: 1.6.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)
+ specifier: 3.0.9
+ version: 3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
vitest-fetch-mock:
- specifier: 0.2.2
- 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))
+ specifier: 0.4.5
+ version: 0.4.5(vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
vue-component-type-helpers:
- specifier: 2.1.10
- version: 2.1.10
+ specifier: 2.2.8
+ version: 2.2.8
vue-eslint-parser:
- specifier: 9.4.3
- version: 9.4.3(eslint@9.14.0)
+ specifier: 10.1.1
+ version: 10.1.1(eslint@9.22.0)
vue-tsc:
- specifier: 2.1.10
- version: 2.1.10(typescript@5.6.3)
+ specifier: 2.2.8
+ version: 2.2.8(typescript@5.8.2)
packages/frontend-embed:
dependencies:
@@ -1088,13 +1094,13 @@ importers:
version: 2.1.1
'@rollup/plugin-json':
specifier: 6.1.0
- version: 6.1.0(rollup@4.26.0)
+ version: 6.1.0(rollup@4.36.0)
'@rollup/plugin-replace':
- specifier: 5.0.7
- version: 5.0.7(rollup@4.26.0)
+ specifier: 6.0.2
+ version: 6.0.2(rollup@4.36.0)
'@rollup/pluginutils':
- specifier: 5.1.3
- version: 5.1.3(rollup@4.26.0)
+ specifier: 5.1.4
+ version: 5.1.4(rollup@4.36.0)
'@transfem-org/sfm-js':
specifier: 0.24.5
version: 0.24.5
@@ -1102,11 +1108,11 @@ importers:
specifier: 15.1.1
version: 15.1.1
'@vitejs/plugin-vue':
- 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))
+ specifier: 5.2.3
+ version: 5.2.3(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))
'@vue/compiler-sfc':
- specifier: 3.5.12
- version: 3.5.12
+ specifier: 3.5.13
+ version: 3.5.13
astring:
specifier: 1.9.0
version: 1.9.0
@@ -1129,42 +1135,42 @@ importers:
specifier: 2.3.1
version: 2.3.1
rollup:
- specifier: 4.26.0
- version: 4.26.0
+ specifier: 4.36.0
+ version: 4.36.0
sass:
- specifier: 1.79.4
- version: 1.79.4
+ specifier: 1.86.0
+ version: 1.86.0
shiki:
- specifier: 1.22.2
- version: 1.22.2
+ specifier: 3.2.1
+ version: 3.2.1
tinycolor2:
specifier: 1.6.0
version: 1.6.0
tsc-alias:
- specifier: 1.8.10
- version: 1.8.10
+ specifier: 1.8.11
+ version: 1.8.11
tsconfig-paths:
specifier: 4.2.0
version: 4.2.0
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
uuid:
- specifier: 10.0.0
- version: 10.0.0
+ specifier: 11.1.0
+ version: 11.1.0
vite:
- specifier: 5.4.11
- version: 5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0)
+ specifier: 6.2.2
+ version: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
vue:
- specifier: 3.5.12
- version: 3.5.12(typescript@5.6.3)
+ specifier: 3.5.13
+ version: 3.5.13(typescript@5.8.2)
devDependencies:
'@misskey-dev/summaly':
- specifier: 5.1.0
- version: 5.1.0
+ specifier: 5.2.0
+ version: 5.2.0
'@testing-library/vue':
specifier: 8.1.0
- 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))
+ version: 8.1.0(@vue/compiler-sfc@3.5.13)(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))
'@types/estree':
specifier: 1.0.6
version: 1.0.6
@@ -1172,50 +1178,47 @@ importers:
specifier: 4.0.9
version: 4.0.9
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.11
+ version: 22.13.11
'@types/punycode.js':
specifier: npm:@types/punycode@2.1.4
version: '@types/punycode@2.1.4'
'@types/tinycolor2':
specifier: 1.4.6
version: 1.4.6
- '@types/uuid':
- specifier: 10.0.0
- version: 10.0.0
'@types/ws':
- specifier: 8.5.13
- version: 8.5.13
+ specifier: 8.18.0
+ version: 8.18.0
'@typescript-eslint/eslint-plugin':
- specifier: 7.17.0
- 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)
+ specifier: 8.27.0
+ version: 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.17.0
- version: 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.27.0
+ version: 8.27.0(eslint@9.22.0)(typescript@5.8.2)
'@vitest/coverage-v8':
- specifier: 1.6.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))
+ specifier: 3.0.9
+ version: 3.0.9(vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
'@vue/runtime-core':
- specifier: 3.5.12
- version: 3.5.12
+ specifier: 3.5.13
+ version: 3.5.13
acorn:
- specifier: 8.14.0
- version: 8.14.0
+ specifier: 8.14.1
+ version: 8.14.1
cross-env:
specifier: 7.0.3
version: 7.0.3
eslint-plugin-import:
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)
+ version: 2.31.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)
eslint-plugin-vue:
- specifier: 9.31.0
- version: 9.31.0(eslint@9.14.0)
+ specifier: 10.0.0
+ version: 10.0.0(eslint@9.22.0)(vue-eslint-parser@10.1.1(eslint@9.22.0))
fast-glob:
- specifier: 3.3.2
- version: 3.3.2
+ specifier: 3.3.3
+ version: 3.3.3
happy-dom:
- specifier: 10.0.3
- version: 10.0.3
+ specifier: 17.4.4
+ version: 17.4.4
intersection-observer:
specifier: 0.12.2
version: 0.12.2
@@ -1223,29 +1226,29 @@ importers:
specifier: 4.0.8
version: 4.0.8
msw:
- specifier: 2.6.4
- version: 2.6.4(@types/node@22.9.0)(typescript@5.6.3)
+ specifier: 2.7.3
+ version: 2.7.3(@types/node@22.13.11)(typescript@5.8.2)
nodemon:
- specifier: 3.1.7
- version: 3.1.7
+ specifier: 3.1.9
+ version: 3.1.9
prettier:
- specifier: 3.3.3
- version: 3.3.3
+ specifier: 3.5.3
+ version: 3.5.3
start-server-and-test:
- specifier: 2.0.8
- version: 2.0.8
+ specifier: 2.0.11
+ version: 2.0.11
vite-plugin-turbosnap:
specifier: 1.0.3
version: 1.0.3
vue-component-type-helpers:
- specifier: 2.1.10
- version: 2.1.10
+ specifier: 2.2.8
+ version: 2.2.8
vue-eslint-parser:
- specifier: 9.4.3
- version: 9.4.3(eslint@9.14.0)
+ specifier: 10.1.1
+ version: 10.1.1(eslint@9.22.0)
vue-tsc:
- specifier: 2.1.10
- version: 2.1.10(typescript@5.6.3)
+ specifier: 2.2.8
+ version: 2.2.8(typescript@5.8.2)
packages/frontend-shared:
dependencies:
@@ -1256,30 +1259,30 @@ importers:
specifier: 3.1.7
version: 3.1.7
vue:
- specifier: 3.5.12
- version: 3.5.12(typescript@5.6.3)
+ specifier: 3.5.13
+ version: 3.5.13(typescript@5.8.2)
devDependencies:
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.11
+ version: 22.13.11
'@typescript-eslint/eslint-plugin':
- specifier: 7.17.0
- 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)
+ specifier: 8.27.0
+ version: 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.17.0
- version: 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.27.0
+ version: 8.27.0(eslint@9.22.0)(typescript@5.8.2)
esbuild:
- specifier: 0.24.0
- version: 0.24.0
+ specifier: 0.25.1
+ version: 0.25.1
eslint-plugin-vue:
- specifier: 9.31.0
- version: 9.31.0(eslint@9.14.0)
+ specifier: 10.0.0
+ version: 10.0.0(eslint@9.22.0)(vue-eslint-parser@10.1.1(eslint@9.22.0))
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
vue-eslint-parser:
- specifier: 9.4.3
- version: 9.4.3(eslint@9.14.0)
+ specifier: 10.1.1
+ version: 10.1.1(eslint@9.22.0)
packages/megalodon:
dependencies:
@@ -1339,7 +1342,7 @@ importers:
version: 9.0.1
ws:
specifier: 8.17.1
- version: 8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+ version: 8.17.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)
devDependencies:
'@typescript-eslint/eslint-plugin':
specifier: ^6.12.0
@@ -1355,7 +1358,7 @@ importers:
version: 9.1.0(eslint@8.57.0)
jest:
specifier: ^29.7.0
- version: 29.7.0(@types/node@22.9.0)
+ version: 29.7.0(@types/node@22.13.10)
jest-worker:
specifier: ^29.7.0
version: 29.7.0
@@ -1367,7 +1370,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.24.0)(jest@29.7.0(@types/node@22.9.0))(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.25.0)(jest@29.7.0(@types/node@22.13.10))(typescript@5.1.6)
typedoc:
specifier: ^0.25.3
version: 0.25.13(typescript@5.1.6)
@@ -1378,48 +1381,48 @@ importers:
specifier: 5.0.1
version: 5.0.1
matter-js:
- specifier: 0.19.0
- version: 0.19.0
+ specifier: 0.20.0
+ version: 0.20.0
seedrandom:
specifier: 3.0.5
version: 3.0.5
devDependencies:
'@types/matter-js':
- specifier: 0.19.7
- version: 0.19.7
+ specifier: 0.19.8
+ version: 0.19.8
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.11
+ version: 22.13.11
'@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.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.27.0
+ version: 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.1.0
- version: 7.1.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.27.0
+ version: 8.27.0(eslint@9.22.0)(typescript@5.8.2)
esbuild:
- specifier: 0.24.0
- version: 0.24.0
+ specifier: 0.25.1
+ version: 0.25.1
execa:
- specifier: 8.0.1
- version: 8.0.1
+ specifier: 9.5.2
+ version: 9.5.2
glob:
- specifier: 11.0.0
- version: 11.0.0
+ specifier: 11.0.1
+ version: 11.0.1
nodemon:
- specifier: 3.1.7
- version: 3.1.7
+ specifier: 3.1.9
+ version: 3.1.9
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
packages/misskey-js:
dependencies:
'@simplewebauthn/types':
- specifier: 11.0.0
- version: 11.0.0
+ specifier: 12.0.0
+ version: 12.0.0
eventemitter3:
specifier: 5.0.1
version: 5.0.1
@@ -1428,35 +1431,35 @@ importers:
version: 4.4.0
devDependencies:
'@microsoft/api-extractor':
- specifier: 7.47.11
- version: 7.47.11(@types/node@22.9.0)
+ specifier: 7.51.1
+ version: 7.51.1(@types/node@22.13.9)
'@swc/jest':
specifier: 0.2.37
- version: 0.2.37(@swc/core@1.9.2)
+ version: 0.2.37(@swc/core@1.11.11)
'@types/jest':
specifier: 29.5.14
version: 29.5.14
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.9
+ version: 22.13.9
'@typescript-eslint/eslint-plugin':
- specifier: 7.17.0
- 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)
+ specifier: 8.26.0
+ version: 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.17.0
- version: 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.26.0
+ version: 8.26.0(eslint@9.22.0)(typescript@5.8.2)
esbuild:
- specifier: 0.24.0
- version: 0.24.0
+ specifier: 0.25.0
+ version: 0.25.0
execa:
specifier: 8.0.1
version: 8.0.1
glob:
- specifier: 11.0.0
- version: 11.0.0
+ specifier: 11.0.1
+ version: 11.0.1
jest:
specifier: 29.7.0
- version: 29.7.0(@types/node@22.9.0)
+ version: 29.7.0(@types/node@22.13.9)
jest-fetch-mock:
specifier: 3.0.3
version: 3.0.3(encoding@0.1.13)
@@ -1470,29 +1473,29 @@ importers:
specifier: 2.0.0
version: 2.0.0
nodemon:
- specifier: 3.1.7
- version: 3.1.7
+ specifier: 3.1.9
+ version: 3.1.9
tsd:
specifier: 0.31.2
version: 0.31.2
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
packages/misskey-js/generator:
devDependencies:
'@readme/openapi-parser':
- specifier: 2.6.0
- version: 2.6.0(openapi-types@12.1.3)
+ specifier: 2.7.0
+ version: 2.7.0(openapi-types@12.1.3)
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.9
+ version: 22.13.9
'@typescript-eslint/eslint-plugin':
- specifier: 7.17.0
- 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)
+ specifier: 8.26.0
+ version: 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.14.0)(typescript@5.8.2))(eslint@9.14.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.17.0
- version: 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.26.0
+ version: 8.26.0(eslint@9.14.0)(typescript@5.8.2)
eslint:
specifier: 9.14.0
version: 9.14.0
@@ -1500,17 +1503,17 @@ importers:
specifier: 12.1.3
version: 12.1.3
openapi-typescript:
- specifier: 6.7.3
- version: 6.7.3
+ specifier: 6.7.6
+ version: 6.7.6
ts-case-convert:
specifier: 2.1.0
version: 2.1.0
tsx:
- specifier: 4.4.0
- version: 4.4.0
+ specifier: 4.19.3
+ version: 4.19.3
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
packages/misskey-reversi:
dependencies:
@@ -1519,35 +1522,35 @@ importers:
version: 1.2.2
devDependencies:
'@types/node':
- specifier: 22.9.0
- version: 22.9.0
+ specifier: 22.13.11
+ version: 22.13.11
'@typescript-eslint/eslint-plugin':
- specifier: 7.1.0
- 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)
+ specifier: 8.27.0
+ version: 8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
'@typescript-eslint/parser':
- specifier: 7.1.0
- version: 7.1.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.27.0
+ version: 8.27.0(eslint@9.22.0)(typescript@5.8.2)
esbuild:
- specifier: 0.24.0
- version: 0.24.0
+ specifier: 0.25.1
+ version: 0.25.1
execa:
- specifier: 8.0.1
- version: 8.0.1
+ specifier: 9.5.2
+ version: 9.5.2
glob:
- specifier: 11.0.0
- version: 11.0.0
+ specifier: 11.0.1
+ version: 11.0.1
nodemon:
- specifier: 3.1.7
- version: 3.1.7
+ specifier: 3.1.9
+ version: 3.1.9
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
packages/sw:
dependencies:
esbuild:
- specifier: 0.24.0
- version: 0.24.0
+ specifier: 0.25.1
+ version: 0.25.1
idb-keyval:
specifier: 6.2.1
version: 6.2.1
@@ -1556,20 +1559,20 @@ importers:
version: link:../misskey-js
devDependencies:
'@typescript-eslint/parser':
- specifier: 7.17.0
- version: 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ specifier: 8.27.0
+ version: 8.27.0(eslint@9.22.0)(typescript@5.8.2)
'@typescript/lib-webworker':
- specifier: npm:@types/serviceworker@0.0.67
- version: '@types/serviceworker@0.0.67'
+ specifier: npm:@types/serviceworker@0.0.74
+ version: '@types/serviceworker@0.0.74'
eslint-plugin-import:
- specifier: 2.30.0
- version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)
+ specifier: 2.31.0
+ version: 2.31.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)
nodemon:
- specifier: 3.1.7
- version: 3.1.7
+ specifier: 3.1.9
+ version: 3.1.9
typescript:
- specifier: 5.6.3
- version: 5.6.3
+ specifier: 5.8.2
+ version: 5.8.2
packages:
@@ -1585,9 +1588,40 @@ packages:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
+ '@ampproject/remapping@2.3.0':
+ resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+ engines: {node: '>=6.0.0'}
+
+ '@analytics/cookie-utils@0.2.12':
+ resolution: {integrity: sha512-2h/yuIu3kmu+ZJlKmlT6GoRvUEY2k1BbQBezEv5kGhnn9KpmzPz715Y3GmM2i+m7Y0QmBdVUoA260dQZkofs2A==}
+
+ '@analytics/core@0.12.17':
+ resolution: {integrity: sha512-GMxRm5Dp3Wam/w5NNvqNKMO6zWecozbVv21Kn4WhftCx6OjJI7zMlVtiLpjGjxa0RRZfVG80YhupF0Qh9XL2gw==}
+
+ '@analytics/global-storage-utils@0.1.7':
+ resolution: {integrity: sha512-V+spzGLZYm4biZT4uefaylm80SrLXf8WOTv9hCgA46cLcyxx3LD4GCpssp1lj+RcWLl/uXJQBRO4Mnn/o1x6Gw==}
+
+ '@analytics/google-analytics@1.1.0':
+ resolution: {integrity: sha512-i8uGyELMtwEUAf3GNWNLNBzhRvReDn1RUxvMdMhjUA7+GNGxPOM4kkzFfv3giQXKNxTEjfsh75kqNcscbJsuaA==}
+
+ '@analytics/localstorage-utils@0.1.10':
+ resolution: {integrity: sha512-uJS+Jp1yLG5VFCgA5T82ZODYBS0xuDQx0NtAZrgbqt9j51BX3TcgmOez5LVkrUNu/lpbxjCLq35I4TKj78VmOQ==}
+
+ '@analytics/session-storage-utils@0.0.7':
+ resolution: {integrity: sha512-PSv40UxG96HVcjY15e3zOqU2n8IqXnH8XvTkg1X43uXNTKVSebiI2kUjA3Q7ESFbw5DPwcLbJhV7GforpuBLDw==}
+
+ '@analytics/storage-utils@0.4.2':
+ resolution: {integrity: sha512-AXObwyVQw9h2uJh1t2hUgabtVxzYpW+7uKVbdHQK80vr3Td5rrmCxrCxarh7HUuAgSDZ0bZWqmYxVgmwKceaLg==}
+
+ '@analytics/type-utils@0.6.2':
+ resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
+
'@apidevtools/swagger-methods@3.0.2':
resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
+ '@asamuzakjp/css-color@3.1.1':
+ resolution: {integrity: sha512-hpRD68SV2OMcZCsrbdkccTw5FXjNDLo5OuqSHyHZfwweGsDWZwDJ2+gONyNAbazZclobMirACLw0lk8WVxIqxA==}
+
'@aws-crypto/crc32@5.2.0':
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
engines: {node: '>=16.0.0'}
@@ -1611,155 +1645,139 @@ packages:
'@aws-crypto/util@5.2.0':
resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
- '@aws-sdk/client-s3@3.620.0':
- resolution: {integrity: sha512-kf3Lqvuq/ciUn4myQjd1a9nhVg95+FEWkIq7pdkgxFoKow8HKj3nuiwI7zYBRTfk0RKXRkJca3GE+3RXpeZSiA==}
- engines: {node: '>=16.0.0'}
-
- '@aws-sdk/client-sso-oidc@3.620.0':
- resolution: {integrity: sha512-CWL8aJa6rrNaQXNsLhblGZzbFBrRz4BXAsFBbyqAZEmryr9q/IC7z/ww3nq8CD2UsW+bn89U/XcoP5r1KWUHuQ==}
- engines: {node: '>=16.0.0'}
- peerDependencies:
- '@aws-sdk/client-sts': ^3.620.0
-
- '@aws-sdk/client-sso@3.620.0':
- resolution: {integrity: sha512-J1CvF7u39XwtCK9rPlkW2AA631EPqkb4PjOOj9aZ9LjQmkJ0DkL+9tEqU2XIWcjDd2Z3hS3LBuS8uN7upIkEnQ==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/client-s3@3.772.0':
+ resolution: {integrity: sha512-HQXlQIyyLp47h1/Hdjr36yK8/gsAAFX2vRzgDJhSRaz0vWZlWX07AJdYfrxapLUXfVU6DbBu3rwi2UGqM7ixqQ==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/client-sts@3.620.0':
- resolution: {integrity: sha512-pG4SqDHZV/ZbpoVoVtpxo6ZZoqVDbVItC3QUO73UJ3Gemxznd/Ck7kAsyb6/dJkV/Aqm3gt2O5UL7vzQLNHSjw==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/client-sso@3.772.0':
+ resolution: {integrity: sha512-sDdxepi74+cL6gXJJ2yw3UNSI7GBvoGTwZqFyPoNAzcURvaYwo8dBr7G4jS9GDanjTlO3CGVAf2VMcpqEvmoEw==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/core@3.620.0':
- resolution: {integrity: sha512-5D9tMahxIDDFLULS9/ULa0HuIu7CZSshfj6wmDSmigXzkWyUvHoVIrme2z6eM3Icat/MO3d4WEy3445Vk385gQ==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/core@3.758.0':
+ resolution: {integrity: sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/credential-provider-env@3.609.0':
- resolution: {integrity: sha512-v69ZCWcec2iuV9vLVJMa6fAb5xwkzN4jYIT8yjo2c4Ia/j976Q+TPf35Pnz5My48Xr94EFcaBazrWedF+kwfuQ==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/credential-provider-env@3.758.0':
+ resolution: {integrity: sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/credential-provider-http@3.620.0':
- resolution: {integrity: sha512-BI2BdrSKDmB/2ouB/NJR0PT0x/+5fmoF6XOE78hFBb4F5w/yynGgcJY936dF+oREfpME6ehjB2b0okGg78Scpw==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/credential-provider-http@3.758.0':
+ resolution: {integrity: sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/credential-provider-ini@3.620.0':
- resolution: {integrity: sha512-P9fYi6dzZIl8ITC7qAPf5DX9omI3LfA91g3KH+0OUmS3ctP7tN+gNo3HmqlzoqnwPe0pXn1FumYAe1qFl6Yjjg==}
- engines: {node: '>=16.0.0'}
- peerDependencies:
- '@aws-sdk/client-sts': ^3.620.0
+ '@aws-sdk/credential-provider-ini@3.772.0':
+ resolution: {integrity: sha512-T1Ec9Q25zl5c/eZUPHZsiq8vgBeWBjHM7WM5xtZszZRPqqhQGnmFlomz1r9rwhW8RFB5k8HRaD/SLKo6jtYl/A==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/credential-provider-node@3.620.0':
- resolution: {integrity: sha512-or8ahy4ysURcWgKX00367DMDTTyMynDEl+FQh4wce66fMyePhFVuoPcRgXzWsi8KYmL95sPCfJFNqBMyFNcgvQ==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/credential-provider-node@3.772.0':
+ resolution: {integrity: sha512-0IdVfjBO88Mtekq/KaScYSIEPIeR+ABRvBOWyj/c/qQ2KJyI0GRlSAzpANfxDLHVPn3yEHuZd9nRL6sOmOMI0A==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/credential-provider-process@3.614.0':
- resolution: {integrity: sha512-Q0SI0sTRwi8iNODLs5+bbv8vgz8Qy2QdxbCHnPk/6Cx6LMf7i3dqmWquFbspqFRd8QiqxStrblwxrUYZi09tkA==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/credential-provider-process@3.758.0':
+ resolution: {integrity: sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/credential-provider-sso@3.620.0':
- resolution: {integrity: sha512-xtIj2hmq3jcKwvGmqhoYapbWeQfFyoQgKBtwD6nx0M6oS5lbFH4rzHhj0gBwatZDjMa35cWtcYVUJCv2/9mWvA==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/credential-provider-sso@3.772.0':
+ resolution: {integrity: sha512-yR3Y5RAVPa4ogojcBOpZUx6XyRVAkynIJCjd0avdlxW1hhnzSr5/pzoiJ6u21UCbkxlJJTDZE3jfFe7tt+HA4w==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/credential-provider-web-identity@3.609.0':
- resolution: {integrity: sha512-U+PG8NhlYYF45zbr1km3ROtBMYqyyj/oK8NRp++UHHeuavgrP+4wJ4wQnlEaKvJBjevfo3+dlIBcaeQ7NYejWg==}
- engines: {node: '>=16.0.0'}
- peerDependencies:
- '@aws-sdk/client-sts': ^3.609.0
+ '@aws-sdk/credential-provider-web-identity@3.772.0':
+ resolution: {integrity: sha512-yHAT5Y2y0fnecSuWRUn8NMunKfDqFYhnOpGq8UyCEcwz9aXzibU0hqRIEm51qpR81hqo0GMFDH0EOmegZ/iW5w==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/lib-storage@3.620.0':
- resolution: {integrity: sha512-xUeKH8RrPQqE6J49Yun0qCdu5SGaN5LgyyjWutLeElqR0G3jhmPVrRzFXsHDXbr9S0QVE4V8DjeC17bkEdG4dw==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/lib-storage@3.772.0':
+ resolution: {integrity: sha512-d59NJAHDa7mGf82ApOdIp1FsN1i7/VlNTk2t3NIepH/8FsMTzKPZQvbMzqE33ba4oik7y6QrDliYXoRXB/nZPg==}
+ engines: {node: '>=18.0.0'}
peerDependencies:
- '@aws-sdk/client-s3': ^3.620.0
+ '@aws-sdk/client-s3': ^3.772.0
- '@aws-sdk/middleware-bucket-endpoint@3.620.0':
- resolution: {integrity: sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-bucket-endpoint@3.734.0':
+ resolution: {integrity: sha512-etC7G18aF7KdZguW27GE/wpbrNmYLVT755EsFc8kXpZj8D6AFKxc7OuveinJmiy0bYXAMspJUWsF6CrGpOw6CQ==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-expect-continue@3.620.0':
- resolution: {integrity: sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-expect-continue@3.734.0':
+ resolution: {integrity: sha512-P38/v1l6HjuB2aFUewt7ueAW5IvKkFcv5dalPtbMGRhLeyivBOHwbCyuRKgVs7z7ClTpu9EaViEGki2jEQqEsQ==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-flexible-checksums@3.620.0':
- resolution: {integrity: sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-flexible-checksums@3.758.0':
+ resolution: {integrity: sha512-o8Rk71S08YTKLoSobucjnbj97OCGaXgpEDNKXpXaavUM5xLNoHCLSUPRCiEN86Ivqxg1n17Y2nSRhfbsveOXXA==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-host-header@3.620.0':
- resolution: {integrity: sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-host-header@3.734.0':
+ resolution: {integrity: sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-location-constraint@3.609.0':
- resolution: {integrity: sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-location-constraint@3.734.0':
+ resolution: {integrity: sha512-EJEIXwCQhto/cBfHdm3ZOeLxd2NlJD+X2F+ZTOxzokuhBtY0IONfC/91hOo5tWQweerojwshSMHRCKzRv1tlwg==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-logger@3.609.0':
- resolution: {integrity: sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-logger@3.734.0':
+ resolution: {integrity: sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-recursion-detection@3.620.0':
- resolution: {integrity: sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-recursion-detection@3.772.0':
+ resolution: {integrity: sha512-zg0LjJa4v7fcLzn5QzZvtVS+qyvmsnu7oQnb86l6ckduZpWDCDC9+A0ZzcXTrxblPCJd3JqkoG1+Gzi4S4Ny/Q==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-sdk-s3@3.620.0':
- resolution: {integrity: sha512-AAZ6NLVOx/bP97PYj/afCMeySzxOHocgJG3ZXh6f8MnJcGpZgx8NyRm0vtiYUTFrS2JtU4xV05Dl3j4afV3s4A==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-sdk-s3@3.758.0':
+ resolution: {integrity: sha512-6mJ2zyyHPYSV6bAcaFpsdoXZJeQlR1QgBnZZ6juY/+dcYiuyWCdyLUbGzSZSE7GTfx6i+9+QWFeoIMlWKgU63A==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-signing@3.620.0':
- resolution: {integrity: sha512-gxI7rubiaanUXaLfJ4NybERa9MGPNg2Ycl/OqANsozrBnR3Pw8vqy3EuVImQOyn2pJ2IFvl8ZPoSMHf4pX56FQ==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-ssec@3.734.0':
+ resolution: {integrity: sha512-d4yd1RrPW/sspEXizq2NSOUivnheac6LPeLSLnaeTbBG9g1KqIqvCzP1TfXEqv2CrWfHEsWtJpX7oyjySSPvDQ==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-ssec@3.609.0':
- resolution: {integrity: sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/middleware-user-agent@3.758.0':
+ resolution: {integrity: sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/middleware-user-agent@3.620.0':
- resolution: {integrity: sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/nested-clients@3.772.0':
+ resolution: {integrity: sha512-gNJbBxR5YlEumsCS9EWWEASXEnysL0aDnr9MNPX1ip/g1xOqRHmytgV/+t8RFZFTKg0OprbWTq5Ich3MqsEuCQ==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/region-config-resolver@3.614.0':
- resolution: {integrity: sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/region-config-resolver@3.734.0':
+ resolution: {integrity: sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/signature-v4-multi-region@3.620.0':
- resolution: {integrity: sha512-yu1pTCqIbkSdaOvmyfW9vV9jWe3pDApkQPZLg4VEN5dXDWRtgQ/amv88myyCEoG14irUN1tsbvytcKzGyEXnhA==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/signature-v4-multi-region@3.758.0':
+ resolution: {integrity: sha512-0RPCo8fYJcrenJ6bRtiUbFOSgQ1CX/GpvwtLU2Fam1tS9h2klKK8d74caeV6A1mIUvBU7bhyQ0wMGlwMtn3EYw==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/token-providers@3.614.0':
- resolution: {integrity: sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==}
- engines: {node: '>=16.0.0'}
- peerDependencies:
- '@aws-sdk/client-sso-oidc': ^3.614.0
+ '@aws-sdk/token-providers@3.772.0':
+ resolution: {integrity: sha512-d1Waa1vyebuokcAWYlkZdtFlciIgob7B39vPRmtxMObbGumJKiOy/qCe2/FB/72h1Ej9Ih32lwvbxUjORQWN4g==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/types@3.609.0':
- resolution: {integrity: sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/types@3.734.0':
+ resolution: {integrity: sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/util-arn-parser@3.568.0':
- resolution: {integrity: sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/util-arn-parser@3.723.0':
+ resolution: {integrity: sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w==}
+ engines: {node: '>=18.0.0'}
- '@aws-sdk/util-endpoints@3.614.0':
- resolution: {integrity: sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/util-endpoints@3.743.0':
+ resolution: {integrity: sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw==}
+ engines: {node: '>=18.0.0'}
'@aws-sdk/util-locate-window@3.208.0':
resolution: {integrity: sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==}
engines: {node: '>=14.0.0'}
- '@aws-sdk/util-user-agent-browser@3.609.0':
- resolution: {integrity: sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==}
+ '@aws-sdk/util-user-agent-browser@3.734.0':
+ resolution: {integrity: sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==}
- '@aws-sdk/util-user-agent-node@3.614.0':
- resolution: {integrity: sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/util-user-agent-node@3.758.0':
+ resolution: {integrity: sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw==}
+ engines: {node: '>=18.0.0'}
peerDependencies:
aws-crt: '>=1.0.0'
peerDependenciesMeta:
aws-crt:
optional: true
- '@aws-sdk/xml-builder@3.609.0':
- resolution: {integrity: sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==}
- engines: {node: '>=16.0.0'}
+ '@aws-sdk/xml-builder@3.734.0':
+ resolution: {integrity: sha512-Zrjxi5qwGEcUsJ0ru7fRtW74WcTS0rbLcehoFB+rN1GRi2hbLcFaYs4PwVA5diLeAJH0gszv3x4Hr/S87MfbKQ==}
+ engines: {node: '>=18.0.0'}
'@babel/code-frame@7.23.5':
resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
@@ -1980,10 +1998,6 @@ packages:
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'}
@@ -1999,16 +2013,9 @@ packages:
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
- '@bull-board/api@6.5.0':
- resolution: {integrity: sha512-sFHxmqtbBBkQaJxHdRq2sAR0+l9TBInItXaIdBMjeBXrFW881g4aLAbO7Lno6cDPKBMauYg4TdBtRPTkjhr43w==}
- peerDependencies:
- '@bull-board/ui': 6.5.0
-
- '@bull-board/fastify@6.5.0':
- resolution: {integrity: sha512-oPLqIJPkis13WMPeuephkGeP/++AB5Qw3aw0qESU1K+e1pdwzf1kYIFpOZuTe9L/MrEulKe2ZgENw6RIgC1RBw==}
-
- '@bull-board/ui@6.5.0':
- resolution: {integrity: sha512-gIoOgKNVAnPdKBBUaBUSNS2cBJz8UYGfEuYzD/H9HIpkCHiPTUVoMO48w/D+urJoko2nW8OSkU1kf2OkZsqP0Q==}
+ '@bcoe/v8-coverage@1.0.2':
+ resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
+ engines: {node: '>=18'}
'@bundled-es-modules/cookie@2.0.1':
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
@@ -2022,42 +2029,73 @@ packages:
'@canvas/image-data@1.0.0':
resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==}
+ '@chainsafe/is-ip@2.1.0':
+ resolution: {integrity: sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==}
+
'@colors/colors@1.5.0':
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
- '@cropper/element-canvas@2.0.0-rc.2':
- resolution: {integrity: sha512-0aqbJ3ycQM6/yn4T03vw8K/OeTB8C6+Z/jimuavy4UM2CENH9ucSLM4hAG0yYCgghIyv9Zd0unaBmtgW+I5+SQ==}
+ '@cropper/element-canvas@2.0.0':
+ resolution: {integrity: sha512-GPtGJgSm92crJhhhwUsaMw3rz2KfJWWSz7kRAlufFEV/EHTP5+6r6/Z1BCGRna830i+Avqbm435XLOtA7PVJwA==}
+
+ '@cropper/element-crosshair@2.0.0':
+ resolution: {integrity: sha512-KfPfyrdeFvUC31Ws7ATtcalWWSaMtrC6bMoCipZhqbUOE7wZoL4ecDSL6BUOZxPa74awZUqfzirCDjHvheBfyw==}
+
+ '@cropper/element-grid@2.0.0':
+ resolution: {integrity: sha512-i78SQ0IJTLFveKX6P7svkfMYVdgHrQ8ZmmEw8keFy9n1ZVbK+SK0UHK5FNMRNI/gtVhKJOGEnK/zeyjUdj4Iyw==}
- '@cropper/element-crosshair@2.0.0-rc.2':
- resolution: {integrity: sha512-yopINLvaZhL3E2GNienju1zeQ1Cifkn5f/0R7ZabXcAgUI0s2sLzNqL8+2XV2J3DzEzYEIYc+49KmMle04nVWQ==}
+ '@cropper/element-handle@2.0.0':
+ resolution: {integrity: sha512-ZJvW+0MkK9E8xYymGdoruaQn2kwjSHFpNSWinjyq6csuVQiCPxlX5ovAEDldmZ9MWePPtWEi3vLKQOo2Yb0T8g==}
- '@cropper/element-grid@2.0.0-rc.2':
- resolution: {integrity: sha512-PzAfEya6CmIc/o/lcA/NZ1rohszz42wjq2z3E2zq2jMfNDxY/EIoFnGI6+hJrxCAaoKD8UlKOEHQdRQbtnjcMg==}
+ '@cropper/element-image@2.0.0':
+ resolution: {integrity: sha512-9BxiTS/aHRmrjopaFQb9mQQXmx4ruhYHGkDZMVz24AXpMFjUY6OpqrWse/WjzD9tfhMFvEdu17b3VAekcAgpeg==}
- '@cropper/element-handle@2.0.0-rc.2':
- resolution: {integrity: sha512-wOWX4xpryxKcrhnJC2mHebqQQ622UN2oyQoDZcaMzvlwt7nnX3bInF+SFrIj9/aCxtCUYY0oD2gaJkfd6aNJ0g==}
+ '@cropper/element-selection@2.0.0':
+ resolution: {integrity: sha512-ensNnbIfJsJ8bhbJTH/RXtk2URFvTOO4TvfRk461n2FPEC588D7rwBmUJxQg74IiTi4y1JbCI+6j+4LyzYBLCQ==}
- '@cropper/element-image@2.0.0-rc.2':
- resolution: {integrity: sha512-RTKnuJrqn1K8FscS11auit2W57AG04mxRNOxBldYs3lKTkwZjzJdQFkZ/Nxu+cwVXT+c6IeEiayNKvu4B7CAQg==}
+ '@cropper/element-shade@2.0.0':
+ resolution: {integrity: sha512-jv/2bbNZnhU4W+T4G0c8ADocLIZvQFTXgCf2RFDNhI5UVxurzWBnDdb8Mx8LnVplnkTqO+xUmHZYve0CwgWo+Q==}
- '@cropper/element-selection@2.0.0-rc.2':
- resolution: {integrity: sha512-UIgIHKHz4qNKlm5YRnC/Pu9+VrInm5TSOzkmU8kPt2swUk0WHNRv3ZcOjCQZ2ccTQnAH3FVM3FYDZ8HjRwLcBg==}
+ '@cropper/element-viewer@2.0.0':
+ resolution: {integrity: sha512-zY+3VRN5TvpM8twlphYtXw0tzJL2VgzeK7ufhL1BixVqOdRxwP13TprYIhqwGt9EW/SyJZUiaIu396T89kRX8A==}
- '@cropper/element-shade@2.0.0-rc.2':
- resolution: {integrity: sha512-vHAGFxlqgflGZWkRYNWNHUY0zsV72YZGmCgtUu4sMrnWLZL/jMGhxmm8zZCe/aB94F829XcQ6uf3BoiApB+7Ng==}
+ '@cropper/element@2.0.0':
+ resolution: {integrity: sha512-lsthn0nQq73GExUE7Mg/ss6Q3RXADGDv055hxoLFwvl/wGHgy6ZkYlfLZ/VmgBHC6jDK5IgPBFnqrPqlXWSGBA==}
- '@cropper/element-viewer@2.0.0-rc.2':
- resolution: {integrity: sha512-2z9mIA7ic3enNS4xvq9Gq6hnRZ1tPr0h+lCrOHP55NL4he63lE9oTVJfDx19rL95wUS4VxL2ANvr2BVLNiBM7A==}
+ '@cropper/elements@2.0.0':
+ resolution: {integrity: sha512-PQkPo1nUjxLFUQuHYu+6atfHxpX9B41Xribao6wpvmvmNIFML6LQdNqqWYb6LyM7ujsu71CZdBiMT5oetjJVoQ==}
- '@cropper/element@2.0.0-rc.2':
- resolution: {integrity: sha512-4G6lTJblndwzpsb43YKeHiKcocOkDIWystGzbHNbqRysE0U0lYHuRyvV7FW6a9S63wtMFSYuwFxcdUdUcmkF8w==}
+ '@cropper/utils@2.0.0':
+ resolution: {integrity: sha512-cprLYr+7kK3faGgoOsTW9gIn5sefDr2KwOmgyjzIXk+8PLpW8FgFKEg5FoWfRD5zMAmkCBuX6rGKDK3VdUEGrg==}
+
+ '@csstools/color-helpers@5.0.2':
+ resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
+ engines: {node: '>=18'}
- '@cropper/elements@2.0.0-rc.2':
- resolution: {integrity: sha512-NG5kdqpv7/tGvUfNjJiIHr2Ip431v5t/P5cIXTcYAgt8PRyFJmjx3fatC7NLnP/FUlv+bbzd8PMRI4LY4Gaw3Q==}
+ '@csstools/css-calc@2.1.2':
+ resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.4
+ '@csstools/css-tokenizer': ^3.0.3
- '@cropper/utils@2.0.0-rc.2':
- resolution: {integrity: sha512-EEivNsyV6BtL496m4Q/IeAC6FGlyKjKIT1qMtwaxtkR+2ZlKnf9O7AdcGpClemIBA+TbwWAzp0UyIvYFtKUZ1Q==}
+ '@csstools/css-color-parser@3.0.8':
+ resolution: {integrity: sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.4
+ '@csstools/css-tokenizer': ^3.0.3
+
+ '@csstools/css-parser-algorithms@3.0.4':
+ resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.3
+
+ '@csstools/css-tokenizer@3.0.3':
+ resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
+ engines: {node: '>=18'}
'@cypress/request@3.0.6':
resolution: {integrity: sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==}
@@ -2076,416 +2114,302 @@ packages:
'@emnapi/runtime@1.3.0':
resolution: {integrity: sha512-XMBySMuNZs3DM96xcJmLW4EfGnf+uGmFNjzpehMjuX5PLB5j87ar2Zc4e3PVeZ3I5g3tYtAqskB28manlF69Zw==}
- '@esbuild/aix-ppc64@0.21.5':
- resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
- engines: {node: '>=12'}
+ '@esbuild/aix-ppc64@0.25.0':
+ resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==}
+ engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
- '@esbuild/aix-ppc64@0.24.0':
- resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==}
+ '@esbuild/aix-ppc64@0.25.1':
+ resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
- '@esbuild/android-arm64@0.18.20':
- resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
- 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'}
+ '@esbuild/android-arm64@0.25.0':
+ resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==}
+ engines: {node: '>=18'}
cpu: [arm64]
os: [android]
- '@esbuild/android-arm64@0.24.0':
- resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==}
+ '@esbuild/android-arm64@0.25.1':
+ resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
- '@esbuild/android-arm@0.18.20':
- resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [android]
-
- '@esbuild/android-arm@0.21.5':
- resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
- engines: {node: '>=12'}
+ '@esbuild/android-arm@0.25.0':
+ resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==}
+ engines: {node: '>=18'}
cpu: [arm]
os: [android]
- '@esbuild/android-arm@0.24.0':
- resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==}
+ '@esbuild/android-arm@0.25.1':
+ resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
- '@esbuild/android-x64@0.18.20':
- resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
-
- '@esbuild/android-x64@0.21.5':
- resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
- engines: {node: '>=12'}
+ '@esbuild/android-x64@0.25.0':
+ resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [android]
- '@esbuild/android-x64@0.24.0':
- resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==}
+ '@esbuild/android-x64@0.25.1':
+ resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
- '@esbuild/darwin-arm64@0.18.20':
- resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
-
- '@esbuild/darwin-arm64@0.21.5':
- resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
- engines: {node: '>=12'}
+ '@esbuild/darwin-arm64@0.25.0':
+ resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==}
+ engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
- '@esbuild/darwin-arm64@0.24.0':
- resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==}
+ '@esbuild/darwin-arm64@0.25.1':
+ resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
- '@esbuild/darwin-x64@0.18.20':
- resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
-
- '@esbuild/darwin-x64@0.21.5':
- resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
- engines: {node: '>=12'}
+ '@esbuild/darwin-x64@0.25.0':
+ resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
- '@esbuild/darwin-x64@0.24.0':
- resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==}
+ '@esbuild/darwin-x64@0.25.1':
+ resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
- '@esbuild/freebsd-arm64@0.18.20':
- resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
-
- '@esbuild/freebsd-arm64@0.21.5':
- resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
- engines: {node: '>=12'}
+ '@esbuild/freebsd-arm64@0.25.0':
+ resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==}
+ engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
- '@esbuild/freebsd-arm64@0.24.0':
- resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==}
+ '@esbuild/freebsd-arm64@0.25.1':
+ resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
- '@esbuild/freebsd-x64@0.18.20':
- resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
- 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'}
+ '@esbuild/freebsd-x64@0.25.0':
+ resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
- '@esbuild/freebsd-x64@0.24.0':
- resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==}
+ '@esbuild/freebsd-x64@0.25.1':
+ resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
- '@esbuild/linux-arm64@0.18.20':
- resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
-
- '@esbuild/linux-arm64@0.21.5':
- resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
- engines: {node: '>=12'}
+ '@esbuild/linux-arm64@0.25.0':
+ resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==}
+ engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
- '@esbuild/linux-arm64@0.24.0':
- resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==}
+ '@esbuild/linux-arm64@0.25.1':
+ resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
- '@esbuild/linux-arm@0.18.20':
- resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
-
- '@esbuild/linux-arm@0.21.5':
- resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
- engines: {node: '>=12'}
+ '@esbuild/linux-arm@0.25.0':
+ resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==}
+ engines: {node: '>=18'}
cpu: [arm]
os: [linux]
- '@esbuild/linux-arm@0.24.0':
- resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==}
+ '@esbuild/linux-arm@0.25.1':
+ resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
- '@esbuild/linux-ia32@0.18.20':
- resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
-
- '@esbuild/linux-ia32@0.21.5':
- resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
- engines: {node: '>=12'}
+ '@esbuild/linux-ia32@0.25.0':
+ resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==}
+ engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
- '@esbuild/linux-ia32@0.24.0':
- resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==}
+ '@esbuild/linux-ia32@0.25.1':
+ resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
- '@esbuild/linux-loong64@0.18.20':
- resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
- engines: {node: '>=12'}
- cpu: [loong64]
- os: [linux]
-
- '@esbuild/linux-loong64@0.21.5':
- resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
- engines: {node: '>=12'}
+ '@esbuild/linux-loong64@0.25.0':
+ resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==}
+ engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
- '@esbuild/linux-loong64@0.24.0':
- resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==}
+ '@esbuild/linux-loong64@0.25.1':
+ resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
- '@esbuild/linux-mips64el@0.18.20':
- resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
-
- '@esbuild/linux-mips64el@0.21.5':
- resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
- engines: {node: '>=12'}
+ '@esbuild/linux-mips64el@0.25.0':
+ resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==}
+ engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
- '@esbuild/linux-mips64el@0.24.0':
- resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==}
+ '@esbuild/linux-mips64el@0.25.1':
+ resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
- '@esbuild/linux-ppc64@0.18.20':
- resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
- 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'}
+ '@esbuild/linux-ppc64@0.25.0':
+ resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==}
+ engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
- '@esbuild/linux-ppc64@0.24.0':
- resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==}
+ '@esbuild/linux-ppc64@0.25.1':
+ resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
- '@esbuild/linux-riscv64@0.18.20':
- resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
-
- '@esbuild/linux-riscv64@0.21.5':
- resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
- engines: {node: '>=12'}
+ '@esbuild/linux-riscv64@0.25.0':
+ resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==}
+ engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
- '@esbuild/linux-riscv64@0.24.0':
- resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==}
+ '@esbuild/linux-riscv64@0.25.1':
+ resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
- '@esbuild/linux-s390x@0.18.20':
- resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
-
- '@esbuild/linux-s390x@0.21.5':
- resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
- engines: {node: '>=12'}
+ '@esbuild/linux-s390x@0.25.0':
+ resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==}
+ engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
- '@esbuild/linux-s390x@0.24.0':
- resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==}
+ '@esbuild/linux-s390x@0.25.1':
+ resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
- '@esbuild/linux-x64@0.18.20':
- resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
- engines: {node: '>=12'}
+ '@esbuild/linux-x64@0.25.0':
+ resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [linux]
- '@esbuild/linux-x64@0.21.5':
- resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
- engines: {node: '>=12'}
+ '@esbuild/linux-x64@0.25.1':
+ resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [linux]
- '@esbuild/linux-x64@0.24.0':
- resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==}
+ '@esbuild/netbsd-arm64@0.25.0':
+ resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==}
engines: {node: '>=18'}
- cpu: [x64]
- os: [linux]
+ cpu: [arm64]
+ os: [netbsd]
- '@esbuild/netbsd-x64@0.18.20':
- resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
- engines: {node: '>=12'}
- cpu: [x64]
+ '@esbuild/netbsd-arm64@0.25.1':
+ resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
os: [netbsd]
- '@esbuild/netbsd-x64@0.21.5':
- resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
- engines: {node: '>=12'}
+ '@esbuild/netbsd-x64@0.25.0':
+ resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
- '@esbuild/netbsd-x64@0.24.0':
- resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==}
+ '@esbuild/netbsd-x64@0.25.1':
+ resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
- '@esbuild/openbsd-arm64@0.24.0':
- resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==}
+ '@esbuild/openbsd-arm64@0.25.0':
+ resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.18.20':
- resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
- engines: {node: '>=12'}
- cpu: [x64]
+ '@esbuild/openbsd-arm64@0.25.1':
+ resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.21.5':
- resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
- engines: {node: '>=12'}
+ '@esbuild/openbsd-x64@0.25.0':
+ resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.24.0':
- resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==}
+ '@esbuild/openbsd-x64@0.25.1':
+ resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
- '@esbuild/sunos-x64@0.18.20':
- resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
-
- '@esbuild/sunos-x64@0.21.5':
- resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
- engines: {node: '>=12'}
+ '@esbuild/sunos-x64@0.25.0':
+ resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/sunos-x64@0.24.0':
- resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==}
+ '@esbuild/sunos-x64@0.25.1':
+ resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/win32-arm64@0.18.20':
- resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
-
- '@esbuild/win32-arm64@0.21.5':
- resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
- engines: {node: '>=12'}
+ '@esbuild/win32-arm64@0.25.0':
+ resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==}
+ engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
- '@esbuild/win32-arm64@0.24.0':
- resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==}
+ '@esbuild/win32-arm64@0.25.1':
+ resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
- '@esbuild/win32-ia32@0.18.20':
- resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
-
- '@esbuild/win32-ia32@0.21.5':
- resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
- engines: {node: '>=12'}
+ '@esbuild/win32-ia32@0.25.0':
+ resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==}
+ engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
- '@esbuild/win32-ia32@0.24.0':
- resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==}
+ '@esbuild/win32-ia32@0.25.1':
+ resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
- '@esbuild/win32-x64@0.18.20':
- resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
-
- '@esbuild/win32-x64@0.21.5':
- resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
- engines: {node: '>=12'}
+ '@esbuild/win32-x64@0.25.0':
+ resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==}
+ engines: {node: '>=18'}
cpu: [x64]
os: [win32]
- '@esbuild/win32-x64@0.24.0':
- resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==}
+ '@esbuild/win32-x64@0.25.1':
+ resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@@ -2504,10 +2428,6 @@ packages:
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}
-
'@eslint/compat@1.1.1':
resolution: {integrity: sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2516,6 +2436,18 @@ packages:
resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/config-array@0.19.2':
+ resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.1.0':
+ resolution: {integrity: sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.12.0':
+ resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==}
+ 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}
@@ -2528,6 +2460,10 @@ packages:
resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/eslintrc@3.3.1':
+ resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@eslint/js@8.57.0':
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2536,19 +2472,31 @@ packages:
resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/js@9.22.0':
+ resolution: {integrity: sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==}
+ 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/object-schema@2.1.6':
+ resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
+ 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}
+ '@eslint/plugin-kit@0.2.7':
+ resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==}
+ 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.1':
- resolution: {integrity: sha512-8ji2MGTbceSnAXKYx/U9iWt6Fmf0zJovh0meO5rpwYS/vy0Z3QIR2J/hKmbcTpYfMu5NUliNpsAtMavmzBQhmA==}
+ '@fastify/accepts@5.0.2':
+ resolution: {integrity: sha512-pX0OrioMz3C2cuYFOGRCNMdP3sR6daTFjeSNFrWlZVutawpPIGI5opK5h4Qa6x6C9oavdDkAjA16orneE2jAFQ==}
'@fastify/ajv-compiler@4.0.1':
resolution: {integrity: sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==}
@@ -2564,11 +2512,11 @@ packages:
'@fastify/busboy@3.0.0':
resolution: {integrity: sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==}
- '@fastify/cookie@11.0.1':
- resolution: {integrity: sha512-n1Ooz4bgQ5LcOlJQboWPfsMNxIrGV0SgU85UkctdpTlCQE0mtA3rlspOPUdqk9ubiiZn053ucnia4DjTquI4/g==}
+ '@fastify/cookie@11.0.2':
+ resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==}
- '@fastify/cors@10.0.1':
- resolution: {integrity: sha512-O8JIf6448uQbOgzSkCqhClw6gFTAqrdfeA6R3fc/3gwTJGUp7gl8/3tbNB+6INuu4RmgVOq99BmvdGbtu5pgOA==}
+ '@fastify/cors@10.1.0':
+ resolution: {integrity: sha512-MZyBCBJtII60CU9Xme/iE4aEy8G7QpzGR8zkdXZkDFt7ElEMachbE61tfhAG/bvSaULlqlf0huMT12T7iqEmdQ==}
'@fastify/deepmerge@2.0.0':
resolution: {integrity: sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==}
@@ -2576,32 +2524,38 @@ packages:
'@fastify/error@4.0.0':
resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==}
- '@fastify/express@4.0.1':
- resolution: {integrity: sha512-mEQ6pawaENeZ3swqVtkxdLi8NQC5eKBkclE+7ma1qQMuB+yI6WxDyEp55pdbqPIqBQTN/cGgHv84qxVS7NKC2Q==}
+ '@fastify/express@4.0.2':
+ resolution: {integrity: sha512-lzu9MLdjlsK4Q2RiqEAwTgwQPrWQVP0kmbgAi/w9rIUqtnacjKvj3EHVTR6PIvXDs6Ut1jnTHiGbuNxHTsZwHQ==}
'@fastify/fast-json-stringify-compiler@5.0.1':
resolution: {integrity: sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==}
- '@fastify/http-proxy@10.0.1':
- resolution: {integrity: sha512-wCMwI9RXK5ISe9G1FGPDCCD2KlSAuLtDDU8XBEfiBxYV0nt+aYm4vPhU/0+IhUM6t+r7UWiV+9OYaJxcTem9+g==}
+ '@fastify/forwarded@3.0.0':
+ resolution: {integrity: sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==}
+
+ '@fastify/http-proxy@10.0.2':
+ resolution: {integrity: sha512-3IlTjOu9xtX5UPd/ZxY3ebYb6caINuBlr7iyVxYmh3zZLV0K5ted8yfU9UCNXVNs33RwbXD2RhDucc3z5BNgEA==}
'@fastify/merge-json-schemas@0.1.1':
resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==}
- '@fastify/multipart@9.0.1':
- resolution: {integrity: sha512-vt2gOCw/O4EwpN4KlLVJxth4iQlDf7T5ggw2Db2C+UbO2WJBG7y0jEBvu/HT6JIW/lBYaqrrUy9MmTpCKgXEpw==}
+ '@fastify/multipart@9.0.3':
+ resolution: {integrity: sha512-pJogxQCrT12/6I5Fh6jr3narwcymA0pv4B0jbC7c6Bl9wnrxomEUnV0d26w6gUls7gSXmhG8JGRMmHFIPsxt1g==}
+
+ '@fastify/proxy-addr@5.0.0':
+ resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==}
'@fastify/reply-from@11.0.1':
resolution: {integrity: sha512-F2Qk88gcqIIiug9V+4I6WeeV1faj1Wu798JyOnwbJcjQhm4LYrHdkpFSVwJE0g1cVjYCFFmH3OVh1HHaninttQ==}
- '@fastify/send@3.1.1':
- resolution: {integrity: sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==}
+ '@fastify/send@3.3.1':
+ resolution: {integrity: sha512-6pofeVwaHN+E/MAofCwDqkWUliE3i++jlD0VH/LOfU8TJlCkMUSgKvA9bawDdVXxjve7XrdYMyDmkiYaoGWEtA==}
- '@fastify/static@8.0.2':
- resolution: {integrity: sha512-xJ+XaZVl4Y+lKztx8jGi+BE73aByhOmjMgaTx98E4XtVZxUpiaYQIMBlwACsJz+xohm0kvzV34BZoiZ+bsJtBQ==}
+ '@fastify/static@8.1.1':
+ resolution: {integrity: sha512-TW9eyVHJLytZNpBlSIqd0bl1giJkEaRaPZG+5AT3L/OBKq9U8D7g/OYmc2NPQZnzPURGhMt3IAWuyVkvd2nOkQ==}
- '@fastify/view@10.0.1':
- resolution: {integrity: sha512-rXtBN0oVDmoRZAS7lelrCIahf+qFtlMOOas8VPdA7JvrJ9ChcF7e36pIUPU0Vbs3KmHxESUb7XatavUZEe/k5Q==}
+ '@fastify/view@10.0.2':
+ resolution: {integrity: sha512-tGjXFyDUMj5a+E8BBrQ2wpsVnpOfMq3cqy4WD8pnjWPE/HGNItBASUPoPUcX/QjPhxfuZZTYv2XdCmKXdcMZPw==}
'@github/webauthn-json@2.1.1':
resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==}
@@ -2661,6 +2615,10 @@ packages:
resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==}
engines: {node: '>=18.18'}
+ '@humanwhocodes/retry@0.4.2':
+ resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
+ 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}
@@ -2793,6 +2751,10 @@ packages:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
+ '@isaacs/fs-minipass@4.0.1':
+ resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+ engines: {node: '>=18.0.0'}
+
'@istanbuljs/load-nyc-config@1.1.0':
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
engines: {node: '>=8'}
@@ -2871,11 +2833,11 @@ packages:
resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0':
- resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==}
+ '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0':
+ resolution: {integrity: sha512-qYDdL7fPwLRI+bJNurVcis+tNgJmvWjH4YTBGXTA8xMuxFrnAz6E5o35iyzyKbq5J5Lr8mJGfrR5GXl+WGwhgQ==}
peerDependencies:
typescript: '>= 4.3.x'
- vite: ^3.0.0 || ^4.0.0 || ^5.0.0
+ vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
peerDependenciesMeta:
typescript:
optional: true
@@ -2898,9 +2860,6 @@ packages:
'@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==}
@@ -2939,41 +2898,38 @@ packages:
'@types/react': '>=16'
react: '>=16'
- '@microsoft/api-extractor-model@7.29.8':
- resolution: {integrity: sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g==}
+ '@microsoft/api-extractor-model@7.30.3':
+ resolution: {integrity: sha512-yEAvq0F78MmStXdqz9TTT4PZ05Xu5R8nqgwI5xmUmQjWBQ9E6R2n8HB/iZMRciG4rf9iwI2mtuQwIzDXBvHn1w==}
- '@microsoft/api-extractor@7.47.11':
- resolution: {integrity: sha512-lrudfbPub5wzBhymfFtgZKuBvXxoSIAdrvS2UbHjoMT2TjIEddq6Z13pcve7A03BAouw0x8sW8G4txdgfiSwpQ==}
+ '@microsoft/api-extractor@7.51.1':
+ resolution: {integrity: sha512-VoFvIeYXme8QctXDkixy1KIn750kZaFy2snAEOB3nhDFfbBcJNEcvBrpCIQIV09MqI4g9egKUkg+/12WMRC77w==}
hasBin: true
- '@microsoft/tsdoc-config@0.17.0':
- resolution: {integrity: sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==}
+ '@microsoft/tsdoc-config@0.17.1':
+ resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==}
- '@microsoft/tsdoc@0.15.0':
- resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==}
+ '@microsoft/tsdoc@0.15.1':
+ resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==}
'@misskey-dev/browser-image-resizer@2024.1.0':
resolution: {integrity: sha512-4EnO0zLW5NDtng3Gaz5MuT761uiuoOuplwX18wBqgj8w56LTU5BjLn/vbHwDIIe0j2gwqDYhMb7bDjmr1/Fomg==}
- '@misskey-dev/eslint-plugin@2.0.3':
- resolution: {integrity: sha512-Gd7chezh53Gq4BZ+Tw/uRi4t5DocXR+vTFuTSRpwrZjbyTk6tWMHLV0EtaGY/6GT+Q6WH3UMJYExsFyHFK+JPw==}
+ '@misskey-dev/eslint-plugin@2.1.0':
+ resolution: {integrity: sha512-f++Vv1r3BQyGqEE0SB5algUZwRoTMZIYfVtpcuQ2fLuYUm0cQ5BBTs/gwAHPajVB2YD8F33gzPIReTKtuJyCwQ==}
peerDependencies:
'@eslint/compat': '>= 1'
- '@typescript-eslint/eslint-plugin': '>= 7'
- '@typescript-eslint/parser': '>= 7'
- eslint: '>= 8'
+ '@stylistic/eslint-plugin': '>= 2'
+ '@typescript-eslint/eslint-plugin': '>= 8'
+ '@typescript-eslint/parser': '>= 8'
+ eslint: '>= 9'
eslint-plugin-import: '>= 2'
globals: '>= 15'
'@misskey-dev/sharp-read-bmp@1.2.0':
resolution: {integrity: sha512-er4pRakXzHYfEgOFAFfQagqDouG+wLm+kwNq1I30oSdIHDa0wM3KjFpfIGQ25Fks4GcmOl1s7Zh6xoQu5dNjTw==}
- '@misskey-dev/summaly@5.1.0':
- resolution: {integrity: sha512-WAUrgX3/z4h4aI8Y/WVwmJcJ6Fa1Zf2LJCSS651t9MHoWVGABLsQ2KCXRGmlpk4i+cMDNIwweObUroosE7j8rg==}
-
- '@mole-inc/bin-wrapper@8.0.1':
- resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ '@misskey-dev/summaly@5.2.0':
+ resolution: {integrity: sha512-Djg6emAOdcY81a8TMTFi/qgIdTO96Z2qZfhHE+k+YRd8z9V/5MxauJr+U9z4kUVjSJJ1yzdOWqi26OsmVUaXZA==}
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2':
resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
@@ -3005,12 +2961,12 @@ packages:
cpu: [x64]
os: [win32]
- '@mswjs/interceptors@0.36.10':
- resolution: {integrity: sha512-GXrJgakgJW3DWKueebkvtYgGKkxA7s0u5B0P5syJM5rvQUnrpLPigvci8Hukl7yEM+sU06l+er2Fgvx/gmiRgg==}
+ '@mswjs/interceptors@0.37.6':
+ resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==}
engines: {node: '>=18'}
- '@nestjs/common@10.4.7':
- resolution: {integrity: sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w==}
+ '@nestjs/common@11.0.12':
+ resolution: {integrity: sha512-6PXxmDe2iYmb57xacnxzpW1NAxRZ7Gf+acMT7/hmRB/4KpZiFU/cNvLWwgbM2BL5QSzQulOwY6ny5bbKnPpB+A==}
peerDependencies:
class-transformer: '*'
class-validator: '*'
@@ -3022,13 +2978,14 @@ packages:
class-validator:
optional: true
- '@nestjs/core@10.4.7':
- resolution: {integrity: sha512-AIpQzW/vGGqSLkKvll1R7uaSNv99AxZI2EFyVJPNGDgFsfXaohfV1Ukl6f+s75Km+6Fj/7aNl80EqzNWQCS8Ig==}
+ '@nestjs/core@11.0.12':
+ resolution: {integrity: sha512-micQrbh9iL0PuYVx2vsUojuNmMUyqoMCuj7eGAUhvjiZUh4DBLPdxYmJEayCT/equHSiw9vNC95Vm0JigVZ44g==}
+ engines: {node: '>= 20'}
peerDependencies:
- '@nestjs/common': ^10.0.0
- '@nestjs/microservices': ^10.0.0
- '@nestjs/platform-express': ^10.0.0
- '@nestjs/websockets': ^10.0.0
+ '@nestjs/common': ^11.0.0
+ '@nestjs/microservices': ^11.0.0
+ '@nestjs/platform-express': ^11.0.0
+ '@nestjs/websockets': ^11.0.0
reflect-metadata: ^0.1.12 || ^0.2.0
rxjs: ^7.1.0
peerDependenciesMeta:
@@ -3039,27 +2996,27 @@ packages:
'@nestjs/websockets':
optional: true
- '@nestjs/platform-express@10.4.7':
- resolution: {integrity: sha512-q6XDOxZPTZ9cxALcVuKUlRBk+cVEv6dW2S8p2yVre22kpEQxq53/OI8EseDvzObGb6hepZ8+yBY04qoYqSlXNQ==}
+ '@nestjs/platform-express@10.4.15':
+ resolution: {integrity: sha512-63ZZPkXHjoDyO7ahGOVcybZCRa7/Scp6mObQKjcX/fTEq1YJeU75ELvMsuQgc8U2opMGOBD7GVuc4DV0oeDHoA==}
peerDependencies:
'@nestjs/common': ^10.0.0
'@nestjs/core': ^10.0.0
- '@nestjs/testing@10.4.7':
- resolution: {integrity: sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==}
+ '@nestjs/testing@11.0.12':
+ resolution: {integrity: sha512-jl1McTqrY+zRBFIWcFMVwesY2v++mAdHrrzXsLxatgkf6wRVh6te1MQ6LikgQ6qz4P5qzVV6EiXQVLGvARe5Xw==}
peerDependencies:
- '@nestjs/common': ^10.0.0
- '@nestjs/core': ^10.0.0
- '@nestjs/microservices': ^10.0.0
- '@nestjs/platform-express': ^10.0.0
+ '@nestjs/common': ^11.0.0
+ '@nestjs/core': ^11.0.0
+ '@nestjs/microservices': ^11.0.0
+ '@nestjs/platform-express': ^11.0.0
peerDependenciesMeta:
'@nestjs/microservices':
optional: true
'@nestjs/platform-express':
optional: true
- '@noble/hashes@1.5.0':
- resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
+ '@noble/hashes@1.6.1':
+ resolution: {integrity: sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==}
engines: {node: ^14.21.3 || >=16}
'@nodelib/fs.scandir@2.1.5':
@@ -3082,9 +3039,9 @@ packages:
resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
- '@nuxtjs/opencollective@0.3.2':
- resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==}
- engines: {node: '>=8.0.0', npm: '>=5.0.0'}
+ '@nuxt/opencollective@0.4.1':
+ resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==}
+ engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'}
hasBin: true
'@one-ini/wasm@0.1.1':
@@ -3099,204 +3056,192 @@ packages:
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
- '@opentelemetry/api-logs@0.52.1':
- 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==}
+ '@opentelemetry/api-logs@0.57.1':
+ resolution: {integrity: sha512-I4PHczeujhQAQv6ZBzqHYEUiggZL4IdSMixtVD3EYqbdrjujE7kRfI5QohjlPoJm8BvenoW5YaTMWRrbpot6tg==}
+ engines: {node: '>=14'}
+
+ '@opentelemetry/api-logs@0.57.2':
+ resolution: {integrity: sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==}
engines: {node: '>=14'}
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
- '@opentelemetry/context-async-hooks@1.25.1':
- resolution: {integrity: sha512-UW/ge9zjvAEmRWVapOP0qyCvPulWU6cQxGxDbWEFfGOj1VBBZAuOqTo3X6yWmDTD3Xe15ysCZChHncr2xFMIfQ==}
+ '@opentelemetry/context-async-hooks@1.30.1':
+ resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
- '@opentelemetry/core@1.25.1':
- resolution: {integrity: sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==}
+ '@opentelemetry/core@1.30.1':
+ resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==}
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.10.0'
-
- '@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-amqplib@0.43.0':
- resolution: {integrity: sha512-ALjfQC+0dnIEcvNYsbZl/VLh7D2P1HhFF4vicRKHhHFIUV3Shpg4kXgiek5PLhmeKSIPiUB25IYH5RIneclL4A==}
+ '@opentelemetry/instrumentation-amqplib@0.46.1':
+ resolution: {integrity: sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-connect@0.40.0':
- resolution: {integrity: sha512-3aR/3YBQ160siitwwRLjwqrv2KBT16897+bo6yz8wIfel6nWOxTZBJudcbsK3p42pTC7qrbotJ9t/1wRLpv79Q==}
+ '@opentelemetry/instrumentation-connect@0.43.0':
+ resolution: {integrity: sha512-Q57JGpH6T4dkYHo9tKXONgLtxzsh1ZEW5M9A/OwKrZFyEpLqWgjhcZ3hIuVvDlhb426iDF1f9FPToV/mi5rpeA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-dataloader@0.12.0':
- resolution: {integrity: sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==}
+ '@opentelemetry/instrumentation-dataloader@0.16.0':
+ resolution: {integrity: sha512-88+qCHZC02up8PwKHk0UQKLLqGGURzS3hFQBZC7PnGwReuoKjHXS1o29H58S+QkXJpkTr2GACbx8j6mUoGjNPA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-express@0.44.0':
- resolution: {integrity: sha512-GWgibp6Q0wxyFaaU8ERIgMMYgzcHmGrw3ILUtGchLtLncHNOKk0SNoWGqiylXWWT4HTn5XdV8MGawUgpZh80cA==}
+ '@opentelemetry/instrumentation-express@0.47.0':
+ resolution: {integrity: sha512-XFWVx6k0XlU8lu6cBlCa29ONtVt6ADEjmxtyAyeF2+rifk8uBJbk1La0yIVfI0DoKURGbaEDTNelaXG9l/lNNQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-fastify@0.41.0':
- resolution: {integrity: sha512-pNRjFvf0mvqfJueaeL/qEkuGJwgtE5pgjIHGYwjc2rMViNCrtY9/Sf+Nu8ww6dDd/Oyk2fwZZP7i0XZfCnETrA==}
+ '@opentelemetry/instrumentation-fastify@0.44.1':
+ resolution: {integrity: sha512-RoVeMGKcNttNfXMSl6W4fsYoCAYP1vi6ZAWIGhBY+o7R9Y0afA7f9JJL0j8LHbyb0P0QhSYk+6O56OwI2k4iRQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-fs@0.16.0':
- resolution: {integrity: sha512-hMDRUxV38ln1R3lNz6osj3YjlO32ykbHqVrzG7gEhGXFQfu7LJUx8t9tEwE4r2h3CD4D0Rw4YGDU4yF4mP3ilg==}
+ '@opentelemetry/instrumentation-fs@0.19.0':
+ resolution: {integrity: sha512-JGwmHhBkRT2G/BYNV1aGI+bBjJu4fJUD/5/Jat0EWZa2ftrLV3YE8z84Fiij/wK32oMZ88eS8DI4ecLGZhpqsQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-generic-pool@0.39.0':
- resolution: {integrity: sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw==}
+ '@opentelemetry/instrumentation-generic-pool@0.43.0':
+ resolution: {integrity: sha512-at8GceTtNxD1NfFKGAuwtqM41ot/TpcLh+YsGe4dhf7gvv1HW/ZWdq6nfRtS6UjIvZJOokViqLPJ3GVtZItAnQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-graphql@0.44.0':
- resolution: {integrity: sha512-FYXTe3Bv96aNpYktqm86BFUTpjglKD0kWI5T5bxYkLUPEPvFn38vWGMJTGrDMVou/i55E4jlWvcm6hFIqLsMbg==}
+ '@opentelemetry/instrumentation-graphql@0.47.0':
+ resolution: {integrity: sha512-Cc8SMf+nLqp0fi8oAnooNEfwZWFnzMiBHCGmDFYqmgjPylyLmi83b+NiTns/rKGwlErpW0AGPt0sMpkbNlzn8w==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-hapi@0.41.0':
- resolution: {integrity: sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ==}
+ '@opentelemetry/instrumentation-hapi@0.45.1':
+ resolution: {integrity: sha512-VH6mU3YqAKTePPfUPwfq4/xr049774qWtfTuJqVHoVspCLiT3bW+fCQ1toZxt6cxRPYASoYaBsMA3CWo8B8rcw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-http@0.53.0':
- resolution: {integrity: sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ==}
+ '@opentelemetry/instrumentation-http@0.57.1':
+ resolution: {integrity: sha512-ThLmzAQDs7b/tdKI3BV2+yawuF09jF111OFsovqT1Qj3D8vjwKBwhi/rDE5xethwn4tSXtZcJ9hBsVAlWFQZ7g==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-ioredis@0.43.0':
- resolution: {integrity: sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig==}
+ '@opentelemetry/instrumentation-ioredis@0.47.0':
+ resolution: {integrity: sha512-4HqP9IBC8e7pW9p90P3q4ox0XlbLGme65YTrA3UTLvqvo4Z6b0puqZQP203YFu8m9rE/luLfaG7/xrwwqMUpJw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-kafkajs@0.4.0':
- resolution: {integrity: sha512-I9VwDG314g7SDL4t8kD/7+1ytaDBRbZQjhVaQaVIDR8K+mlsoBhLsWH79yHxhHQKvwCSZwqXF+TiTOhoQVUt7A==}
+ '@opentelemetry/instrumentation-kafkajs@0.7.0':
+ resolution: {integrity: sha512-LB+3xiNzc034zHfCtgs4ITWhq6Xvdo8bsq7amR058jZlf2aXXDrN9SV4si4z2ya9QX4tz6r4eZJwDkXOp14/AQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-knex@0.41.0':
- resolution: {integrity: sha512-OhI1SlLv5qnsnm2dOVrian/x3431P75GngSpnR7c4fcVFv7prXGYu29Z6ILRWJf/NJt6fkbySmwdfUUnFnHCTg==}
+ '@opentelemetry/instrumentation-knex@0.44.0':
+ resolution: {integrity: sha512-SlT0+bLA0Lg3VthGje+bSZatlGHw/vwgQywx0R/5u9QC59FddTQSPJeWNw29M6f8ScORMeUOOTwihlQAn4GkJQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-koa@0.43.0':
- resolution: {integrity: sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ==}
+ '@opentelemetry/instrumentation-koa@0.47.0':
+ resolution: {integrity: sha512-HFdvqf2+w8sWOuwtEXayGzdZ2vWpCKEQv5F7+2DSA74Te/Cv4rvb2E5So5/lh+ok4/RAIPuvCbCb/SHQFzMmbw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-lru-memoizer@0.40.0':
- resolution: {integrity: sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==}
+ '@opentelemetry/instrumentation-lru-memoizer@0.44.0':
+ resolution: {integrity: sha512-Tn7emHAlvYDFik3vGU0mdwvWJDwtITtkJ+5eT2cUquct6nIs+H8M47sqMJkCpyPe5QIBJoTOHxmc6mj9lz6zDw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-mongodb@0.48.0':
- resolution: {integrity: sha512-9YWvaGvrrcrydMsYGLu0w+RgmosLMKe3kv/UNlsPy8RLnCkN2z+bhhbjjjuxtUmvEuKZMCoXFluABVuBr1yhjw==}
+ '@opentelemetry/instrumentation-mongodb@0.51.0':
+ resolution: {integrity: sha512-cMKASxCX4aFxesoj3WK8uoQ0YUrRvnfxaO72QWI2xLu5ZtgX/QvdGBlU3Ehdond5eb74c2s1cqRQUIptBnKz1g==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-mongoose@0.42.0':
- resolution: {integrity: sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg==}
+ '@opentelemetry/instrumentation-mongoose@0.46.0':
+ resolution: {integrity: sha512-mtVv6UeaaSaWTeZtLo4cx4P5/ING2obSqfWGItIFSunQBrYROfhuVe7wdIrFUs2RH1tn2YYpAJyMaRe/bnTTIQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-mysql2@0.41.0':
- resolution: {integrity: sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g==}
+ '@opentelemetry/instrumentation-mysql2@0.45.0':
+ resolution: {integrity: sha512-qLslv/EPuLj0IXFvcE3b0EqhWI8LKmrgRPIa4gUd8DllbBpqJAvLNJSv3cC6vWwovpbSI3bagNO/3Q2SuXv2xA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-mysql@0.41.0':
- resolution: {integrity: sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw==}
+ '@opentelemetry/instrumentation-mysql@0.45.0':
+ resolution: {integrity: sha512-tWWyymgwYcTwZ4t8/rLDfPYbOTF3oYB8SxnYMtIQ1zEf5uDm90Ku3i6U/vhaMyfHNlIHvDhvJh+qx5Nc4Z3Acg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-nestjs-core@0.40.0':
- resolution: {integrity: sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg==}
+ '@opentelemetry/instrumentation-nestjs-core@0.44.0':
+ resolution: {integrity: sha512-t16pQ7A4WYu1yyQJZhRKIfUNvl5PAaF2pEteLvgJb/BWdd1oNuU1rOYt4S825kMy+0q4ngiX281Ss9qiwHfxFQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-pg@0.44.0':
- resolution: {integrity: sha512-oTWVyzKqXud1BYEGX1loo2o4k4vaU1elr3vPO8NZolrBtFvQ34nx4HgUaexUDuEog00qQt+MLR5gws/p+JXMLQ==}
+ '@opentelemetry/instrumentation-pg@0.50.0':
+ resolution: {integrity: sha512-TtLxDdYZmBhFswm8UIsrDjh/HFBeDXd4BLmE8h2MxirNHewLJ0VS9UUddKKEverb5Sm2qFVjqRjcU+8Iw4FJ3w==}
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==}
+ '@opentelemetry/instrumentation-redis-4@0.46.0':
+ resolution: {integrity: sha512-aTUWbzbFMFeRODn3720TZO0tsh/49T8H3h8vVnVKJ+yE36AeW38Uj/8zykQ/9nO8Vrtjr5yKuX3uMiG/W8FKNw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-tedious@0.15.0':
- resolution: {integrity: sha512-Kb7yo8Zsq2TUwBbmwYgTAMPK0VbhoS8ikJ6Bup9KrDtCx2JC01nCb+M0VJWXt7tl0+5jARUbKWh5jRSoImxdCw==}
+ '@opentelemetry/instrumentation-tedious@0.18.0':
+ resolution: {integrity: sha512-9zhjDpUDOtD+coeADnYEJQ0IeLVCj7w/hqzIutdp5NqS1VqTAanaEfsEcSypyvYv5DX3YOsTUoF+nr2wDXPETA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation-undici@0.6.0':
- resolution: {integrity: sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ==}
+ '@opentelemetry/instrumentation-undici@0.10.0':
+ resolution: {integrity: sha512-vm+V255NGw9gaSsPD6CP0oGo8L55BffBc8KnxqsMuc6XiAD1L8SFNzsW0RHhxJFqy9CJaJh+YiJ5EHXuZ5rZBw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.7.0
- '@opentelemetry/instrumentation@0.52.1':
- resolution: {integrity: sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==}
+ '@opentelemetry/instrumentation@0.53.0':
+ resolution: {integrity: sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation@0.53.0':
- resolution: {integrity: sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==}
+ '@opentelemetry/instrumentation@0.57.1':
+ resolution: {integrity: sha512-SgHEKXoVxOjc20ZYusPG3Fh+RLIZTSa4x8QtD3NfgAUDyqdFFS9W1F2ZVbZkqDCdyMcQG02Ok4duUGLHJXHgbA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
- '@opentelemetry/instrumentation@0.54.2':
- resolution: {integrity: sha512-go6zpOVoZVztT9r1aPd79Fr3OWiD4N24bCPJsIKkBses8oyFo12F/Ew3UBTdIu6hsW4HC4MVEJygG6TEyJI/lg==}
+ '@opentelemetry/instrumentation@0.57.2':
+ resolution: {integrity: sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
@@ -3305,22 +3250,18 @@ packages:
resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==}
engines: {node: '>=14'}
- '@opentelemetry/resources@1.28.0':
- resolution: {integrity: sha512-cIyXSVJjGeTICENN40YSvLDAq4Y2502hGK3iN7tfdynQLKWb3XWZQEkPc+eSx47kiy11YeFAlYkEfXwR1w8kfw==}
+ '@opentelemetry/resources@1.30.1':
+ resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
- '@opentelemetry/sdk-trace-base@1.28.0':
- resolution: {integrity: sha512-ceUVWuCpIao7Y5xE02Xs3nQi0tOGmMea17ecBdwtCvdo9ekmO+ijc9RFDgfifMl7XCBf41zne/1POM3LqSTZDA==}
+ '@opentelemetry/sdk-trace-base@1.30.1':
+ resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==}
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'}
@@ -3335,6 +3276,88 @@ packages:
peerDependencies:
'@opentelemetry/api': ^1.1.0
+ '@parcel/watcher-android-arm64@2.5.1':
+ resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@parcel/watcher-darwin-arm64@2.5.1':
+ resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@parcel/watcher-darwin-x64@2.5.1':
+ resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1':
+ resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1':
+ resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1':
+ resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-musl@2.5.1':
+ resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@parcel/watcher-win32-arm64@2.5.1':
+ resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@parcel/watcher-win32-x64@2.5.1':
+ resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@parcel/watcher@2.5.1':
+ resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
+ engines: {node: '>= 10.0.0'}
+
'@peculiar/asn1-android@2.3.10':
resolution: {integrity: sha512-z9Rx9cFJv7UUablZISe7uksNbFJCq13hO0yEAOoIpAymALTLlvUOSLnGiQS7okPaM5dP42oTLhezH6XDXRXjGw==}
@@ -3365,12 +3388,12 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
- '@prisma/instrumentation@5.19.1':
- resolution: {integrity: sha512-VLnzMQq7CWroL5AeaW0Py2huiNKeoMfCH3SUxstdzPrlWQi6UQ9UrfcbUkNHlVFqOMacqy8X/8YtE0kuKDpD9w==}
+ '@prisma/instrumentation@5.22.0':
+ resolution: {integrity: sha512-LxccF392NN37ISGxIurUljZSh1YWnphO34V5a0+T7FVQG2u9bhAXRTJpgmQ3483woVhkraQZFF7cbRrpbw/F4Q==}
- '@readme/better-ajv-errors@1.6.0':
- resolution: {integrity: sha512-9gO9rld84Jgu13kcbKRU+WHseNhaVt76wYMeRDGsUGYxwJtI3RmEJ9LY9dZCYQGI8eUZLuxb5qDja0nqklpFjQ==}
- engines: {node: '>=14'}
+ '@readme/better-ajv-errors@2.3.2':
+ resolution: {integrity: sha512-T4GGnRAlY3C339NhoUpgJJFsMYko9vIgFAlhgV+/vEGFw66qEY4a4TRJIAZBcX/qT1pq5DvXSme+SQODHOoBrw==}
+ engines: {node: '>=18'}
peerDependencies:
ajv: 4.11.8 - 8
@@ -3378,8 +3401,8 @@ packages:
resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==}
deprecated: This package is no longer maintained. Please use `@apidevtools/json-schema-ref-parser` instead.
- '@readme/openapi-parser@2.6.0':
- resolution: {integrity: sha512-pyFJXezWj9WI1O+gdp95CoxfY+i+Uq3kKk4zXIFuRAZi9YnHpHOpjumWWr67wkmRTw19Hskh9spyY0Iyikf3fA==}
+ '@readme/openapi-parser@2.7.0':
+ resolution: {integrity: sha512-P8WSr8WTOxilnT89tcCRKWYsG/II4sAwt1a/DIWub8xTtkrG9cCBBy/IUcvc5X8oGWN82MwcTA3uEkDrXZd/7A==}
engines: {node: '>=18'}
peerDependencies:
openapi-types: '>=7'
@@ -3397,8 +3420,8 @@ packages:
rollup:
optional: true
- '@rollup/plugin-replace@5.0.7':
- resolution: {integrity: sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==}
+ '@rollup/plugin-replace@6.0.2':
+ resolution: {integrity: sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
@@ -3406,8 +3429,8 @@ packages:
rollup:
optional: true
- '@rollup/pluginutils@5.1.3':
- resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==}
+ '@rollup/pluginutils@5.1.4':
+ resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
@@ -3415,93 +3438,98 @@ packages:
rollup:
optional: true
- '@rollup/rollup-android-arm-eabi@4.26.0':
- resolution: {integrity: sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==}
+ '@rollup/rollup-android-arm-eabi@4.36.0':
+ resolution: {integrity: sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.26.0':
- resolution: {integrity: sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==}
+ '@rollup/rollup-android-arm64@4.36.0':
+ resolution: {integrity: sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.26.0':
- resolution: {integrity: sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==}
+ '@rollup/rollup-darwin-arm64@4.36.0':
+ resolution: {integrity: sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.26.0':
- resolution: {integrity: sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==}
+ '@rollup/rollup-darwin-x64@4.36.0':
+ resolution: {integrity: sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-freebsd-arm64@4.26.0':
- resolution: {integrity: sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==}
+ '@rollup/rollup-freebsd-arm64@4.36.0':
+ resolution: {integrity: sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==}
cpu: [arm64]
os: [freebsd]
- '@rollup/rollup-freebsd-x64@4.26.0':
- resolution: {integrity: sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==}
+ '@rollup/rollup-freebsd-x64@4.36.0':
+ resolution: {integrity: sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==}
cpu: [x64]
os: [freebsd]
- '@rollup/rollup-linux-arm-gnueabihf@4.26.0':
- resolution: {integrity: sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.36.0':
+ resolution: {integrity: sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.26.0':
- resolution: {integrity: sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==}
+ '@rollup/rollup-linux-arm-musleabihf@4.36.0':
+ resolution: {integrity: sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.26.0':
- resolution: {integrity: sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==}
+ '@rollup/rollup-linux-arm64-gnu@4.36.0':
+ resolution: {integrity: sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.26.0':
- resolution: {integrity: sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==}
+ '@rollup/rollup-linux-arm64-musl@4.36.0':
+ resolution: {integrity: sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-powerpc64le-gnu@4.26.0':
- resolution: {integrity: sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==}
+ '@rollup/rollup-linux-loongarch64-gnu@4.36.0':
+ resolution: {integrity: sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.36.0':
+ resolution: {integrity: sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.26.0':
- resolution: {integrity: sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==}
+ '@rollup/rollup-linux-riscv64-gnu@4.36.0':
+ resolution: {integrity: sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.26.0':
- resolution: {integrity: sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==}
+ '@rollup/rollup-linux-s390x-gnu@4.36.0':
+ resolution: {integrity: sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==}
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.26.0':
- resolution: {integrity: sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==}
+ '@rollup/rollup-linux-x64-gnu@4.36.0':
+ resolution: {integrity: sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.26.0':
- resolution: {integrity: sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==}
+ '@rollup/rollup-linux-x64-musl@4.36.0':
+ resolution: {integrity: sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-win32-arm64-msvc@4.26.0':
- resolution: {integrity: sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==}
+ '@rollup/rollup-win32-arm64-msvc@4.36.0':
+ resolution: {integrity: sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.26.0':
- resolution: {integrity: sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==}
+ '@rollup/rollup-win32-ia32-msvc@4.36.0':
+ resolution: {integrity: sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.26.0':
- resolution: {integrity: sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==}
+ '@rollup/rollup-win32-x64-msvc@4.36.0':
+ resolution: {integrity: sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==}
cpu: [x64]
os: [win32]
@@ -3511,8 +3539,8 @@ packages:
'@ruffle-rs/ruffle@0.1.0-nightly.2024.10.15':
resolution: {integrity: sha512-NBR7BIbpOLznwc7XxrCQde1UIWMjsIBsKVkr4zziqMULM+ibOc02VekQuOchHkRTLdTIZ/se2lM4IC7dNBN7vQ==}
- '@rushstack/node-core-library@5.9.0':
- resolution: {integrity: sha512-MMsshEWkTbXqxqFxD4gcIUWQOCeBChlGczdZbHfqmNZQFLHB3yWxDFSMHFUdu2/OB9NUk7Awn5qRL+rws4HQNg==}
+ '@rushstack/node-core-library@5.11.0':
+ resolution: {integrity: sha512-I8+VzG9A0F3nH2rLpPd7hF8F7l5Xb7D+ldrWVZYegXM6CsKkvWc670RlgK3WX8/AseZfXA/vVrh0bpXe2Y2UDQ==}
peerDependencies:
'@types/node': '*'
peerDependenciesMeta:
@@ -3522,65 +3550,64 @@ packages:
'@rushstack/rig-package@0.5.3':
resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==}
- '@rushstack/terminal@0.14.2':
- resolution: {integrity: sha512-2fC1wqu1VCExKC0/L+0noVcFQEXEnoBOtCIex1TOjBzEDWcw8KzJjjj7aTP6mLxepG0XIyn9OufeFb6SFsa+sg==}
+ '@rushstack/terminal@0.15.0':
+ resolution: {integrity: sha512-vXQPRQ+vJJn4GVqxkwRe+UGgzNxdV8xuJZY2zem46Y0p3tlahucH9/hPmLGj2i9dQnUBFiRnoM9/KW7PYw8F4Q==}
peerDependencies:
'@types/node': '*'
peerDependenciesMeta:
'@types/node':
optional: true
- '@rushstack/ts-command-line@4.23.0':
- resolution: {integrity: sha512-jYREBtsxduPV6ptNq8jOKp9+yx0ld1Tb/Tkdnlj8gTjazl1sF3DwX2VbluyYrNd0meWIL0bNeer7WDf5tKFjaQ==}
+ '@rushstack/ts-command-line@4.23.5':
+ resolution: {integrity: sha512-jg70HfoK44KfSP3MTiL5rxsZH7X1ktX3cZs9Sl8eDu1/LxJSbPsh0MOFRC710lIuYYSgxWjI5AjbCBAl7u3RxA==}
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
- '@sentry/core@8.38.0':
- resolution: {integrity: sha512-sGD+5TEHU9G7X7zpyaoJxpOtwjTjvOd1f/MKBrWW2vf9UbYK+GUJrOzLhMoSWp/pHSYgvObkJkDb/HwieQjvhQ==}
+ '@sentry/core@8.55.0':
+ resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==}
engines: {node: '>=14.18'}
- '@sentry/node@8.38.0':
- resolution: {integrity: sha512-nwW0XqZFQseXYn0i6i6nKPkbjgHMBEFSF9TnK6mHHqJHHObHIZ6qu5CfvGKgxATia8JPIg9NN8XcyYOnQMi07w==}
+ '@sentry/node@8.55.0':
+ resolution: {integrity: sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==}
engines: {node: '>=14.18'}
- '@sentry/opentelemetry@8.38.0':
- resolution: {integrity: sha512-AfjmIf/v7+x2WplhkX66LyGKvrzzPeSgff9uJ0cFCC2s0yd1qA2VPuIwEyr5i/FOJOP5bvFr8tu/hz3LA4+F5Q==}
+ '@sentry/opentelemetry@8.55.0':
+ resolution: {integrity: sha512-UvatdmSr3Xf+4PLBzJNLZ2JjG1yAPWGe/VrJlJAqyTJ2gKeTzgXJJw8rp4pbvNZO8NaTGEYhhO+scLUj0UtLAQ==}
engines: {node: '>=14.18'}
peerDependencies:
'@opentelemetry/api': ^1.9.0
- '@opentelemetry/core': ^1.25.1
- '@opentelemetry/instrumentation': ^0.54.0
- '@opentelemetry/sdk-trace-base': ^1.26.0
- '@opentelemetry/semantic-conventions': ^1.27.0
+ '@opentelemetry/context-async-hooks': ^1.30.1
+ '@opentelemetry/core': ^1.30.1
+ '@opentelemetry/instrumentation': ^0.57.1
+ '@opentelemetry/sdk-trace-base': ^1.30.1
+ '@opentelemetry/semantic-conventions': ^1.28.0
- '@sentry/profiling-node@8.38.0':
- resolution: {integrity: sha512-7I+hANLQRUAciRLzz4nUehEUHYeMNKkMfu6KkBuLcD1F3x7Y/tcxyHlHl+bNKf6tyUdW7IKGb+7Pk/WKMBnZrg==}
+ '@sentry/profiling-node@8.55.0':
+ resolution: {integrity: sha512-rYrlxbMlfQLHhkBUEC7bviuja1rojCb4+TtXi4NGnB4PppZeveGeuVTdJDWt3Ed6IBd20EEYoXv4+0aETbEnpw==}
engines: {node: '>=14.18'}
hasBin: true
- '@sentry/types@8.38.0':
- resolution: {integrity: sha512-fP5H9ZX01W4Z/EYctk3mkSHi7d06cLcX2/UWqwdWbyPWI+pL2QpUPICeO/C+8SnmYx//wFj3qWDhyPCh1PdFAA==}
- engines: {node: '>=14.18'}
+ '@shikijs/core@3.2.1':
+ resolution: {integrity: sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==}
- '@sentry/utils@8.38.0':
- resolution: {integrity: sha512-3X7MgIKIx+2q5Al7QkhaRB4wV6DvzYsaeIwdqKUzGLuRjXmNgJrLoU87TAwQRmZ6Wr3IoEpThZZMNrzYPXxArw==}
- engines: {node: '>=14.18'}
+ '@shikijs/engine-javascript@3.2.1':
+ resolution: {integrity: sha512-eMdcUzN3FMQYxOmRf2rmU8frikzoSHbQDFH2hIuXsrMO+IBOCI9BeeRkCiBkcLDHeRKbOCtYMJK3D6U32ooU9Q==}
- '@shikijs/core@1.22.2':
- resolution: {integrity: sha512-bvIQcd8BEeR1yFvOYv6HDiyta2FFVePbzeowf5pPS1avczrPK+cjmaxxh0nx5QzbON7+Sv0sQfQVciO7bN72sg==}
+ '@shikijs/engine-oniguruma@3.2.1':
+ resolution: {integrity: sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==}
- '@shikijs/engine-javascript@1.22.2':
- resolution: {integrity: sha512-iOvql09ql6m+3d1vtvP8fLCVCK7BQD1pJFmHIECsujB0V32BJ0Ab6hxk1ewVSMFA58FI0pR2Had9BKZdyQrxTw==}
+ '@shikijs/langs@3.2.1':
+ resolution: {integrity: sha512-If0iDHYRSGbihiA8+7uRsgb1er1Yj11pwpX1c6HLYnizDsKAw5iaT3JXj5ZpaimXSWky/IhxTm7C6nkiYVym+A==}
- '@shikijs/engine-oniguruma@1.22.2':
- resolution: {integrity: sha512-GIZPAGzQOy56mGvWMoZRPggn0dTlBf1gutV5TdceLCZlFNqWmuc7u+CzD0Gd9vQUTgLbrt0KLzz6FNprqYAxlA==}
+ '@shikijs/themes@3.2.1':
+ resolution: {integrity: sha512-k5DKJUT8IldBvAm8WcrDT5+7GA7se6lLksR+2E3SvyqGTyFMzU2F9Gb7rmD+t+Pga1MKrYFxDIeyWjMZWM6uBQ==}
- '@shikijs/types@1.22.2':
- resolution: {integrity: sha512-NCWDa6LGZqTuzjsGfXOBWfjS/fDIbDdmVDug+7ykVe1IKT4c1gakrvlfFYp5NhAXH/lyqLM8wsAPo5wNy73Feg==}
+ '@shikijs/types@3.2.1':
+ resolution: {integrity: sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==}
- '@shikijs/vscode-textmate@9.3.0':
- resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==}
+ '@shikijs/vscode-textmate@10.0.2':
+ resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
'@sideway/address@4.1.4':
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
@@ -3594,23 +3621,16 @@ packages:
'@sideway/pinpoint@2.0.0':
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
- '@simplewebauthn/server@10.0.1':
- resolution: {integrity: sha512-djNWcRn+H+6zvihBFJSpG3fzb0NQS9c/Mw5dYOtZ9H+oDw8qn9Htqxt4cpqRvSOAfwqP7rOvE9rwqVaoGGc3hg==}
+ '@simplewebauthn/server@12.0.0':
+ resolution: {integrity: sha512-aJdTe9GikOk40U7Q5Mm/Sqkxcq4a2oPZAcLcnyqMyFqrUaOS6vdsZW8/H3Mnsw9umcr88pcgB7kozPPt+5wOBw==}
engines: {node: '>=20.0.0'}
- '@simplewebauthn/types@10.0.0':
- resolution: {integrity: sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==}
-
- '@simplewebauthn/types@11.0.0':
- resolution: {integrity: sha512-b2o0wC5u2rWts31dTgBkAtSNKGX0cvL6h8QedNsKmj8O4QoLFQFR3DBVBUlpyVEhYKA+mXGUaXbcOc4JdQ3HzA==}
+ '@simplewebauthn/types@12.0.0':
+ resolution: {integrity: sha512-q6y8MkoV8V8jB4zzp18Uyj2I7oFp2/ONL8c3j8uT06AOWu3cIChc1au71QYHrP2b+xDapkGTiv+9lX7xkTlAsA==}
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
- '@sindresorhus/is@4.6.0':
- resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
- engines: {node: '>=10'}
-
'@sindresorhus/is@5.3.0':
resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==}
engines: {node: '>=14.16'}
@@ -3619,371 +3639,390 @@ packages:
resolution: {integrity: sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==}
engines: {node: '>=18'}
+ '@sindresorhus/merge-streams@4.0.0':
+ resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
+ engines: {node: '>=18'}
+
'@sinonjs/commons@2.0.0':
resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
'@sinonjs/commons@3.0.0':
resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==}
+ '@sinonjs/commons@3.0.1':
+ resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
+
'@sinonjs/fake-timers@10.3.0':
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
'@sinonjs/fake-timers@11.2.2':
resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==}
+ '@sinonjs/fake-timers@11.3.1':
+ resolution: {integrity: sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==}
+
+ '@sinonjs/fake-timers@13.0.5':
+ resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==}
+
'@sinonjs/samsam@8.0.0':
resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==}
- '@sinonjs/text-encoding@0.7.2':
- resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==}
+ '@sinonjs/text-encoding@0.7.3':
+ resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==}
'@smithy/abort-controller@2.2.0':
resolution: {integrity: sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==}
engines: {node: '>=14.0.0'}
- '@smithy/abort-controller@3.1.1':
- resolution: {integrity: sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/abort-controller@4.0.2':
+ resolution: {integrity: sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==}
+ engines: {node: '>=18.0.0'}
- '@smithy/chunked-blob-reader-native@3.0.0':
- resolution: {integrity: sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==}
+ '@smithy/chunked-blob-reader-native@4.0.0':
+ resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==}
+ engines: {node: '>=18.0.0'}
- '@smithy/chunked-blob-reader@3.0.0':
- resolution: {integrity: sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==}
+ '@smithy/chunked-blob-reader@5.0.0':
+ resolution: {integrity: sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==}
+ engines: {node: '>=18.0.0'}
- '@smithy/config-resolver@3.0.5':
- resolution: {integrity: sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/config-resolver@4.1.0':
+ resolution: {integrity: sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==}
+ engines: {node: '>=18.0.0'}
- '@smithy/core@2.3.1':
- resolution: {integrity: sha512-BC7VMXx/1BCmRPCVzzn4HGWAtsrb7/0758EtwOGFJQrlSwJBEjCcDLNZLFoL/68JexYa2s+KmgL/UfmXdG6v1w==}
- engines: {node: '>=16.0.0'}
+ '@smithy/core@3.2.0':
+ resolution: {integrity: sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==}
+ engines: {node: '>=18.0.0'}
- '@smithy/credential-provider-imds@3.2.0':
- resolution: {integrity: sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/credential-provider-imds@4.0.2':
+ resolution: {integrity: sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==}
+ engines: {node: '>=18.0.0'}
- '@smithy/eventstream-codec@3.1.2':
- resolution: {integrity: sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==}
+ '@smithy/eventstream-codec@4.0.2':
+ resolution: {integrity: sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ==}
+ engines: {node: '>=18.0.0'}
- '@smithy/eventstream-serde-browser@3.0.5':
- resolution: {integrity: sha512-dEyiUYL/ekDfk+2Ra4GxV+xNnFoCmk1nuIXg+fMChFTrM2uI/1r9AdiTYzPqgb72yIv/NtAj6C3dG//1wwgakQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/eventstream-serde-browser@4.0.2':
+ resolution: {integrity: sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug==}
+ engines: {node: '>=18.0.0'}
- '@smithy/eventstream-serde-config-resolver@3.0.3':
- resolution: {integrity: sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/eventstream-serde-config-resolver@4.1.0':
+ resolution: {integrity: sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A==}
+ engines: {node: '>=18.0.0'}
- '@smithy/eventstream-serde-node@3.0.4':
- resolution: {integrity: sha512-mjlG0OzGAYuUpdUpflfb9zyLrBGgmQmrobNT8b42ZTsGv/J03+t24uhhtVEKG/b2jFtPIHF74Bq+VUtbzEKOKg==}
- engines: {node: '>=16.0.0'}
+ '@smithy/eventstream-serde-node@4.0.2':
+ resolution: {integrity: sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw==}
+ engines: {node: '>=18.0.0'}
- '@smithy/eventstream-serde-universal@3.0.4':
- resolution: {integrity: sha512-Od9dv8zh3PgOD7Vj4T3HSuox16n0VG8jJIM2gvKASL6aCtcS8CfHZDWe1Ik3ZXW6xBouU+45Q5wgoliWDZiJ0A==}
- engines: {node: '>=16.0.0'}
+ '@smithy/eventstream-serde-universal@4.0.2':
+ resolution: {integrity: sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng==}
+ engines: {node: '>=18.0.0'}
- '@smithy/fetch-http-handler@3.2.4':
- resolution: {integrity: sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==}
+ '@smithy/fetch-http-handler@5.0.2':
+ resolution: {integrity: sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==}
+ engines: {node: '>=18.0.0'}
- '@smithy/hash-blob-browser@3.1.2':
- resolution: {integrity: sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==}
+ '@smithy/hash-blob-browser@4.0.2':
+ resolution: {integrity: sha512-3g188Z3DyhtzfBRxpZjU8R9PpOQuYsbNnyStc/ZVS+9nVX1f6XeNOa9IrAh35HwwIZg+XWk8bFVtNINVscBP+g==}
+ engines: {node: '>=18.0.0'}
- '@smithy/hash-node@3.0.3':
- resolution: {integrity: sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==}
- engines: {node: '>=16.0.0'}
+ '@smithy/hash-node@4.0.2':
+ resolution: {integrity: sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==}
+ engines: {node: '>=18.0.0'}
- '@smithy/hash-stream-node@3.1.2':
- resolution: {integrity: sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==}
- engines: {node: '>=16.0.0'}
+ '@smithy/hash-stream-node@4.0.2':
+ resolution: {integrity: sha512-POWDuTznzbIwlEXEvvXoPMS10y0WKXK790soe57tFRfvf4zBHyzE529HpZMqmDdwG9MfFflnyzndUQ8j78ZdSg==}
+ engines: {node: '>=18.0.0'}
- '@smithy/invalid-dependency@3.0.3':
- resolution: {integrity: sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==}
+ '@smithy/invalid-dependency@4.0.2':
+ resolution: {integrity: sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==}
+ engines: {node: '>=18.0.0'}
'@smithy/is-array-buffer@2.0.0':
resolution: {integrity: sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==}
engines: {node: '>=14.0.0'}
- '@smithy/is-array-buffer@3.0.0':
- resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/is-array-buffer@4.0.0':
+ resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==}
+ engines: {node: '>=18.0.0'}
- '@smithy/md5-js@3.0.3':
- resolution: {integrity: sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==}
+ '@smithy/md5-js@4.0.2':
+ resolution: {integrity: sha512-Hc0R8EiuVunUewCse2syVgA2AfSRco3LyAv07B/zCOMa+jpXI9ll+Q21Nc6FAlYPcpNcAXqBzMhNs1CD/pP2bA==}
+ engines: {node: '>=18.0.0'}
- '@smithy/middleware-content-length@3.0.5':
- resolution: {integrity: sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==}
- engines: {node: '>=16.0.0'}
+ '@smithy/middleware-content-length@4.0.2':
+ resolution: {integrity: sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==}
+ engines: {node: '>=18.0.0'}
- '@smithy/middleware-endpoint@3.1.0':
- resolution: {integrity: sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==}
- engines: {node: '>=16.0.0'}
+ '@smithy/middleware-endpoint@4.1.0':
+ resolution: {integrity: sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==}
+ engines: {node: '>=18.0.0'}
- '@smithy/middleware-retry@3.0.13':
- resolution: {integrity: sha512-zvCLfaRYCaUmjbF2yxShGZdolSHft7NNCTA28HVN9hKcEbOH+g5irr1X9s+in8EpambclGnevZY4A3lYpvDCFw==}
- engines: {node: '>=16.0.0'}
+ '@smithy/middleware-retry@4.1.0':
+ resolution: {integrity: sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==}
+ engines: {node: '>=18.0.0'}
- '@smithy/middleware-serde@3.0.3':
- resolution: {integrity: sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/middleware-serde@4.0.3':
+ resolution: {integrity: sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==}
+ engines: {node: '>=18.0.0'}
- '@smithy/middleware-stack@3.0.3':
- resolution: {integrity: sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/middleware-stack@4.0.2':
+ resolution: {integrity: sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==}
+ engines: {node: '>=18.0.0'}
- '@smithy/node-config-provider@3.1.4':
- resolution: {integrity: sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/node-config-provider@4.0.2':
+ resolution: {integrity: sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==}
+ engines: {node: '>=18.0.0'}
'@smithy/node-http-handler@2.5.0':
resolution: {integrity: sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==}
engines: {node: '>=14.0.0'}
- '@smithy/node-http-handler@3.1.4':
- resolution: {integrity: sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==}
- engines: {node: '>=16.0.0'}
+ '@smithy/node-http-handler@4.0.4':
+ resolution: {integrity: sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==}
+ engines: {node: '>=18.0.0'}
- '@smithy/property-provider@3.1.3':
- resolution: {integrity: sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==}
- engines: {node: '>=16.0.0'}
+ '@smithy/property-provider@4.0.2':
+ resolution: {integrity: sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==}
+ engines: {node: '>=18.0.0'}
'@smithy/protocol-http@3.3.0':
resolution: {integrity: sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==}
engines: {node: '>=14.0.0'}
- '@smithy/protocol-http@4.1.0':
- resolution: {integrity: sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/protocol-http@5.1.0':
+ resolution: {integrity: sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==}
+ engines: {node: '>=18.0.0'}
'@smithy/querystring-builder@2.2.0':
resolution: {integrity: sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==}
engines: {node: '>=14.0.0'}
- '@smithy/querystring-builder@3.0.3':
- resolution: {integrity: sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==}
- engines: {node: '>=16.0.0'}
+ '@smithy/querystring-builder@4.0.2':
+ resolution: {integrity: sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==}
+ engines: {node: '>=18.0.0'}
- '@smithy/querystring-parser@3.0.3':
- resolution: {integrity: sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/querystring-parser@4.0.2':
+ resolution: {integrity: sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==}
+ engines: {node: '>=18.0.0'}
- '@smithy/service-error-classification@3.0.3':
- resolution: {integrity: sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/service-error-classification@4.0.2':
+ resolution: {integrity: sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==}
+ engines: {node: '>=18.0.0'}
- '@smithy/shared-ini-file-loader@3.1.4':
- resolution: {integrity: sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/shared-ini-file-loader@4.0.2':
+ resolution: {integrity: sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==}
+ engines: {node: '>=18.0.0'}
- '@smithy/signature-v4@4.1.0':
- resolution: {integrity: sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==}
- engines: {node: '>=16.0.0'}
+ '@smithy/signature-v4@5.0.2':
+ resolution: {integrity: sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw==}
+ engines: {node: '>=18.0.0'}
- '@smithy/smithy-client@3.1.11':
- resolution: {integrity: sha512-l0BpyYkciNyMaS+PnFFz4aO5sBcXvGLoJd7mX9xrMBIm2nIQBVvYgp2ZpPDMzwjKCavsXu06iuCm0F6ZJZc6yQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/smithy-client@4.2.0':
+ resolution: {integrity: sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==}
+ engines: {node: '>=18.0.0'}
'@smithy/types@2.12.0':
resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==}
engines: {node: '>=14.0.0'}
- '@smithy/types@3.3.0':
- resolution: {integrity: sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/types@4.2.0':
+ resolution: {integrity: sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==}
+ engines: {node: '>=18.0.0'}
- '@smithy/url-parser@3.0.3':
- resolution: {integrity: sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==}
+ '@smithy/url-parser@4.0.2':
+ resolution: {integrity: sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-base64@3.0.0':
- resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-base64@4.0.0':
+ resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-body-length-browser@3.0.0':
- resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==}
+ '@smithy/util-body-length-browser@4.0.0':
+ resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-body-length-node@3.0.0':
- resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-body-length-node@4.0.0':
+ resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==}
+ engines: {node: '>=18.0.0'}
'@smithy/util-buffer-from@2.0.0':
resolution: {integrity: sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==}
engines: {node: '>=14.0.0'}
- '@smithy/util-buffer-from@3.0.0':
- resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-buffer-from@4.0.0':
+ resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-config-provider@3.0.0':
- resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-config-provider@4.0.0':
+ resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-defaults-mode-browser@3.0.13':
- resolution: {integrity: sha512-ZIRSUsnnMRStOP6OKtW+gCSiVFkwnfQF2xtf32QKAbHR6ACjhbAybDvry+3L5qQYdh3H6+7yD/AiUE45n8mTTw==}
- engines: {node: '>= 10.0.0'}
+ '@smithy/util-defaults-mode-browser@4.0.8':
+ resolution: {integrity: sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-defaults-mode-node@3.0.13':
- resolution: {integrity: sha512-voUa8TFJGfD+U12tlNNLCDlXibt9vRdNzRX45Onk/WxZe7TS+hTOZouEZRa7oARGicdgeXvt1A0W45qLGYdy+g==}
- engines: {node: '>= 10.0.0'}
+ '@smithy/util-defaults-mode-node@4.0.8':
+ resolution: {integrity: sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-endpoints@2.0.5':
- resolution: {integrity: sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-endpoints@3.0.2':
+ resolution: {integrity: sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-hex-encoding@3.0.0':
- resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-hex-encoding@4.0.0':
+ resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-middleware@3.0.3':
- resolution: {integrity: sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-middleware@4.0.2':
+ resolution: {integrity: sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-retry@3.0.3':
- resolution: {integrity: sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-retry@4.0.2':
+ resolution: {integrity: sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-stream@3.1.3':
- resolution: {integrity: sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-stream@4.2.0':
+ resolution: {integrity: sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==}
+ engines: {node: '>=18.0.0'}
'@smithy/util-uri-escape@2.2.0':
resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==}
engines: {node: '>=14.0.0'}
- '@smithy/util-uri-escape@3.0.0':
- resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-uri-escape@4.0.0':
+ resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==}
+ engines: {node: '>=18.0.0'}
'@smithy/util-utf8@2.0.0':
resolution: {integrity: sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==}
engines: {node: '>=14.0.0'}
- '@smithy/util-utf8@3.0.0':
- resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-utf8@4.0.0':
+ resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==}
+ engines: {node: '>=18.0.0'}
- '@smithy/util-waiter@3.1.2':
- resolution: {integrity: sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==}
- engines: {node: '>=16.0.0'}
+ '@smithy/util-waiter@4.0.3':
+ resolution: {integrity: sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==}
+ engines: {node: '>=18.0.0'}
'@sqltools/formatter@1.2.5':
resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
- '@storybook/addon-actions@8.4.4':
- resolution: {integrity: sha512-+Dd6alcieS6UN7IKhXLuhyQYQMu9HG/Tdr790a4EOQKpJM1NxIMuPuUH3fAoKfa9VhtI1BxTBr7zNtzg9Akqhg==}
+ '@storybook/addon-actions@8.6.7':
+ resolution: {integrity: sha512-XgZCwIcZGThEyD7e2q7rN/jzg7ZHUxn/ln403eex04jWAGBBbtC2IVuowwCWV8HwDihnhpCZEP6HlgjakOYZbQ==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-backgrounds@8.4.4':
- resolution: {integrity: sha512-asaGD4ruIPFthyhpByQSJagvtNN7EGKdHj5yMnsMvkSXnN0r1uVkI2/Z37hmLt02Qbzf6OQiBPW5TDL+X+EEBg==}
+ '@storybook/addon-backgrounds@8.6.7':
+ resolution: {integrity: sha512-aDFzi83gDhYn0+FGjRYbY5TfBtoG/UgVr9Abi7s5ceabZRhPrYikMyFX0o8V3Z8COl6wUmWmF1luYE4MfXgN2g==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-controls@8.4.4':
- resolution: {integrity: sha512-FbZRbwJQggLz6M3zB6scCp1SDGwQ5zdiD6sjBilZzgGO5rBFqG0A8PoOyr4iPrLU2y/NZBdRrJBD+6MkaJ+yzw==}
+ '@storybook/addon-controls@8.6.7':
+ resolution: {integrity: sha512-6ReB1Sc1qlqvAM7NUmtw2K1cKCgGBs8zYRgL44Q2ti+r55a2ownhm6WUm/kZs2ixSkV9ehm1osiqbGBfAn0Isw==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-docs@8.4.4':
- resolution: {integrity: sha512-wuHaStfpd2rkAN5Lf0qmvE3JKTHTEDbnAMTvfs9inzGBL0iAwBLjW48/ll7lLkJ2E3k/FQtaevNpuc7C52u1Bw==}
+ '@storybook/addon-docs@8.6.7':
+ resolution: {integrity: sha512-kgNPEVuLGNJE8EdVQi5Tg2DYgR66/gut07jvhqnJfNqUkj6UpBHad0JR1uwrd7xS3kJs29Fs7UyU87RJnSlwcg==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-essentials@8.4.4':
- resolution: {integrity: sha512-0ObUQ98zZkeWqP2k3Un5jny3WxT3THgUKZUGD+mR8eq6CuTmJ3bUXWzDHreuDxQwgr8s5f04XD8IcRvjZ9IRgA==}
+ '@storybook/addon-essentials@8.6.7':
+ resolution: {integrity: sha512-PFT62xuknk4wD1hTZEnYbGP1mJFPlhk7zVVlMjoldMUhmbHsFRhdWCpo93Vu9E3BWVxFxL3Jj+UwSwH4uVmekQ==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-highlight@8.4.4':
- resolution: {integrity: sha512-k7EUxiMe8RCasmgfa6ZKx7UG6kU9RooTYGwqY5TG5xAQOzDwKn4qom+OYkT/9/6lORhJrUe2GgQLCrq/WGpS1A==}
+ '@storybook/addon-highlight@8.6.7':
+ resolution: {integrity: sha512-4KE1RF4XfqII7XrJPgf/1W0t0EWRKmik5Rrpb6WofXfgZ2QYzLFnyESjf67/g2TMgDnle2drfa/pt5tGV4+I2Q==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-interactions@8.4.4':
- resolution: {integrity: sha512-izqcc6tY0BiKW7DYrEnoXUEH9FYDPWNfQnqqE0nVBv3BS2DoNmm8M9SB8fZx7pPfw53cMJBGt3vrlY0Wtxy1+Q==}
+ '@storybook/addon-interactions@8.6.7':
+ resolution: {integrity: sha512-FbEWWxCl/5DJDyEGTJqtTJ5XbxM2rOUGCPy+3CkPSpI9yvz3zprRTJRHPFrh7hUqQ4Qkqfjm7JCO29+0CmeE0g==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-links@8.4.4':
- resolution: {integrity: sha512-hqTv06fPq9k5GUZD8JR49ANw5sBg8EYAsuCNoSd9OwVSBO/3y53HrMA0NCILUK8hnupPvtBuKXXoHmHes9R+1g==}
+ '@storybook/addon-links@8.6.7':
+ resolution: {integrity: sha512-fIiXlaOa9Bv2tbBshQbh/BjzGOilXVx+6nrX9VkLOg7UvzAvivtSraRmPWjgdtsChAHC8Xac42KUCNGQ/rkf5w==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.4.4
+ storybook: ^8.6.7
peerDependenciesMeta:
react:
optional: true
- '@storybook/addon-mdx-gfm@8.4.4':
- resolution: {integrity: sha512-dj98NGWowhSwWYn2LUaLMxHNvBY+73n9CFsELrttg24nOxmeRfku0uh2hp5epMmRMX3Fej7nCkKNJaU1fihZ+Q==}
+ '@storybook/addon-mdx-gfm@8.6.7':
+ resolution: {integrity: sha512-IfGgPnOMq51yBpnaY2w5hlm4pBgIMig61vsigqySU7KKFY6qxD/LcIJAxOPh2s9dLhYGYDrO0hFGR9fQ7Niu5A==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-measure@8.4.4':
- resolution: {integrity: sha512-KsjrwrXwrI+z7hKKfjyY1w1b0gLSLZmp15vIRJMELybWV0+4bZFLJGwMBOQFx+aWBED8yZrRV9OjTmoczawsZg==}
+ '@storybook/addon-measure@8.6.7':
+ resolution: {integrity: sha512-4dkkCltjKRcJH+ZMv5nbNT0LBQfcXIydVfN9mAvhDsiPFD5eZcHbN4XVfUslECWgrkaa/a6FE1W9PNEUBjCJaA==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-outline@8.4.4':
- resolution: {integrity: sha512-CVS1dm6BNUWKGrJj9E1ThBp5Khe6Yw+Hhz6OFxrPZfoTr6RstwoTmvSpKjDUCn8zj6ujoORdiQUh1FsHOxAPBg==}
+ '@storybook/addon-outline@8.6.7':
+ resolution: {integrity: sha512-atCpCi2CqAWQwL1nu1l5VpIA4fRMnbD4RZMsEiib1suUfNyJv0RdsSgZhp/f+e9sUS0TtMdwhzWT36eEA7VxhQ==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-storysource@8.4.4':
- resolution: {integrity: sha512-BuMQMQvYqiaosbGkUxDPU2nfZtI2E/zxpNaubpUAH2j+bx4zdXRXyW1P71wj5GZC84bszoyXhdd++9A0knmaYA==}
+ '@storybook/addon-storysource@8.6.7':
+ resolution: {integrity: sha512-tIoTQp3MMyF3S4XarMOBVO40DofILO3Mz8upT4wGEfQULLjgCkS2K5c4BbT4de1hF49JsqvPByVlavntWQFTdg==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-toolbars@8.4.4':
- resolution: {integrity: sha512-ENPshJMDpfzOJ4Tgm1hSzQoaEmgDxCtP6C8LKk4MOd3X92MJ7p6kfb3y3R1BLg4E/g90qp6lKPFdcohS2tKCgQ==}
+ '@storybook/addon-toolbars@8.6.7':
+ resolution: {integrity: sha512-gR+mRs+Cc5GINZdKgE7afJLFCSMHkz40+zzdrPu6yY2P4B3UOvuQpt+zC/Er5YQ31EEjIvM6/XMQTM0i2db8AA==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/addon-viewport@8.4.4':
- resolution: {integrity: sha512-SRHJlLhf3tu7+sYNfVIYTeMegn6aiv4HGX97ZLvL76NWWBU8BntQ1LKMki7475mWiZNUFMoYYPsHlG+HU9FAtg==}
+ '@storybook/addon-viewport@8.6.7':
+ resolution: {integrity: sha512-kTrt6ByCbBIbqoRqQO9watDl5nSIKCC+R0/EmpEl6ZtzBV3l8trZHdvCHhIqOyv7nfaa7pIeTTG1GD6Gdrxk3w==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/blocks@8.4.4':
- resolution: {integrity: sha512-LwM3guL7uWpYR1a/SY0KZjCUskTKEaS22eF7GK8iXAV5BY4KpKr6ArW4O9orK29KtFwKhDZQLcMcECsOJBVk/A==}
+ '@storybook/blocks@8.6.7':
+ resolution: {integrity: sha512-IFhIKO7R1UPpnoG/5tZH0FgC79oYgXNf+7aGUwq29M/CQWy6p/Pvp0y4P962btY1UZRol+SsU//33nH8o6yNRw==}
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.4.4
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ storybook: ^8.6.7
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
- '@storybook/builder-vite@8.4.4':
- resolution: {integrity: sha512-UfPzE0p2xvBK7sA853N3VN+Plfw6/DIVppwbgsaRdzie52QXZQrl60u0igD47DHi6+xbqCBWDz7up4h3k00Z5A==}
+ '@storybook/builder-vite@8.6.7':
+ resolution: {integrity: sha512-hgYnVu2cy8clrmDwidu4XjvFMTEi9WiblLH5cPI3LWQjVajIQmDpcWVp6kbD063sIOphh9zYP7cVKGO7ktMB/g==}
peerDependencies:
- storybook: ^8.4.4
- vite: ^4.0.0 || ^5.0.0
+ storybook: ^8.6.7
+ vite: ^4.0.0 || ^5.0.0 || ^6.0.0
- '@storybook/components@8.4.4':
- resolution: {integrity: sha512-0BSZVmsk23C0BSRKx3liZSVQFXtoF86XQFdNQxjrXIwdHIEN7TcL3DwcxeVUU5ilGp7HeDgAydGNIPGgTeEe6g==}
+ '@storybook/components@8.6.7':
+ resolution: {integrity: sha512-8pnjH1w7PZ/Iiuve1/BJY7EO/kmu0qdE34X1ZM8DyHzuy33EL/PfUuhxNkrL4ayMXrEDp/EJMHx2bqO1RdRV6A==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- '@storybook/core-events@8.4.4':
- resolution: {integrity: sha512-pkwr0UU95WSJtn9Q7q5ip0x8WxerLf5z4CWonvymGu9Z0bZyMXeA+GOEt/YQIJgqI4fbTK8Jqi+suC6ibUu9oQ==}
+ '@storybook/core-events@8.6.7':
+ resolution: {integrity: sha512-7g6Ic2L60SwKiPyqDUBfb5z1hw6YKmA2KpV1ndOFxMQm052zTnGequXdLXG6kIpgw30pws5eq1tjh3tkPCPFew==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- '@storybook/core@8.4.4':
- resolution: {integrity: sha512-WjTmJpsHsFCd7tQ/8jFpDWjhntauXcWYYTcEZk56Pq4miyNrrXhV0S80Gxv3Uvzk0jocgtT2AKf8rQuH2UkQEg==}
+ '@storybook/core@8.6.7':
+ resolution: {integrity: sha512-FcvLFA+Qn3+D6LgQkk0MOXA5FBz8DGc0UZmZuVbIwIUV4MV4ywCMwtKdG0cyhtzQg0YNyfiIYWJr7lZ4jLLhYg==}
peerDependencies:
prettier: ^2 || ^3
peerDependenciesMeta:
prettier:
optional: true
- '@storybook/csf-plugin@8.4.4':
- resolution: {integrity: sha512-4+6SUhp5sEJN9BY5RuxcFKvJbOqCzIUp9oHSSz36hkP07a4QH+SwxfEd0U7JRfmPpB63L+izywTzWhdADiAMOQ==}
+ '@storybook/csf-plugin@8.6.7':
+ resolution: {integrity: sha512-HK7yQD4kFu04JOKnUwoFeR58r5WY6ucF0D8zfW4Gx+r8hBJ5K4t3z6k2dlIlRQF1X5+2vNkQOwD8liHjckuZ8Q==}
peerDependencies:
- storybook: ^8.4.4
-
- '@storybook/csf@0.1.11':
- resolution: {integrity: sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==}
+ storybook: ^8.6.7
'@storybook/global@5.0.0':
resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
@@ -3995,45 +4034,49 @@ 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.4.4':
- resolution: {integrity: sha512-mq/YVEZrB8jyyio2Of01rQixsQ72z8ssAhJS9ldIlK+cvERQi0VBCpH3pejPmjOB40yiKBJHNqH4HIANVhibgw==}
+ '@storybook/instrumenter@8.6.7':
+ resolution: {integrity: sha512-FeQiV0g5crCWs0P1wKY4xZzb4PxAYNcrm2+9LLGVqwnC7qzrSCPf0p10MlveVfwsen1m6Wbqfe+wl21c31Hfmg==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/manager-api@8.4.4':
- resolution: {integrity: sha512-rmNPcbEyzakEHoaecUbhkW7WWOkyZ0z7ywH4d5/s0ZuQS57Px2N+ZLVgRJwYK+YNHiJYqDf1BTln9YJ/Mt1L6Q==}
+ '@storybook/manager-api@8.6.7':
+ resolution: {integrity: sha512-BA8RxaLP07WGF660LWo7qB3Jomr/+MPuCZmuKPqXxPhfIovqYjr0hnugxJBjEah0ic31aNX4NucNfDRuV7F5sA==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- '@storybook/preview-api@8.4.4':
- resolution: {integrity: sha512-iZrWQcjRBqBHFdDXVxGpw6mHBZMCMYqhWXdyJ0d1S2y3PwcfOjkcXlQ1UiAenFHlA6dKrcYw8luKUQTL9bKReA==}
+ '@storybook/preview-api@8.6.7':
+ resolution: {integrity: sha512-Rz83Nx43v3Dn9/SjhIsorkcx1gPmlclueuzf6YywJTqE1E/L4dgoe2mOA9MfF0jr0bh3TwEA2J3ii0Jstg1Orw==}
peerDependencies:
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.4.4':
- resolution: {integrity: sha512-kufv2FDK3kjADBo+/aKHsUn9T5E4p9IBAmCoIvXBGRDumPRds7Pt3MB4ODKlg+IumR7LMEq0jTJkn27ZRTuUmw==}
+ '@storybook/react-dom-shim@8.6.7':
+ resolution: {integrity: sha512-+JH7gbRI6NRbt9o0l1rY4wFdeVt8wGRddm0b55OBlwBGlFo2nvGVOH73J4AGphXVhfY7z33I3TXIjXQ561UdEQ==}
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.4.4
+ storybook: ^8.6.7
- '@storybook/react-vite@8.4.4':
- resolution: {integrity: sha512-NbTAY4R526hJ+gz7BFLS1HpGx3BikQDbq1BuEcaWsf/rJnygwlzeQmdPyfrfNC8R0ufIKRWUiPrPmMvrf8ZI6A==}
+ '@storybook/react-vite@8.6.7':
+ resolution: {integrity: sha512-KiTeYaZ+AUQ1AFHSItP8dhUbd2v7Qy8+BB7w64VxQMw/dw5n0Z38lo4Tzdlkn22q2smW2ce4QwAzh2pfTz3b8g==}
engines: {node: '>=18.0.0'}
peerDependencies:
+ '@storybook/test': 8.6.7
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.4.4
- vite: ^4.0.0 || ^5.0.0
+ storybook: ^8.6.7
+ vite: ^4.0.0 || ^5.0.0 || ^6.0.0
+ peerDependenciesMeta:
+ '@storybook/test':
+ optional: true
- '@storybook/react@8.4.4':
- resolution: {integrity: sha512-92lGnRcAI2qW6zH8GMBScyXmOS1ANI8ZuSP4ExQj+lGsCrAr7PBr0wuHy3wIn1YyAvQGPUn/mpYrmMz08c2HfA==}
+ '@storybook/react@8.6.7':
+ resolution: {integrity: sha512-6R8znSm7kzsoAJyRbEiDWE+5xjeAIzwEcfT60fqx+uMdd0vDFM7f2uT4fYy+CijWas1oFWcNV/LMd3EqSkBGsQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
- '@storybook/test': 8.4.4
+ '@storybook/test': 8.6.7
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.4.4
+ storybook: ^8.6.7
typescript: '>= 4.2.x'
peerDependenciesMeta:
'@storybook/test':
@@ -4041,47 +4084,53 @@ packages:
typescript:
optional: true
- '@storybook/source-loader@8.4.4':
- resolution: {integrity: sha512-xaC23ljSEpHSMdp/VdqKd1o4Dr7x5lA2897RR6SKFRFDgkKD5Mp1UXsrcwqSZNSeXETTmVWXf8rHrz14VKkK6w==}
+ '@storybook/source-loader@8.6.7':
+ resolution: {integrity: sha512-ycfrPHCs5OUrJTLCXDxvxLVB1zjL7IEepPs53o4RGRWO8xV1z0QfXXiX1drk48rep6dDu+a3mRWfNJ8m0RV/GA==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/test@8.4.4':
- resolution: {integrity: sha512-tmJd+lxl3MC0Xdu1KW/69V8tibv98OvdopxGqfVR0x5dkRHM3sFK/tv1ZJAUeronlvRyhGySOu1tHUrMjcNqyA==}
+ '@storybook/test@8.6.7':
+ resolution: {integrity: sha512-uF1JbBtdT7tuiXfEtHsUShBHIhm2vc0C39nKVJaTWyK9CybajXaj2Ny3IRa3oY9NKnklwGgN+kZ/Z9YiIOc4MQ==}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
- '@storybook/theming@8.4.4':
- resolution: {integrity: sha512-iq4yt3Fx35ZV5owNC//E6G+QPV19xHHVN2Ugi3p7KOSFK3chuXX9mxZ1rfir+t+U30a5EPOEnlsY3/1LXn7aTw==}
+ '@storybook/theming@8.6.7':
+ resolution: {integrity: sha512-F/i4XS5bew9dvtNiHvDJF0mko1IUbPM9PUjTYPaw6cK8ytS0kdec703MsJ/GUA7seeEWBeGdZjV3ua0pys650A==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- '@storybook/types@8.4.4':
- resolution: {integrity: sha512-NUeIhecJ+i2ul/u/ftV+f9gBT2cUOuLjgy1a+l0UbJd7n3wwN17vX2zrrDkrGG3dp3edr8bWMGjAN3WERJje1A==}
+ '@storybook/types@8.6.7':
+ resolution: {integrity: sha512-Zs8SWF099Bkw39vSdS+81X/12PEiVzg5s84j47YUXF4XTPtjB38Fl36TdEUXUcL/VOOU7uyRQyZu3EoB+sambg==}
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- '@storybook/vue3-vite@8.4.4':
- resolution: {integrity: sha512-cyhPX16KzOWuHZCcMXqJ+k11xOvelWXmML6pSvhV0OtizDHxgesCdbSa1X1P76ZszjOlt8MJfPiSaE+XNwB0UQ==}
+ '@storybook/vue3-vite@8.6.7':
+ resolution: {integrity: sha512-CHaWS218UXP2dB23LsCOPCPvImb2KkD5Qiz9fvT7U5RukWbHomQNKo9EQs3ujIjwVjomotBXMm1TfMUIE2Q3lg==}
engines: {node: '>=18.0.0'}
peerDependencies:
- storybook: ^8.4.4
- vite: ^4.0.0 || ^5.0.0
+ storybook: ^8.6.7
+ vite: ^4.0.0 || ^5.0.0 || ^6.0.0
- '@storybook/vue3@8.4.4':
- resolution: {integrity: sha512-HVUtE8x4nIJeCO592VNyrACMgGA6ViarRS6Faw+MWdUQXnZlwkadGusx2T++hnaalAt9VJLF5NRIcV8O7dA6Ig==}
+ '@storybook/vue3@8.6.7':
+ resolution: {integrity: sha512-w32n6x4bOtvNGT2hz+MepJ6pwOJr0ARw2vagMEhYGnwinwCIM7yNaaQv2a3uy87BQnH5miyFKFXEL0Axrj9wOg==}
engines: {node: '>=18.0.0'}
peerDependencies:
- storybook: ^8.4.4
+ storybook: ^8.6.7
vue: ^3.0.0
- '@swc/cli@0.3.12':
- resolution: {integrity: sha512-h7bvxT+4+UDrLWJLFHt6V+vNAcUNii2G4aGSSotKz1ECEk4MyEh5CWxmeSscwuz5K3i+4DWTgm4+4EyMCQKn+g==}
+ '@stylistic/eslint-plugin@4.2.0':
+ resolution: {integrity: sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: '>=9.0.0'
+
+ '@swc/cli@0.6.0':
+ resolution: {integrity: sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==}
engines: {node: '>= 16.14.0'}
hasBin: true
peerDependencies:
'@swc/core': ^1.2.66
- chokidar: 3.5.3
+ chokidar: 4.0.3
peerDependenciesMeta:
chokidar:
optional: true
@@ -4092,26 +4141,14 @@ packages:
cpu: [arm64]
os: [android]
- '@swc/core-darwin-arm64@1.3.56':
- resolution: {integrity: sha512-DZcu7BzDaLEdWHabz9DRTP0yEBLqkrWmskFcD5BX0lGAvoIvE4duMnAqi5F2B3X7630QioHRCYFoRw2WkeE3Cw==}
+ '@swc/core-darwin-arm64@1.11.11':
+ resolution: {integrity: sha512-vJcjGVDB8cZH7zyOkC0AfpFYI/7GHKG0NSsH3tpuKrmoAXJyCYspKPGid7FT53EAlWreN7+Pew+bukYf5j+Fmg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
- '@swc/core-darwin-arm64@1.9.2':
- resolution: {integrity: sha512-nETmsCoY29krTF2PtspEgicb3tqw7Ci5sInTI03EU5zpqYbPjoPH99BVTjj0OsF53jP5MxwnLI5Hm21lUn1d6A==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [darwin]
-
- '@swc/core-darwin-x64@1.3.56':
- resolution: {integrity: sha512-VH5saqYFasdRXJy6RAT+MXm0+IjkMZvOkohJwUei+oA65cKJofQwrJ1jZro8yOJFYvUSI3jgNRGsdBkmo/4hMw==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [darwin]
-
- '@swc/core-darwin-x64@1.9.2':
- resolution: {integrity: sha512-9gD+bwBz8ZByjP6nZTXe/hzd0tySIAjpDHgkFiUrc+5zGF+rdTwhcNrzxNHJmy6mw+PW38jqII4uspFHUqqxuQ==}
+ '@swc/core-darwin-x64@1.11.11':
+ resolution: {integrity: sha512-/N4dGdqEYvD48mCF3QBSycAbbQd3yoZ2YHSzYesQf8usNc2YpIhYqEH3sql02UsxTjEFOJSf1bxZABDdhbSl6A==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
@@ -4122,104 +4159,56 @@ packages:
cpu: [x64]
os: [freebsd]
- '@swc/core-linux-arm-gnueabihf@1.3.56':
- resolution: {integrity: sha512-LWwPo6NnJkH01+ukqvkoNIOpMdw+Zundm4vBeicwyVrkP+mC3kwVfi03TUFpQUz3kRKdw/QEnxGTj+MouCPbtw==}
- engines: {node: '>=10'}
- cpu: [arm]
- os: [linux]
-
- '@swc/core-linux-arm-gnueabihf@1.9.2':
- resolution: {integrity: sha512-kYq8ief1Qrn+WmsTWAYo4r+Coul4dXN6cLFjiPZ29Cv5pyU+GFvSPAB4bEdMzwy99rCR0u2P10UExaeCjurjvg==}
+ '@swc/core-linux-arm-gnueabihf@1.11.11':
+ resolution: {integrity: sha512-hsBhKK+wVXdN3x9MrL5GW0yT8o9GxteE5zHAI2HJjRQel3HtW7m5Nvwaq+q8rwMf4YQRd8ydbvwl4iUOZx7i2Q==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
- '@swc/core-linux-arm64-gnu@1.3.56':
- resolution: {integrity: sha512-GzsUy/4egJ4cMlxbM+Ub7AMi5CKAc+pxBxrh8MUPQbyStW8jGgnQsJouTnGy0LHawtdEnsCOl6PcO6OgvktXuQ==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [linux]
-
- '@swc/core-linux-arm64-gnu@1.9.2':
- resolution: {integrity: sha512-n0W4XiXlmEIVqxt+rD3ZpkogsEWUk1jJ+i5bQNgB+1JuWh0fBE8c/blDgTQXa0GB5lTPVDZQussgdNOCnAZwiA==}
+ '@swc/core-linux-arm64-gnu@1.11.11':
+ resolution: {integrity: sha512-YOCdxsqbnn/HMPCNM6nrXUpSndLXMUssGTtzT7ffXqr7WuzRg2e170FVDVQFIkb08E7Ku5uOnnUVAChAJQbMOQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
- '@swc/core-linux-arm64-musl@1.3.56':
- resolution: {integrity: sha512-9gxL09BIiAv8zY0DjfnFf19bo8+P4T9tdhzPwcm+1yPJcY5yr1+YFWLNFzz01agtOj6VlZ2/wUJTaOfdjjtc+A==}
+ '@swc/core-linux-arm64-musl@1.11.11':
+ resolution: {integrity: sha512-nR2tfdQRRzwqR2XYw9NnBk9Fdvff/b8IiJzDL28gRR2QiJWLaE8LsRovtWrzCOYq6o5Uu9cJ3WbabWthLo4jLw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
- '@swc/core-linux-arm64-musl@1.9.2':
- resolution: {integrity: sha512-8xzrOmsyCC1zrx2Wzx/h8dVsdewO1oMCwBTLc1gSJ/YllZYTb04pNm6NsVbzUX2tKddJVRgSJXV10j/NECLwpA==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [linux]
-
- '@swc/core-linux-x64-gnu@1.3.56':
- resolution: {integrity: sha512-n0ORNknl50vMRkll3BDO1E4WOqY6iISlPV1ZQCRLWQ6YQ2q8/WAryBxc2OAybcGHBUFkxyACpJukeU1QZ/9tNw==}
+ '@swc/core-linux-x64-gnu@1.11.11':
+ resolution: {integrity: sha512-b4gBp5HA9xNWNC5gsYbdzGBJWx4vKSGybGMGOVWWuF+ynx10+0sA/o4XJGuNHm8TEDuNh9YLKf6QkIO8+GPJ1g==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
- '@swc/core-linux-x64-gnu@1.9.2':
- resolution: {integrity: sha512-kZrNz/PjRQKcchWF6W292jk3K44EoVu1ad5w+zbS4jekIAxsM8WwQ1kd+yjUlN9jFcF8XBat5NKIs9WphJCVXg==}
+ '@swc/core-linux-x64-musl@1.11.11':
+ resolution: {integrity: sha512-dEvqmQVswjNvMBwXNb8q5uSvhWrJLdttBSef3s6UC5oDSwOr00t3RQPzyS3n5qmGJ8UMTdPRmsopxmqaODISdg==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
- '@swc/core-linux-x64-musl@1.3.56':
- resolution: {integrity: sha512-r+D34WLAOAlJtfw1gaVWpHRwCncU9nzW9i7w9kSw4HpWYnHJOz54jLGSEmNsrhdTCz1VK2ar+V2ktFUsrlGlDA==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [linux]
-
- '@swc/core-linux-x64-musl@1.9.2':
- resolution: {integrity: sha512-TTIpR4rjMkhX1lnFR+PSXpaL83TrQzp9znRdp2TzYrODlUd/R20zOwSo9vFLCyH6ZoD47bccY7QeGZDYT3nlRg==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [linux]
-
- '@swc/core-win32-arm64-msvc@1.3.56':
- resolution: {integrity: sha512-29Yt75Is6X24z3x8h/xZC1HnDPkPpyLH9mDQiM6Cuc0I9mVr1XSriPEUB2N/awf5IE4SA8c+3IVq1DtKWbkJIw==}
+ '@swc/core-win32-arm64-msvc@1.11.11':
+ resolution: {integrity: sha512-aZNZznem9WRnw2FbTqVpnclvl8Q2apOBW2B316gZK+qxbe+ktjOUnYaMhdCG3+BYggyIBDOnaJeQrXbKIMmNdw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
- '@swc/core-win32-arm64-msvc@1.9.2':
- resolution: {integrity: sha512-+Eg2d4icItKC0PMjZxH7cSYFLWk0aIp94LNmOw6tPq0e69ax6oh10upeq0D1fjWsKLmOJAWEvnXlayZcijEXDw==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [win32]
-
- '@swc/core-win32-ia32-msvc@1.3.56':
- resolution: {integrity: sha512-mplp0zbYDrcHtfvkniXlXdB04e2qIjz2Gq/XHKr4Rnc6xVORJjjXF91IemXKpavx2oZYJws+LNJL7UFQ8jyCdQ==}
- engines: {node: '>=10'}
- cpu: [ia32]
- os: [win32]
-
- '@swc/core-win32-ia32-msvc@1.9.2':
- resolution: {integrity: sha512-nLWBi4vZDdM/LkiQmPCakof8Dh1/t5EM7eudue04V1lIcqx9YHVRS3KMwEaCoHLGg0c312Wm4YgrWQd9vwZ5zQ==}
+ '@swc/core-win32-ia32-msvc@1.11.11':
+ resolution: {integrity: sha512-DjeJn/IfjgOddmJ8IBbWuDK53Fqw7UvOz7kyI/728CSdDYC3LXigzj3ZYs4VvyeOt+ZcQZUB2HA27edOifomGw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
- '@swc/core-win32-x64-msvc@1.3.56':
- resolution: {integrity: sha512-zp8MBnrw/bjdLenO/ifYzHrImSjKunqL0C2IF4LXYNRfcbYFh2NwobsVQMZ20IT0474lKRdlP8Oxdt+bHuXrzA==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [win32]
-
- '@swc/core-win32-x64-msvc@1.9.2':
- resolution: {integrity: sha512-ik/k+JjRJBFkXARukdU82tSVx0CbExFQoQ78qTO682esbYXzjdB5eLVkoUbwen299pnfr88Kn4kyIqFPTje8Xw==}
+ '@swc/core-win32-x64-msvc@1.11.11':
+ resolution: {integrity: sha512-Gp/SLoeMtsU4n0uRoKDOlGrRC6wCfifq7bqLwSlAG8u8MyJYJCcwjg7ggm0rhLdC2vbiZ+lLVl3kkETp+JUvKg==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
- '@swc/core@1.9.2':
- resolution: {integrity: sha512-dYyEkO6mRYtZFpnOsnYzv9rY69fHAHoawYOjGOEcxk9WYtaJhowMdP/w6NcOKnz2G7GlZaenjkzkMa6ZeQeMsg==}
+ '@swc/core@1.11.11':
+ resolution: {integrity: sha512-pCVY2Wn6dV/labNvssk9b3Owi4WOYsapcbWm90XkIj4xH/56Z6gzja9fsU+4MdPuEfC2Smw835nZHcdCFGyX6A==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '*'
@@ -4236,8 +4225,8 @@ packages:
peerDependencies:
'@swc/core': '*'
- '@swc/types@0.1.17':
- resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==}
+ '@swc/types@0.1.19':
+ resolution: {integrity: sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==}
'@swc/wasm@1.2.130':
resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==}
@@ -4245,10 +4234,6 @@ packages:
'@syuilo/aiscript@0.19.0':
resolution: {integrity: sha512-ZWG4s1m6RrFjE7NeIMaxFz769YO1jW5ReTrOROrEO4IHheOrjxxJ/Ffe2TUNqX9/XxDloMwfWplKhfSzx8LGMA==}
- '@szmarczak/http-timer@4.0.6':
- resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
- engines: {node: '>=10'}
-
'@szmarczak/http-timer@5.0.1':
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
engines: {node: '>=14.16'}
@@ -4340,11 +4325,8 @@ packages:
'@types/braces@3.0.1':
resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==}
- '@types/cacheable-request@6.0.3':
- resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
-
- '@types/canvas-confetti@1.6.4':
- resolution: {integrity: sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==}
+ '@types/canvas-confetti@1.9.0':
+ resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==}
'@types/color-convert@2.0.4':
resolution: {integrity: sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==}
@@ -4373,6 +4355,9 @@ packages:
'@types/disposable-email-domains@1.0.2':
resolution: {integrity: sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw==}
+ '@types/dlv@1.1.5':
+ resolution: {integrity: sha512-JHOWNfiWepAhfwlSw17kiWrWrk6od2dEQgHltJw9AS0JPFoLZJBge5+Dnil2NfdjAvJ/+vGSX60/BRW20PpUXw==}
+
'@types/doctrine@0.0.9':
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
@@ -4395,12 +4380,12 @@ packages:
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==}
-
'@types/graceful-fs@4.1.6':
resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
+ '@types/hammerjs@2.0.46':
+ resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==}
+
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -4434,9 +4419,6 @@ packages:
'@types/jsdom@21.1.7':
resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==}
- '@types/json-schema@7.0.12':
- resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
-
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -4446,17 +4428,14 @@ packages:
'@types/jsonld@1.5.15':
resolution: {integrity: sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==}
- '@types/jsrsasign@10.5.14':
- resolution: {integrity: sha512-lppSlfK6etu+cuKs40K4rg8As79PH6hzIB+v55zSqImbSH3SE6Fm8MBHCiI91cWlAP3Z4igtJK1VL3fSN09blQ==}
+ '@types/jsrsasign@10.5.15':
+ resolution: {integrity: sha512-3stUTaSRtN09PPzVWR6aySD9gNnuymz+WviNHoTb85dKu+BjaV4uBbWWGykBBJkfwPtcNZVfTn2lbX00U+yhpQ==}
'@types/katex@0.16.7':
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
- '@types/keyv@3.1.4':
- resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
-
- '@types/matter-js@0.19.7':
- resolution: {integrity: sha512-dlh50YEh1lQS4fiCDGBnK75ocHQIq/1E371Qk6hASJImICIivdZQC2GkOqnfBm0Hac2xLk5+yrqRFDAEfj/yLA==}
+ '@types/matter-js@0.19.8':
+ resolution: {integrity: sha512-W2ZWG58Lijv/4v768NgpeyFqqiOyslmAU7qqM1Lhz4XBoUgGtZtPz4CjcOKYtqHIak14dvPldslQhltqLTWwsw==}
'@types/mdast@4.0.3':
resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
@@ -4473,9 +4452,6 @@ packages:
'@types/mime@3.0.1':
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
- '@types/minimatch@5.1.2':
- resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
-
'@types/minimist@1.2.2':
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
@@ -4485,11 +4461,17 @@ packages:
'@types/mysql@2.15.26':
resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==}
- '@types/node@22.9.0':
- resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==}
+ '@types/node@22.13.10':
+ resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==}
+
+ '@types/node@22.13.11':
+ resolution: {integrity: sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==}
- '@types/nodemailer@6.4.16':
- resolution: {integrity: sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==}
+ '@types/node@22.13.9':
+ resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==}
+
+ '@types/nodemailer@6.4.17':
+ resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==}
'@types/normalize-package-data@2.4.1':
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -4515,8 +4497,8 @@ packages:
'@types/pg-pool@2.0.6':
resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==}
- '@types/pg@8.11.10':
- resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==}
+ '@types/pg@8.11.11':
+ resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==}
'@types/pg@8.6.1':
resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==}
@@ -4563,9 +4545,6 @@ packages:
'@types/resolve@1.20.3':
resolution: {integrity: sha512-NH5oErHOtHZYcjCtg69t26aXEk4BN2zLWqf7wnDZ+dpe0iR7Rds1SPGEItl3fca21oOe0n3OCnZ4W7jBxu7FOw==}
- '@types/responselike@1.0.0':
- resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
-
'@types/sanitize-html@2.13.0':
resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==}
@@ -4581,11 +4560,8 @@ packages:
'@types/serve-static@1.15.1':
resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
- '@types/serviceworker@0.0.67':
- resolution: {integrity: sha512-7TCH7iNsCSNb+aUD9M/36TekrWFSLCjNK8zw/3n5kOtRjbLtDfGYMXTrDnGhSfqXNwpqmt9Vd90w5C/ad1tX6Q==}
-
- '@types/shimmer@1.0.5':
- resolution: {integrity: sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==}
+ '@types/serviceworker@0.0.74':
+ resolution: {integrity: sha512-HNt7NJHrjGtCmI3h1+rsb1g/ZY0iy5KaeenfEV7zAWPSaCs49hEUvgH++V1BHNwlLfB3sbjPh3pSiNixfYjb1w==}
'@types/shimmer@1.2.0':
resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==}
@@ -4593,8 +4569,8 @@ packages:
'@types/simple-oauth2@5.0.7':
resolution: {integrity: sha512-8JbWVJbiTSBQP/7eiyGKyXWAqp3dKQZpaA+pdW16FCi32ujkzRMG8JfjoAzdWt6W8U591ZNdHcPtP2D7ILTKuA==}
- '@types/sinon@10.0.13':
- resolution: {integrity: sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==}
+ '@types/sinon@17.0.4':
+ resolution: {integrity: sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==}
'@types/sinonjs__fake-timers@8.1.1':
resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==}
@@ -4632,9 +4608,6 @@ packages:
'@types/unist@3.0.2':
resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
- '@types/uuid@10.0.0':
- resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
-
'@types/uuid@9.0.8':
resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
@@ -4644,12 +4617,12 @@ packages:
'@types/web-push@3.6.4':
resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==}
+ '@types/ws@8.18.0':
+ resolution: {integrity: sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==}
+
'@types/ws@8.5.11':
resolution: {integrity: sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==}
- '@types/ws@8.5.13':
- resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==}
-
'@types/yargs-parser@21.0.0':
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
@@ -4670,27 +4643,21 @@ packages:
typescript:
optional: true
- '@typescript-eslint/eslint-plugin@7.1.0':
- resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ '@typescript-eslint/eslint-plugin@8.26.0':
+ resolution: {integrity: sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^7.0.0
- eslint: ^8.56.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
+ '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/eslint-plugin@7.17.0':
- resolution: {integrity: sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/eslint-plugin@8.27.0':
+ resolution: {integrity: sha512-4henw4zkePi5p252c8ncBLzLce52SEUz2Ebj8faDnuUXz2UuHEONYcJ+G0oaCF+bYCWVZtrGzq3FD7YXetmnSA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^7.0.0
- eslint: ^8.56.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
+ '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/parser@6.21.0':
resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==}
@@ -4702,37 +4669,35 @@ packages:
typescript:
optional: true
- '@typescript-eslint/parser@7.1.0':
- resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ '@typescript-eslint/parser@8.26.0':
+ resolution: {integrity: sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^8.56.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/parser@7.17.0':
- resolution: {integrity: sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/parser@8.27.0':
+ resolution: {integrity: sha512-XGwIabPallYipmcOk45DpsBSgLC64A0yvdAkrwEzwZ2viqGqRUJ8eEYoPz0CWnutgAFbNMPdsGGvzjSmcWVlEA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^8.56.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/scope-manager@6.21.0':
resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==}
engines: {node: ^16.0.0 || >=18.0.0}
- '@typescript-eslint/scope-manager@7.1.0':
- resolution: {integrity: sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ '@typescript-eslint/scope-manager@8.26.0':
+ resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/scope-manager@7.17.0':
- resolution: {integrity: sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/scope-manager@8.27.0':
+ resolution: {integrity: sha512-8oI9GwPMQmBryaaxG1tOZdxXVeMDte6NyJA4i7/TWa4fBwgnAXYlIQP+uYOeqAaLJ2JRxlG9CAyL+C+YE9Xknw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/scope-manager@8.28.0':
+ resolution: {integrity: sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/type-utils@6.21.0':
resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==}
@@ -4744,37 +4709,35 @@ packages:
typescript:
optional: true
- '@typescript-eslint/type-utils@7.1.0':
- resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ '@typescript-eslint/type-utils@8.26.0':
+ resolution: {integrity: sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^8.56.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/type-utils@7.17.0':
- resolution: {integrity: sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/type-utils@8.27.0':
+ resolution: {integrity: sha512-wVArTVcz1oJOIEJxui/nRhV0TXzD/zMSOYi/ggCfNq78EIszddXcJb7r4RCp/oBrjt8n9A0BSxRMKxHftpDxDA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^8.56.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/types@6.21.0':
resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==}
engines: {node: ^16.0.0 || >=18.0.0}
- '@typescript-eslint/types@7.1.0':
- resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ '@typescript-eslint/types@8.26.0':
+ resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/types@7.17.0':
- resolution: {integrity: sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/types@8.27.0':
+ resolution: {integrity: sha512-/6cp9yL72yUHAYq9g6DsAU+vVfvQmd1a8KyA81uvfDE21O2DwQ/qxlM4AR8TSdAu+kJLBDrEHKC5/W2/nxsY0A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/types@8.28.0':
+ resolution: {integrity: sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@6.21.0':
resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==}
@@ -4785,23 +4748,23 @@ packages:
typescript:
optional: true
- '@typescript-eslint/typescript-estree@7.1.0':
- resolution: {integrity: sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ '@typescript-eslint/typescript-estree@8.26.0':
+ resolution: {integrity: sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
+ typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/typescript-estree@7.17.0':
- resolution: {integrity: sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/typescript-estree@8.27.0':
+ resolution: {integrity: sha512-BnKq8cqPVoMw71O38a1tEb6iebEgGA80icSxW7g+kndx0o6ot6696HjG7NdgfuAVmVEtwXUr3L8R9ZuVjoQL6A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
+ typescript: '>=4.8.4 <5.9.0'
+
+ '@typescript-eslint/typescript-estree@8.28.0':
+ resolution: {integrity: sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/utils@6.21.0':
resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==}
@@ -4809,71 +4772,99 @@ packages:
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
- '@typescript-eslint/utils@7.1.0':
- resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ '@typescript-eslint/utils@8.26.0':
+ resolution: {integrity: sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^8.56.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
- '@typescript-eslint/utils@7.17.0':
- resolution: {integrity: sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/utils@8.27.0':
+ resolution: {integrity: sha512-njkodcwH1yvmo31YWgRHNb/x1Xhhq4/m81PhtvmRngD8iHPehxffz1SNCO+kwaePhATC+kOa/ggmvPoPza5i0Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^8.56.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
+
+ '@typescript-eslint/utils@8.28.0':
+ resolution: {integrity: sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.9.0'
'@typescript-eslint/visitor-keys@6.21.0':
resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==}
engines: {node: ^16.0.0 || >=18.0.0}
- '@typescript-eslint/visitor-keys@7.1.0':
- resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==}
- engines: {node: ^16.0.0 || >=18.0.0}
+ '@typescript-eslint/visitor-keys@8.26.0':
+ resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/visitor-keys@8.27.0':
+ resolution: {integrity: sha512-WsXQwMkILJvffP6z4U3FYJPlbf/j07HIxmDjZpbNvBJkMfvwXj5ACRkkHwBDvLBbDbtX5TdU64/rcvKJ/vuInQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/visitor-keys@7.17.0':
- resolution: {integrity: sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/visitor-keys@8.28.0':
+ resolution: {integrity: sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.2.0':
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
- '@vitejs/plugin-vue@5.2.0':
- resolution: {integrity: sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==}
+ '@vitejs/plugin-vue@5.2.3':
+ resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
- vite: ^5.0.0
+ vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
- '@vitest/coverage-v8@1.6.0':
- resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==}
+ '@vitest/coverage-v8@3.0.9':
+ resolution: {integrity: sha512-15OACZcBtQ34keIEn19JYTVuMFTlFrClclwWjHo/IRPg/8ELpkgNTl0o7WLP9WO9XGH6+tip9CPYtEOrIDJvBA==}
peerDependencies:
- vitest: 1.6.0
-
- '@vitest/expect@1.6.0':
- resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
+ '@vitest/browser': 3.0.9
+ vitest: 3.0.9
+ peerDependenciesMeta:
+ '@vitest/browser':
+ optional: true
'@vitest/expect@2.0.5':
resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==}
+ '@vitest/expect@3.0.9':
+ resolution: {integrity: sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==}
+
+ '@vitest/mocker@3.0.9':
+ resolution: {integrity: sha512-ryERPIBOnvevAkTq+L1lD+DTFBRcjueL9lOUfXsLfwP92h4e+Heb+PjiqS3/OURWPtywfafK0kj++yDFjWUmrA==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
'@vitest/pretty-format@2.0.5':
resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==}
'@vitest/pretty-format@2.1.2':
resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==}
- '@vitest/runner@1.6.0':
- resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==}
+ '@vitest/pretty-format@3.0.9':
+ resolution: {integrity: sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==}
- '@vitest/snapshot@1.6.0':
- resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==}
+ '@vitest/runner@3.0.9':
+ resolution: {integrity: sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw==}
- '@vitest/spy@1.6.0':
- resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==}
+ '@vitest/snapshot@3.0.9':
+ resolution: {integrity: sha512-AiLUiuZ0FuA+/8i19mTYd+re5jqjEc2jZbgJ2up0VY0Ddyyxg/uUtBDpIFAy4uzKaQxOW8gMgBdAJJ2ydhu39A==}
'@vitest/spy@2.0.5':
resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==}
- '@vitest/utils@1.6.0':
- resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==}
+ '@vitest/spy@3.0.9':
+ resolution: {integrity: sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==}
'@vitest/utils@2.0.5':
resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==}
@@ -4881,41 +4872,44 @@ packages:
'@vitest/utils@2.1.2':
resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==}
+ '@vitest/utils@3.0.9':
+ resolution: {integrity: sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==}
+
'@volar/language-core@2.2.0':
resolution: {integrity: sha512-a8WG9+4OdeNDW4ywABZIM6S6UN7em8uIlM/BZ2pWQUYrVmX+m8sj/X+QadvO+Li/t/LjAqbWJQtVgxdpEWLALQ==}
- '@volar/language-core@2.4.10':
- resolution: {integrity: sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==}
+ '@volar/language-core@2.4.12':
+ resolution: {integrity: sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==}
'@volar/source-map@2.2.0':
resolution: {integrity: sha512-HQlPRlHOVqCCHK8wI76ZldHkEwKsjp7E6idUc36Ekni+KJDNrqgSqPvyHQixybXPHNU7CI9Uxd9/IkxO7LuNBw==}
- '@volar/source-map@2.4.10':
- resolution: {integrity: sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==}
+ '@volar/source-map@2.4.12':
+ resolution: {integrity: sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw==}
'@volar/typescript@2.2.0':
resolution: {integrity: sha512-wC6l4zLiiCLxF+FGaHCbWlQYf4vMsnRxYhcI6WgvaNppOD6r1g+Ef1RKRJUApALWU46Yy/JDU/TbdV6w/X6Liw==}
- '@volar/typescript@2.4.10':
- resolution: {integrity: sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==}
-
- '@vue/compiler-core@3.5.11':
- resolution: {integrity: sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg==}
+ '@volar/typescript@2.4.12':
+ resolution: {integrity: sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g==}
'@vue/compiler-core@3.5.12':
resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==}
- '@vue/compiler-dom@3.5.11':
- resolution: {integrity: sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==}
+ '@vue/compiler-core@3.5.13':
+ resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
'@vue/compiler-dom@3.5.12':
resolution: {integrity: sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==}
- '@vue/compiler-sfc@3.5.12':
- resolution: {integrity: sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==}
+ '@vue/compiler-dom@3.5.13':
+ resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==}
+
+ '@vue/compiler-sfc@3.5.13':
+ resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==}
- '@vue/compiler-ssr@3.5.12':
- resolution: {integrity: sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==}
+ '@vue/compiler-ssr@3.5.13':
+ resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==}
'@vue/compiler-vue2@2.7.16':
resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
@@ -4928,34 +4922,34 @@ packages:
typescript:
optional: true
- '@vue/language-core@2.1.10':
- resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==}
+ '@vue/language-core@2.2.8':
+ resolution: {integrity: sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
- '@vue/reactivity@3.5.12':
- resolution: {integrity: sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==}
+ '@vue/reactivity@3.5.13':
+ resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
- '@vue/runtime-core@3.5.12':
- resolution: {integrity: sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==}
+ '@vue/runtime-core@3.5.13':
+ resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
- '@vue/runtime-dom@3.5.12':
- resolution: {integrity: sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==}
+ '@vue/runtime-dom@3.5.13':
+ resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==}
- '@vue/server-renderer@3.5.12':
- resolution: {integrity: sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==}
+ '@vue/server-renderer@3.5.13':
+ resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==}
peerDependencies:
- vue: 3.5.12
-
- '@vue/shared@3.5.11':
- resolution: {integrity: sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==}
+ vue: 3.5.13
'@vue/shared@3.5.12':
resolution: {integrity: sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==}
+ '@vue/shared@3.5.13':
+ resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
+
'@vue/test-utils@2.4.1':
resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==}
peerDependencies:
@@ -4965,6 +4959,46 @@ packages:
'@vue/server-renderer':
optional: true
+ '@xhmikosr/archive-type@7.0.0':
+ resolution: {integrity: sha512-sIm84ZneCOJuiy3PpWR5bxkx3HaNt1pqaN+vncUBZIlPZCq8ASZH+hBVdu5H8znR7qYC6sKwx+ie2Q7qztJTxA==}
+ engines: {node: ^14.14.0 || >=16.0.0}
+
+ '@xhmikosr/bin-check@7.0.3':
+ resolution: {integrity: sha512-4UnCLCs8DB+itHJVkqFp9Zjg+w/205/J2j2wNBsCEAm/BuBmtua2hhUOdAMQE47b1c7P9Xmddj0p+X1XVsfHsA==}
+ engines: {node: '>=18'}
+
+ '@xhmikosr/bin-wrapper@13.0.5':
+ resolution: {integrity: sha512-DT2SAuHDeOw0G5bs7wZbQTbf4hd8pJ14tO0i4cWhRkIJfgRdKmMfkDilpaJ8uZyPA0NVRwasCNAmMJcWA67osw==}
+ engines: {node: '>=18'}
+
+ '@xhmikosr/decompress-tar@8.0.1':
+ resolution: {integrity: sha512-dpEgs0cQKJ2xpIaGSO0hrzz3Kt8TQHYdizHsgDtLorWajuHJqxzot9Hbi0huRxJuAGG2qiHSQkwyvHHQtlE+fg==}
+ engines: {node: '>=18'}
+
+ '@xhmikosr/decompress-tarbz2@8.0.2':
+ resolution: {integrity: sha512-p5A2r/AVynTQSsF34Pig6olt9CvRj6J5ikIhzUd3b57pUXyFDGtmBstcw+xXza0QFUh93zJsmY3zGeNDlR2AQQ==}
+ engines: {node: '>=18'}
+
+ '@xhmikosr/decompress-targz@8.0.1':
+ resolution: {integrity: sha512-mvy5AIDIZjQ2IagMI/wvauEiSNHhu/g65qpdM4EVoYHUJBAmkQWqcPJa8Xzi1aKVTmOA5xLJeDk7dqSjlHq8Mg==}
+ engines: {node: '>=18'}
+
+ '@xhmikosr/decompress-unzip@7.0.0':
+ resolution: {integrity: sha512-GQMpzIpWTsNr6UZbISawsGI0hJ4KA/mz5nFq+cEoPs12UybAqZWKbyIaZZyLbJebKl5FkLpsGBkrplJdjvUoSQ==}
+ engines: {node: '>=18'}
+
+ '@xhmikosr/decompress@10.0.1':
+ resolution: {integrity: sha512-6uHnEEt5jv9ro0CDzqWlFgPycdE+H+kbJnwyxgZregIMLQ7unQSCNVsYG255FoqU8cP46DyggI7F7LohzEl8Ag==}
+ engines: {node: '>=18'}
+
+ '@xhmikosr/downloader@15.0.1':
+ resolution: {integrity: sha512-fiuFHf3Dt6pkX8HQrVBsK0uXtkgkVlhrZEh8b7VgoDqFf+zrgFBPyrwCqE/3nDwn3hLeNz+BsrS7q3mu13Lp1g==}
+ engines: {node: '>=18'}
+
+ '@xhmikosr/os-filter-obj@3.0.0':
+ resolution: {integrity: sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==}
+ engines: {node: ^14.14.0 || >=16.0.0}
+
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
@@ -4993,17 +5027,13 @@ packages:
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
- acorn-walk@8.3.2:
- resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
- engines: {node: '>=0.4.0'}
-
acorn@7.4.1:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
engines: {node: '>=0.4.0'}
hasBin: true
- acorn@8.14.0:
- resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
+ acorn@8.14.1:
+ resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
engines: {node: '>=0.4.0'}
hasBin: true
@@ -5011,6 +5041,10 @@ packages:
resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
engines: {node: '>= 14'}
+ agent-base@7.1.3:
+ resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
+ engines: {node: '>= 14'}
+
aggregate-error@3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
@@ -5052,8 +5086,16 @@ 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==}
+ alien-signals@1.0.7:
+ resolution: {integrity: sha512-OfUBerxNtc4PsNwkSu8KVHMOJUKmFLmLmeYsBBTnwzlezm+LmvJk31iE7Ggk1hS/S7GIrn9QNGm+NlkhxJmMQQ==}
+
+ analytics-utils@1.0.14:
+ resolution: {integrity: sha512-9v0kPd8v0GuBvfQcg5BO48AElaEAr9IXMAfJWXYMAhrD3QprgozEIUgMp/de0vS136PUOBB+10XQH9eBgBmfMw==}
+ peerDependencies:
+ '@types/dlv': ^1.0.0
+
+ analytics@0.8.16:
+ resolution: {integrity: sha512-LEFQ47G9V1zVp9WIh2xhnbmSFEJq+WEzSv6voJ5uba88lefiIIYeG2nq87gFu83ocz1qtb9u7XgeaKKVBbbgWA==}
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
@@ -5090,6 +5132,10 @@ packages:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
+ ansis@3.17.0:
+ resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==}
+ engines: {node: '>=14'}
+
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
@@ -5107,6 +5153,9 @@ packages:
arch@2.2.0:
resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
+ arch@3.0.0:
+ resolution: {integrity: sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==}
+
archiver-utils@5.0.2:
resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==}
engines: {node: '>= 14'}
@@ -5134,9 +5183,6 @@ packages:
aria-query@5.3.0:
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
- array-buffer-byte-length@1.0.0:
- resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
-
array-buffer-byte-length@1.0.1:
resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
engines: {node: '>= 0.4'}
@@ -5164,10 +5210,6 @@ packages:
resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
engines: {node: '>= 0.4'}
- arraybuffer.prototype.slice@1.0.1:
- resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==}
- engines: {node: '>= 0.4'}
-
arraybuffer.prototype.slice@1.0.3:
resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
engines: {node: '>= 0.4'}
@@ -5196,9 +5238,6 @@ packages:
resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
engines: {node: '>=0.8'}
- assertion-error@1.1.0:
- resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
-
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -5246,8 +5285,8 @@ packages:
avvio@9.0.0:
resolution: {integrity: sha512-UbYrOXgE/I+knFG+3kJr9AgC7uNo8DG+FGGODpH9Bj1O1kL/QDjBXnTem9leD3VdQKtaHjV3O85DQ7hHh4IIHw==}
- aws-sdk-client-mock@4.0.1:
- resolution: {integrity: sha512-yD2mmgy73Xce097G5hIpr1k7j50qzvJ49/+6osGZiCyk4m6cwhb+2x7kKFY1gEMwTzaS8+m8fXv9SB29SkRYyQ==}
+ aws-sdk-client-mock@4.1.0:
+ resolution: {integrity: sha512-h/tOYTkXEsAcV3//6C1/7U4ifSpKyJvb6auveAepqqNJl6TdZaPFEtKjBQNf8UxQdDP850knB2i/whq4zlsxJw==}
aws-sign2@0.7.0:
resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==}
@@ -5261,8 +5300,8 @@ packages:
axios@1.7.4:
resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==}
- axios@1.7.7:
- resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
+ axios@1.8.4:
+ resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
b4a@1.6.4:
resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==}
@@ -5315,22 +5354,14 @@ packages:
resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==}
engines: {node: '>=12.0.0'}
- bin-check@4.1.0:
- resolution: {integrity: sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==}
- engines: {node: '>=4'}
-
- bin-version-check@5.0.0:
- resolution: {integrity: sha512-Q3FMQnS5eZmrBGqmDXLs4dbAn/f+52voP6ykJYmweSA60t6DyH4UTSwZhtbK5UH+LBoWvDljILUQMLRUtsynsA==}
+ bin-version-check@5.1.0:
+ resolution: {integrity: sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==}
engines: {node: '>=12'}
bin-version@6.0.0:
resolution: {integrity: sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==}
engines: {node: '>=12'}
- binary-extensions@2.2.0:
- resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
- engines: {node: '>=8'}
-
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -5362,10 +5393,6 @@ packages:
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
- braces@3.0.2:
- resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
- engines: {node: '>=8'}
-
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
@@ -5376,13 +5403,13 @@ 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==}
+ browserslist@4.23.0:
+ resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
- browserslist@4.23.0:
- resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
+ browserslist@4.24.4:
+ resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
@@ -5415,16 +5442,12 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
- bufferutil@4.0.7:
- resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==}
+ bufferutil@4.0.9:
+ resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==}
engines: {node: '>=6.14.2'}
- bufferutil@4.0.8:
- resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
- engines: {node: '>=6.14.2'}
-
- bullmq@5.26.1:
- resolution: {integrity: sha512-XuxCGFlC1PQ2i1JHQiB9dqkqKQILMwQpU7ipi+cT/dzJaoXVcS0/IByUz6SsZ3xyOQY3twPt6G7J2d5GrsJuEA==}
+ bullmq@5.44.1:
+ resolution: {integrity: sha512-NEWQIixKw5QdXmSnhQU25A336N10DvxuDcmiVXQyuwSx1lrzfNIdQA3BNADfpAUwHtuJkJImHVLCM3/KEr9nWA==}
buraha@0.0.1:
resolution: {integrity: sha512-G563A0mTbzknm2jDaNxfZuNKIdeArs8T+XQN6t+KbmgnOoevXSXhKDkyf8Md/36Jrx99ikwbCag37VGe3myExQ==}
@@ -5445,10 +5468,6 @@ packages:
resolution: {integrity: sha512-I7mVOPl3PUCeRub1U8YoGz2Lqv9WOBpobZ8RyWFXmReuILz+3OAyTa5oH3QPdtKZD7N0Yk00aLfzn0qvp8dZ1w==}
engines: {node: ^16.14.0 || >=18.0.0}
- cacheable-lookup@5.0.4:
- resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
- engines: {node: '>=10.6.0'}
-
cacheable-lookup@7.0.0:
resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
engines: {node: '>=14.16'}
@@ -5461,14 +5480,14 @@ packages:
resolution: {integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==}
engines: {node: '>=18'}
- cacheable-request@7.0.2:
- resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==}
- engines: {node: '>=8'}
-
cachedir@2.3.0:
resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==}
engines: {node: '>=6'}
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
call-bind@1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
@@ -5498,12 +5517,12 @@ 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==}
+ caniuse-lite@1.0.30001707:
+ resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==}
+
canonicalize@1.0.8:
resolution: {integrity: sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==}
@@ -5524,14 +5543,14 @@ packages:
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
- chai@4.3.10:
- resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==}
- engines: {node: '>=4'}
-
chai@5.1.1:
resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
engines: {node: '>=12'}
+ chai@5.2.0:
+ resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
+ engines: {node: '>=12'}
+
chalk-template@1.1.0:
resolution: {integrity: sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==}
engines: {node: '>=14.16'}
@@ -5548,8 +5567,8 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
- chalk@5.3.0:
- resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
+ chalk@5.4.1:
+ resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
char-regex@1.0.2:
@@ -5568,8 +5587,8 @@ packages:
character-parser@2.2.0:
resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==}
- chart.js@4.4.6:
- resolution: {integrity: sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==}
+ chart.js@4.4.8:
+ resolution: {integrity: sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==}
engines: {pnpm: '>=8'}
chartjs-adapter-date-fns@3.0.0:
@@ -5578,8 +5597,8 @@ packages:
chart.js: '>=2.8.0'
date-fns: '>=2.0.0'
- chartjs-chart-matrix@2.0.1:
- resolution: {integrity: sha512-BGfeY+/PHnITyDlc7WfnKJ1RyOfgOzIqWp/gxzzl7pUjyoGzHDcw51qd2xJF9gdT9Def7ZwOnOMm8GJUXDxI0w==}
+ chartjs-chart-matrix@2.1.1:
+ resolution: {integrity: sha512-hJ5NKGYqfM37mnkr3XXIJDn9Eij4G7mbNsNxY1zEmtoVLu/k6HO9yL8sL8vFgVnJbhWqAJdlrb+dlTOFKh8xfA==}
peerDependencies:
chart.js: '>=3.0.0'
@@ -5588,14 +5607,11 @@ packages:
peerDependencies:
chart.js: '>=2.6.0'
- chartjs-plugin-zoom@2.0.1:
- resolution: {integrity: sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==}
+ chartjs-plugin-zoom@2.2.0:
+ resolution: {integrity: sha512-in6kcdiTlP6npIVLMd4zXZ08PDUXC52gZ4FAy5oyjk1zX3gKarXMAof7B9eFiisf9WOC3bh2saHg+J5WtLXZeA==}
peerDependencies:
chart.js: '>=3.2.0'
- check-error@1.0.3:
- resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
-
check-error@2.1.1:
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'}
@@ -5611,13 +5627,9 @@ packages:
resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==}
engines: {node: '>=18.17'}
- cheerio@1.0.0-rc.12:
- resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
- engines: {node: '>= 6'}
-
- chokidar@3.5.3:
- resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
- engines: {node: '>= 8.10.0'}
+ chokidar@4.0.3:
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
@@ -5626,8 +5638,12 @@ packages:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
- chromatic@11.18.1:
- resolution: {integrity: sha512-hkNT9vA6K9+PnE/khhZYBnRCOm8NonaQDs7RZ8YHFo7/lh1b/x/uFMkTjWjaj/mkM6QOR/evu5VcZMtcaauSlw==}
+ chownr@3.0.0:
+ resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+ engines: {node: '>=18'}
+
+ chromatic@11.27.0:
+ resolution: {integrity: sha512-jQ2ufjS+ePpg+NtcPI9B2eOi+pAzlRd2nhd1LgNMsVCC9Bzf5t8mJtyd8v2AUuJS0LdX0QVBgkOnlNv9xviHzA==}
hasBin: true
peerDependencies:
'@chromatic-com/cypress': ^0.*.* || ^1.0.0
@@ -5688,9 +5704,6 @@ packages:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
- clone-response@1.0.3:
- resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
-
cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
@@ -5699,10 +5712,6 @@ packages:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
- code-error-fragment@0.0.230:
- resolution: {integrity: sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==}
- engines: {node: '>= 4'}
-
collect-v8-coverage@1.0.1:
resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==}
@@ -5794,8 +5803,9 @@ packages:
config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
- consola@2.15.3:
- resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
constantinople@4.0.1:
resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==}
@@ -5814,10 +5824,6 @@ packages:
cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
- 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'}
@@ -5854,12 +5860,12 @@ packages:
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
- cron-parser@4.8.1:
- resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==}
+ cron-parser@4.9.0:
+ resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
engines: {node: '>=12.0.0'}
- cropperjs@2.0.0-rc.2:
- resolution: {integrity: sha512-BTuz+UeZphGOEnBCuQiNT4rk1uFfKJaKmTgoH9XU7Q8IMkLdodW7YPWINmXJXwWMt1nXiKze5qKADVbz9xtVFg==}
+ cropperjs@2.0.0:
+ resolution: {integrity: sha512-TO2j0Qre01kPHbow4FuTrbdEB4jTmGRySxW49jyEIqlJZuEBfrvCTT0vC3eRB2WBXudDfKi1Onako6DKWKxeAQ==}
cross-env@7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
@@ -5872,13 +5878,14 @@ packages:
cross-fetch@4.0.0:
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
- cross-spawn@5.1.0:
- resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
-
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
css-declaration-sorter@7.2.0:
resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==}
engines: {node: ^14 || ^16 || >=18}
@@ -5908,21 +5915,21 @@ packages:
engines: {node: '>=4'}
hasBin: true
- cssnano-preset-default@6.1.2:
- resolution: {integrity: sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==}
- engines: {node: ^14 || ^16 || >=18.0}
+ cssnano-preset-default@7.0.6:
+ resolution: {integrity: sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- cssnano-utils@4.0.2:
- resolution: {integrity: sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ cssnano-utils@5.0.0:
+ resolution: {integrity: sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- cssnano@6.1.2:
- resolution: {integrity: sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==}
- engines: {node: ^14 || ^16 || >=18.0}
+ cssnano@7.0.6:
+ resolution: {integrity: sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
@@ -5930,8 +5937,8 @@ packages:
resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
- cssstyle@4.0.1:
- resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==}
+ cssstyle@4.3.0:
+ resolution: {integrity: sha512-6r0NiY0xizYqfBvWp1G7WXJ06/bZyrk7Dc6PHql82C/pKGUTKu4yAX4Y8JPamb1ob9nBKuxWzCGTRuGwU3yxJQ==}
engines: {node: '>=18'}
csstype@3.1.3:
@@ -5970,6 +5977,9 @@ packages:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
+ date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
dayjs@1.11.10:
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
@@ -6019,6 +6029,15 @@ packages:
supports-color:
optional: true
+ debug@4.4.0:
+ resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
decamelize-keys@1.1.1:
resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
engines: {node: '>=0.10.0'}
@@ -6056,10 +6075,6 @@ packages:
deep-email-validator@0.1.21:
resolution: {integrity: sha512-DBAmMzbr+MAubXQ+TS9tZuPwLcdKscb8YzKZiwoLqF3NmaeEgXvSSHhZ0EXOFeKFE2FNWC4mNXCyiQ/JdFXUwg==}
- deep-eql@4.1.3:
- resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
- engines: {node: '>=6'}
-
deep-eql@5.0.2:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
@@ -6078,6 +6093,10 @@ packages:
resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
engines: {node: '>=0.10.0'}
+ defaults@3.0.0:
+ resolution: {integrity: sha512-RsqXDEAALjfRTro+IFNKpcPCt0/Cy2FqHSIlnomiJp9YGadpQnrtbRpSgN2+np21qHcIKiva4fiOQGjS9/qR/A==}
+ engines: {node: '>=18'}
+
defer-to-connect@2.0.1:
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
engines: {node: '>=10'}
@@ -6118,6 +6137,11 @@ packages:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ detect-libc@1.0.3:
+ resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
@@ -6136,10 +6160,6 @@ packages:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- diff@5.1.0:
- resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
- engines: {node: '>=0.3.1'}
-
diff@5.2.0:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'}
@@ -6154,6 +6174,9 @@ packages:
disposable-email-domains@1.0.62:
resolution: {integrity: sha512-LBQvhRw7mznQTPoyZbsmYeNOZt1pN5aCsx4BAU/3siVFuiM9f2oyKzUaB8v1jbxFjE3aYqYiMo63kAL4pHgfWQ==}
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
@@ -6205,6 +6228,10 @@ packages:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'}
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
@@ -6225,21 +6252,19 @@ packages:
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
- ejs@3.1.10:
- resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
- 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==}
+ electron-to-chromium@1.5.123:
+ resolution: {integrity: sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==}
+
emittery@0.13.1:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
engines: {node: '>=12'}
+ emoji-regex-xs@1.0.0:
+ resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -6284,10 +6309,6 @@ packages:
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
- es-abstract@1.22.1:
- resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==}
- engines: {node: '>= 0.4'}
-
es-abstract@1.23.3:
resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==}
engines: {node: '>= 0.4'}
@@ -6296,6 +6317,10 @@ packages:
resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
engines: {node: '>= 0.4'}
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
@@ -6303,20 +6328,24 @@ packages:
es-get-iterator@1.1.3:
resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==}
+ es-module-lexer@1.6.0:
+ resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==}
+
es-object-atoms@1.0.0:
resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==}
engines: {node: '>= 0.4'}
- es-set-tostringtag@2.0.1:
- resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==}
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
es-set-tostringtag@2.0.3:
resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==}
engines: {node: '>= 0.4'}
- es-shim-unscopables@1.0.0:
- resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==}
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
es-shim-unscopables@1.0.2:
resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
@@ -6333,18 +6362,13 @@ packages:
peerDependencies:
esbuild: '>=0.12 <1'
- esbuild@0.18.20:
- resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
- engines: {node: '>=12'}
- hasBin: true
-
- esbuild@0.21.5:
- resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
- engines: {node: '>=12'}
+ esbuild@0.25.0:
+ resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==}
+ engines: {node: '>=18'}
hasBin: true
- esbuild@0.24.0:
- resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==}
+ esbuild@0.25.1:
+ resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==}
engines: {node: '>=18'}
hasBin: true
@@ -6352,6 +6376,10 @@ packages:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
escape-goat@3.0.0:
resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==}
engines: {node: '>=10'}
@@ -6412,16 +6440,6 @@ packages:
eslint-import-resolver-webpack:
optional: true
- eslint-plugin-import@2.30.0:
- resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==}
- engines: {node: '>=4'}
- peerDependencies:
- '@typescript-eslint/parser': '*'
- eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
- peerDependenciesMeta:
- '@typescript-eslint/parser':
- optional: true
-
eslint-plugin-import@2.31.0:
resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==}
engines: {node: '>=4'}
@@ -6432,11 +6450,12 @@ packages:
'@typescript-eslint/parser':
optional: true
- eslint-plugin-vue@9.31.0:
- resolution: {integrity: sha512-aYMUCgivhz1o4tLkRHj5oq9YgYPM4/EJc0M7TAKRLCUA5OYxRLAhYEVD2nLtTwLyixEFI+/QXSvKU9ESZFgqjQ==}
- engines: {node: ^14.17.0 || >=16.0.0}
+ eslint-plugin-vue@10.0.0:
+ resolution: {integrity: sha512-XKckedtajqwmaX6u1VnECmZ6xJt+YvlmMzBPZd+/sI3ub2lpYZyFnsyWo7c3nMOQKJQudeyk1lw/JxdgeKT64w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
+ eslint: ^8.57.0 || ^9.0.0
+ vue-eslint-parser: ^10.0.0
eslint-rule-docs@1.1.235:
resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==}
@@ -6449,6 +6468,10 @@ packages:
resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint-scope@8.3.0:
+ resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==}
+ 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}
@@ -6473,6 +6496,16 @@ packages:
jiti:
optional: true
+ eslint@9.22.0:
+ resolution: {integrity: sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
espree@10.3.0:
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -6486,10 +6519,6 @@ packages:
engines: {node: '>=4'}
hasBin: true
- esquery@1.4.2:
- resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==}
- engines: {node: '>=0.10'}
-
esquery@1.6.0:
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
engines: {node: '>=0.10'}
@@ -6536,10 +6565,6 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
- execa@0.7.0:
- resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==}
- engines: {node: '>=4'}
-
execa@4.1.0:
resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==}
engines: {node: '>=10'}
@@ -6556,6 +6581,10 @@ packages:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
+ execa@9.5.2:
+ resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==}
+ engines: {node: ^18.19.0 || >=20.5.0}
+
executable@4.1.1:
resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==}
engines: {node: '>=4'}
@@ -6568,6 +6597,10 @@ packages:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
+ expect-type@1.2.0:
+ resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==}
+ engines: {node: '>=12.0.0'}
+
expect@29.7.0:
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -6575,14 +6608,14 @@ packages:
exponential-backoff@3.1.1:
resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==}
- 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'}
+ express@4.21.2:
+ resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
+ engines: {node: '>= 0.10.0'}
+
ext-list@2.2.2:
resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==}
engines: {node: '>=0.10.0'}
@@ -6615,8 +6648,8 @@ packages:
fast-fifo@1.3.0:
resolution: {integrity: sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw==}
- fast-glob@3.3.2:
- resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
fast-json-stable-stringify@2.1.0:
@@ -6644,10 +6677,6 @@ packages:
fast-uri@3.0.1:
resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==}
- fast-xml-parser@4.2.5:
- resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==}
- hasBin: true
-
fast-xml-parser@4.4.1:
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
hasBin: true
@@ -6669,8 +6698,8 @@ packages:
resolution: {integrity: sha512-2qfoaQ3BQDhZ1gtbkKZd6n0kKxJISJGM6u/skD9ljdWItAscjXrtZ1lnjr7PavmXX9j4EyCPmBDiIsLn07d5vA==}
engines: {node: '>= 10'}
- fastify@5.0.0:
- resolution: {integrity: sha512-Qe4dU+zGOzg7vXjw4EvcuyIbNnMwTmcuOhlOrOJsgwzvjEZmsM/IeHulgJk+r46STjdJS/ZJbxO8N70ODXDMEQ==}
+ fastify@5.2.1:
+ resolution: {integrity: sha512-rslrNBF67eg8/Gyn7P2URV8/6pz8kSAscFL4EThZJ8JBMaXacVdVE4hmUcnPNKERl5o/xTiBSLfdowBRhVF1WA==}
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
@@ -6693,6 +6722,10 @@ 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}
@@ -6701,28 +6734,17 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
- file-type@17.1.6:
- resolution: {integrity: sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
file-type@19.6.0:
resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==}
engines: {node: '>=18'}
- filelist@1.0.4:
- resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
-
filename-reserved-regex@3.0.0:
resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- filenamify@5.1.1:
- resolution: {integrity: sha512-M45CbrJLGACfrPOkrTp3j2EcO9OBkKUYME0eiqOCa7i2poaklU0jhlIaMlr8ijLorT0uLAzrn3qXOp5684CkfA==}
- engines: {node: '>=12.20'}
-
- fill-range@7.0.1:
- resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
- engines: {node: '>=8'}
+ filenamify@6.0.0:
+ resolution: {integrity: sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==}
+ engines: {node: '>=16'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
@@ -6810,14 +6832,17 @@ packages:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
- form-data@4.0.1:
- resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
+ form-data@4.0.2:
+ resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
engines: {node: '>= 6'}
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
+ forwarded-parse@2.1.2:
+ resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
+
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@@ -6832,9 +6857,9 @@ packages:
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
- fs-extra@7.0.1:
- resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
- engines: {node: '>=6 <7 || >=8'}
+ fs-extra@11.3.0:
+ resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
+ engines: {node: '>=14.14'}
fs-extra@8.1.0:
resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
@@ -6863,10 +6888,6 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
- function.prototype.name@1.1.5:
- resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
- engines: {node: '>= 0.4'}
-
function.prototype.name@1.1.6:
resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
engines: {node: '>= 0.4'}
@@ -6882,9 +6903,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
- get-func-name@2.0.2:
- resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
-
get-intrinsic@1.2.1:
resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
@@ -6892,13 +6910,17 @@ packages:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
get-package-type@0.1.0:
resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
engines: {node: '>=8.0.0'}
- get-stream@3.0.0:
- resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==}
- engines: {node: '>=4'}
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
get-stream@5.2.0:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
@@ -6916,16 +6938,12 @@ packages:
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
engines: {node: '>=18'}
- get-symbol-description@1.0.0:
- resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
- engines: {node: '>= 0.4'}
-
get-symbol-description@1.0.2:
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
engines: {node: '>= 0.4'}
- get-tsconfig@4.7.2:
- resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
+ get-tsconfig@4.10.0:
+ resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
getos@3.2.1:
resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==}
@@ -6944,22 +6962,25 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
- glob-promise@4.2.2:
- resolution: {integrity: sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==}
- engines: {node: '>=12'}
- peerDependencies:
- glob: ^7.1.6
-
glob@10.3.10:
resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
+ glob@10.4.5:
+ resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ hasBin: true
+
glob@11.0.0:
resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==}
engines: {node: 20 || >=22}
hasBin: true
+ glob@11.0.1:
+ resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==}
+ engines: {node: 20 || >=22}
+ hasBin: true
+
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
@@ -6985,8 +7006,8 @@ packages:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
- globals@15.12.0:
- resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==}
+ globals@16.0.0:
+ resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==}
engines: {node: '>=18'}
globalthis@1.0.3:
@@ -7000,24 +7021,21 @@ packages:
gopd@1.0.1:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
- got@11.8.5:
- resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==}
- engines: {node: '>=10.19.0'}
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
- got@12.6.1:
- resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==}
- engines: {node: '>=14.16'}
+ got@13.0.0:
+ resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==}
+ engines: {node: '>=16'}
- got@14.4.4:
- resolution: {integrity: sha512-tqiF7eSgTBwQkxb1LxsEpva8TaMYVisbhplrFVmw9GQE3855Z+MH/mnsXLLOkDxR6hZJRFMj5VTAZ8lmTF8ZOA==}
+ got@14.4.6:
+ resolution: {integrity: sha512-rnhwfM/PhMNJ1i17k3DuDqgj0cKx3IHxBKVv/WX1uDKqrhi2Gv3l7rhPThR/Cc6uU++dD97W9c8Y0qyw9x0jag==}
engines: {node: '>=20'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
- grapheme-splitter@1.0.4:
- resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
-
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
@@ -7029,11 +7047,12 @@ packages:
resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==}
engines: {node: '>=0.8.0'}
- happy-dom@10.0.3:
- resolution: {integrity: sha512-WkCP+Z5fX6U5PY+yHP3ElV5D9PoxRAHRWPFq3pG9rg/6Hjf5ak7dozAgSCywsTRUq2qfa8vV8OQvUy5pRXy8EQ==}
+ happy-dom@16.8.1:
+ resolution: {integrity: sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw==}
+ engines: {node: '>=18.0.0'}
- happy-dom@15.11.4:
- resolution: {integrity: sha512-AU6tzh3ADd28vSmXahgLsGyGGihXPGeKH0owDn9lhHolB6vIwEhag//T+TBzDoAcHhmVEwlxwSgtW1KZep+1MA==}
+ happy-dom@17.4.4:
+ resolution: {integrity: sha512-/Pb0ctk3HTZ5xEL3BZ0hK1AqDSAUuRQitOmROPHhfUYEWpmTImwfD8vFDGADmMAX0JYgbcgxWoLFKtsWhcpuVA==}
engines: {node: '>=18.0.0'}
hard-rejection@2.1.0:
@@ -7069,6 +7088,10 @@ packages:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
has-tostringtag@1.0.0:
resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
engines: {node: '>= 0.4'}
@@ -7092,8 +7115,8 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
- hast-util-to-html@9.0.3:
- resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==}
+ hast-util-to-html@9.0.5:
+ resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
@@ -7127,8 +7150,8 @@ packages:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
- html-entities@2.3.2:
- resolution: {integrity: sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==}
+ html-entities@2.5.2:
+ resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
@@ -7168,10 +7191,6 @@ packages:
resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==}
engines: {node: '>=0.10'}
- http2-wrapper@1.0.3:
- resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
- engines: {node: '>=10.19.0'}
-
http2-wrapper@2.2.1:
resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==}
engines: {node: '>=10.19.0'}
@@ -7188,6 +7207,10 @@ packages:
resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==}
engines: {node: '>= 14'}
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
human-signals@1.1.1:
resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==}
engines: {node: '>=8.12.0'}
@@ -7204,6 +7227,10 @@ 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'}
@@ -7221,16 +7248,16 @@ packages:
ignore-by-default@1.0.1:
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
- ignore-walk@6.0.5:
- resolution: {integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==}
- engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ ignore-walk@7.0.0:
+ resolution: {integrity: sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==}
+ engines: {node: ^18.17.0 || >=20.5.0}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
- immutable@4.2.2:
- resolution: {integrity: sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==}
+ immutable@5.0.3:
+ resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==}
import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
@@ -7277,6 +7304,9 @@ packages:
insert-text-at-cursor@0.3.0:
resolution: {integrity: sha512-/nPtyeX9xPUvxZf+r0518B7uqNKlP+LqNJqSiXFEaa2T71rWIwTVXGH7hB9xO/EVdwa5/pWlFCPwShOW81XIxQ==}
+ inspect-with-kind@1.0.5:
+ resolution: {integrity: sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==}
+
install-artifact-from-github@1.3.5:
resolution: {integrity: sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==}
hasBin: true
@@ -7292,8 +7322,8 @@ packages:
intersection-observer@0.12.2:
resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
- ioredis@5.4.1:
- resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==}
+ ioredis@5.6.0:
+ resolution: {integrity: sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==}
engines: {node: '>=12.22.0'}
ip-address@9.0.5:
@@ -7304,9 +7334,9 @@ packages:
resolution: {integrity: sha512-KifhLKBjdS/hB3TD4UUOalVp1BpzPFvRpgJvXcP0Ya98tuSQTUQ71iI7EW7CKddkBJTYB3GfTWl5eJwpLOXj2A==}
engines: {node: '>=16.14.0'}
- ip-regex@4.3.0:
- resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==}
- engines: {node: '>=8'}
+ ip-regex@5.0.0:
+ resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
ip@2.0.1:
resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==}
@@ -7343,10 +7373,6 @@ packages:
is-bigint@1.0.4:
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
- is-binary-path@2.1.0:
- resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
- engines: {node: '>=8'}
-
is-boolean-object@1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
@@ -7405,20 +7431,12 @@ packages:
resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==}
engines: {node: '>=10'}
- is-ip@3.1.0:
- resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==}
- engines: {node: '>=8'}
-
is-lambda@1.0.1:
resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
is-map@2.0.2:
resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
- is-negative-zero@2.0.2:
- resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
- engines: {node: '>= 0.4'}
-
is-negative-zero@2.0.3:
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'}
@@ -7470,10 +7488,6 @@ packages:
resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==}
engines: {node: '>= 0.4'}
- is-stream@1.1.0:
- resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
- engines: {node: '>=0.10.0'}
-
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
@@ -7513,6 +7527,10 @@ packages:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
+ is-unicode-supported@2.1.0:
+ resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
+ engines: {node: '>=18'}
+
is-weakmap@2.0.1:
resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
@@ -7526,9 +7544,6 @@ packages:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
- isarray@0.0.1:
- resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
-
isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -7565,14 +7580,18 @@ packages:
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
engines: {node: '>=10'}
- istanbul-lib-source-maps@5.0.4:
- resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==}
+ istanbul-lib-source-maps@5.0.6:
+ resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
engines: {node: '>=10'}
istanbul-reports@3.1.6:
resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==}
engines: {node: '>=8'}
+ istanbul-reports@3.1.7:
+ resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
+ engines: {node: '>=8'}
+
iterare@1.2.1:
resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==}
engines: {node: '>=6'}
@@ -7581,15 +7600,13 @@ packages:
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
engines: {node: '>=14'}
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
jackspeak@4.0.1:
resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==}
engines: {node: 20 || >=22}
- jake@10.8.5:
- resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==}
- engines: {node: '>=10'}
- hasBin: true
-
jest-changed-files@29.7.0:
resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -7745,9 +7762,6 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
- js-tokens@9.0.0:
- resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==}
-
js-yaml@3.14.1:
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
hasBin: true
@@ -7762,19 +7776,19 @@ packages:
jsbn@1.1.0:
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
- jschardet@3.0.0:
- resolution: {integrity: sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==}
+ jschardet@3.1.4:
+ resolution: {integrity: sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==}
engines: {node: '>=0.1.90'}
jsdoc-type-pratt-parser@4.1.0:
resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==}
engines: {node: '>=12.0.0'}
- jsdom@24.1.1:
- resolution: {integrity: sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==}
+ jsdom@26.0.0:
+ resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==}
engines: {node: '>=18'}
peerDependencies:
- canvas: ^2.11.2
+ canvas: ^3.0.0
peerDependenciesMeta:
canvas:
optional: true
@@ -7808,10 +7822,6 @@ packages:
json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
- json-to-ast@2.1.0:
- resolution: {integrity: sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==}
- engines: {node: '>= 4'}
-
json5@1.0.2:
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
hasBin: true
@@ -7833,8 +7843,8 @@ packages:
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
- jsonld@8.3.2:
- resolution: {integrity: sha512-MwBbq95szLwt8eVQ1Bcfwmgju/Y5P2GdtlHE2ncyfuYjIdEhluUVyj1eudacf1mOkWIoS9GpDBTECqhmq7EOaA==}
+ jsonld@8.3.3:
+ resolution: {integrity: sha512-9YcilrF+dLfg9NTEof/mJLMtbdX1RJ8dbWtJgE00cMOIohb1lIyJl710vFiTaiHTl6ZYODJuBd32xFvUhmv3kg==}
engines: {node: '>=14'}
jsonpointer@5.0.1:
@@ -7855,13 +7865,13 @@ packages:
jstransformer@1.0.0:
resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
- juice@11.0.0:
- resolution: {integrity: sha512-sGF8hPz9/Wg+YXbaNDqc1Iuoaw+J/P9lBHNQKXAGc9pPNjCd4fyPai0Zxj7MRtdjMr0lcgk5PjEIkP2b8R9F3w==}
+ juice@11.0.1:
+ resolution: {integrity: sha512-R3KLud4l/sN9AMmFZs0QY7cugGSiKvPhGyIsufCV5nJ0MjSlngUE7k80TmFeK9I62wOXrjWBtYA1knVs2OkF8w==}
engines: {node: '>=18.17'}
hasBin: true
- just-extend@4.2.1:
- resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==}
+ just-extend@6.2.0:
+ resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==}
jwa@2.0.0:
resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==}
@@ -7917,8 +7927,8 @@ packages:
light-my-request@6.1.0:
resolution: {integrity: sha512-+NFuhlOGoEwxeQfJ/pobkVFxcnKyDtiX847hLjuB/IzBxIl3q4VJeFI8uRCgb3AlTWL1lgOr+u5+8QdUcr33ng==}
- lilconfig@3.1.1:
- resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
lines-and-columns@1.2.4:
@@ -7933,10 +7943,6 @@ packages:
enquirer:
optional: true
- local-pkg@0.5.0:
- resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
- engines: {node: '>=14'}
-
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@@ -7981,19 +7987,11 @@ packages:
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
- loose-envify@1.4.0:
- resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
- hasBin: true
-
- loupe@2.3.7:
- resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
-
loupe@3.1.2:
resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==}
- lowercase-keys@2.0.0:
- resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
- engines: {node: '>=8'}
+ loupe@3.1.3:
+ resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
lowercase-keys@3.0.0:
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
@@ -8003,13 +8001,13 @@ packages:
resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
engines: {node: 14 || >=16.14}
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
lru-cache@11.0.0:
resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==}
engines: {node: 20 || >=22}
- lru-cache@4.1.5:
- resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
-
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -8036,14 +8034,11 @@ packages:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
engines: {node: '>=12'}
- magic-string@0.30.10:
- resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
+ magic-string@0.30.17:
+ resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
- magic-string@0.30.11:
- resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
-
- magicast@0.3.4:
- resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==}
+ magicast@0.3.5:
+ resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
mailcheck@1.1.1:
resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==}
@@ -8084,8 +8079,9 @@ packages:
engines: {node: '>= 12'}
hasBin: true
- matter-js@0.19.0:
- resolution: {integrity: sha512-v2huwvQGOHTGOkMqtHd2hercCG3f6QAObTisPPHg8TZqq2lz7eIY/5i/5YUV8Ibf3mEioFEmwibcPUF2/fnKKQ==}
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
matter-js@0.20.0:
resolution: {integrity: sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==}
@@ -8136,8 +8132,8 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
- meilisearch@0.45.0:
- resolution: {integrity: sha512-+zCzEqE+CumY4icB0Vox180adZqaNtnr60hJWGiEdmol5eWmksfY8rYsTcz87styXC2ZOg+2yF56gdH6oyIBTA==}
+ meilisearch@0.49.0:
+ resolution: {integrity: sha512-oMJ/e6Or6cz2+owcEKeB11p2OWiWW9NmssqOZC/KIwQB0sBGKLJ7RCpYzf+GhUIZIZ9FRYZ419ox3RGebVQX5g==}
memoizerific@1.11.3:
resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==}
@@ -8286,10 +8282,6 @@ packages:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
- mimic-response@1.0.1:
- resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
- engines: {node: '>=4'}
-
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@@ -8366,10 +8358,6 @@ packages:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
- minipass@7.0.4:
- resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
- engines: {node: '>=16 || 14 >=14.17'}
-
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -8378,6 +8366,10 @@ packages:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
+ minizlib@3.0.1:
+ resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==}
+ engines: {node: '>= 18'}
+
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
@@ -8390,16 +8382,13 @@ packages:
engines: {node: '>=10'}
hasBin: true
- mkdirp@2.1.6:
- resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==}
+ mkdirp@3.0.1:
+ resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
engines: {node: '>=10'}
hasBin: true
- mlly@1.5.0:
- resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==}
-
- mnemonist@0.39.8:
- resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==}
+ mnemonist@0.40.0:
+ resolution: {integrity: sha512-kdd8AFNig2AD5Rkih7EPCXhu/iMvwevQFX/uEiGhZyPZi7fHqOoF4V4kHLpCfysxXMgQ4B52kdPMCwARshKvEg==}
mock-socket@9.3.1:
resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==}
@@ -8436,8 +8425,8 @@ packages:
peerDependencies:
msw: ^2.0.0
- msw@2.6.4:
- resolution: {integrity: sha512-Pm4LmWQeytDsNCR+A7gt39XAdtH6zQb6jnIKRig0FlvYOn8eksn3s1nXxUfz5KYUjbckof7Z4p2ewzgffPoCbg==}
+ msw@2.7.3:
+ resolution: {integrity: sha512-+mycXv8l2fEAjFZ5sjrtjJDmm2ceKGjrNbBr1durRg6VkU9fNUE/gsmQ51hWbHqs+l35W1iM+ZsmOD9Fd6lspw==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
@@ -8467,13 +8456,13 @@ packages:
nan@2.20.0:
resolution: {integrity: sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==}
- nanoid@3.3.7:
- resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- nanoid@5.0.8:
- resolution: {integrity: sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==}
+ nanoid@5.1.5:
+ resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
engines: {node: ^18 || >=20}
hasBin: true
@@ -8507,8 +8496,8 @@ packages:
resolution: {integrity: sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==}
os: ['!win32']
- nise@5.1.4:
- resolution: {integrity: sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==}
+ nise@6.1.1:
+ resolution: {integrity: sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==}
node-abi@3.62.0:
resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==}
@@ -8545,10 +8534,6 @@ packages:
resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==}
hasBin: true
- node-gyp-build@4.6.0:
- resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
- hasBin: true
-
node-gyp-build@4.8.1:
resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
hasBin: true
@@ -8564,8 +8549,11 @@ packages:
node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
- nodemailer@6.9.16:
- resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==}
+ node-releases@2.0.19:
+ resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+
+ nodemailer@6.10.0:
+ resolution: {integrity: sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==}
engines: {node: '>=6.0.0'}
nodemon@3.1.7:
@@ -8573,6 +8561,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ nodemon@3.1.9:
+ resolution: {integrity: sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
nofilter@3.1.0:
resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==}
engines: {node: '>=12.19'}
@@ -8602,18 +8595,10 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
- normalize-url@6.1.0:
- resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
- engines: {node: '>=10'}
-
normalize-url@8.0.1:
resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==}
engines: {node: '>=14.16'}
- npm-run-path@2.0.2:
- resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==}
- engines: {node: '>=4'}
-
npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
@@ -8626,11 +8611,15 @@ 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==}
- nwsapi@2.2.12:
- resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==}
+ nwsapi@2.2.19:
+ resolution: {integrity: sha512-94bcyI3RsqiZufXjkr3ltkI86iEl+I7uiHVDtcq9wJUTwYQJ5odHDeSzkkrRzi80jJ8MaeZgqKjH1bAWAFw9bA==}
oauth2orize-pkce@0.1.2:
resolution: {integrity: sha512-grto2UYhXHi9GLE3IBgBBbV87xci55+bCyjpVuxKyzol6I5Rg0K1MiTuXE+JZk54R86SG2wqXODMiZYHraPpxw==}
@@ -8642,6 +8631,9 @@ packages:
oauth@0.10.0:
resolution: {integrity: sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==}
+ oauth@0.10.2:
+ resolution: {integrity: sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==}
+
object-assign-deep@0.4.0:
resolution: {integrity: sha512-54Uvn3s+4A/cMWx9tlRez1qtc7pN7pbQ+Yi7mjLjcBpWLlP+XbSHiHbQW6CElDiV4OvuzqnMrBdkgxI1mT8V/Q==}
engines: {node: '>=6'}
@@ -8713,8 +8705,11 @@ 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==}
+ oniguruma-parser@0.5.4:
+ resolution: {integrity: sha512-yNxcQ8sKvURiTwP0mV6bLQCYE7NKfKRRWunhbZnXgxSmB1OXa1lHrN3o4DZd+0Si0kU5blidK7BcROO8qv5TZA==}
+
+ oniguruma-to-es@4.1.0:
+ resolution: {integrity: sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA==}
open@8.4.2:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
@@ -8723,34 +8718,26 @@ packages:
openapi-types@12.1.3:
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
- openapi-typescript@6.7.3:
- resolution: {integrity: sha512-es3mGcDXV6TKPo6n3aohzHm0qxhLyR39MhF6mkD1FwFGjhxnqMqfSIgM0eCpInZvqatve4CxmXcMZw3jnnsaXw==}
+ openapi-typescript@6.7.6:
+ resolution: {integrity: sha512-c/hfooPx+RBIOPM09GSxABOZhYPblDoyaGhqBkD/59vtpN21jEuWKDlM0KYTvqJVlSYjKs0tBcIdeXKChlSPtw==}
hasBin: true
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
- os-filter-obj@2.0.0:
- resolution: {integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==}
- engines: {node: '>=4'}
-
os-utils@0.0.14:
resolution: {integrity: sha512-ajB8csaHLBvJOYsHJkp8YdO2FvlBbf/ZxaYQwXXRDyQ84UoE+uTuLXxqd0shekXMX6Qr/pt/DDyLMRAMsgfWzg==}
ospath@1.2.2:
resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==}
- otpauth@9.3.4:
- resolution: {integrity: sha512-qXv+lpsCUO9ewitLYfeDKbLYt7UUCivnU/fwGK2OqhgrCBsRkTUNKWsgKAhkXG3aistOY+jEeuL90JEBu6W3mQ==}
+ otpauth@9.3.6:
+ resolution: {integrity: sha512-eIcCvuEvcAAPHxUKC9Q4uCe0Fh/yRc5jv9z+f/kvyIF2LPrhgAOuLB7J9CssGYhND/BL8M9hlHBTFmffpoQlMQ==}
outvariant@1.4.3:
resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
- p-cancelable@2.1.1:
- resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
- engines: {node: '>=8'}
-
p-cancelable@3.0.0:
resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
engines: {node: '>=12.20'}
@@ -8771,10 +8758,6 @@ packages:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
- p-limit@5.0.0:
- resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
- engines: {node: '>=18'}
-
p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
@@ -8813,6 +8796,10 @@ 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==}
@@ -8849,10 +8836,6 @@ packages:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
- path-key@2.0.1:
- resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
- engines: {node: '>=4'}
-
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@@ -8868,6 +8851,10 @@ packages:
resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
engines: {node: '>=16 || 14 >=14.17'}
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
path-scurry@2.0.0:
resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
engines: {node: 20 || >=22}
@@ -8875,24 +8862,22 @@ packages:
path-to-regexp@0.1.10:
resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
- 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@0.1.12:
+ resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
+ path-to-regexp@8.2.0:
+ resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
+ engines: {node: '>=16'}
+
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
- pathe@1.1.2:
- resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
-
- pathval@1.1.1:
- resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pathval@2.0.0:
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
@@ -8901,10 +8886,6 @@ packages:
pause-stream@0.0.11:
resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}
- 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'}
@@ -8929,17 +8910,17 @@ packages:
resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
engines: {node: '>=4'}
- pg-pool@3.7.0:
- resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==}
+ pg-pool@3.8.0:
+ resolution: {integrity: sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==}
peerDependencies:
pg: '>=8.0'
- pg-protocol@1.6.1:
- resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==}
-
pg-protocol@1.7.0:
resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==}
+ pg-protocol@1.8.0:
+ resolution: {integrity: sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==}
+
pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
@@ -8948,8 +8929,8 @@ packages:
resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==}
engines: {node: '>=10'}
- pg@8.13.1:
- resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==}
+ pg@8.14.1:
+ resolution: {integrity: sha512-0TdbqfjwIun9Fm/r89oB7RFQ0bLgduAhiIqIXOsyKoiC/L54DbuAAzIEN/9Op0f1Po9X7iCPXGoa/Ah+2aI8Xw==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
@@ -8964,15 +8945,6 @@ packages:
resolution: {integrity: sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==}
engines: {node: '>= 0.12.0'}
- picocolors@1.0.0:
- resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
-
- picocolors@1.0.1:
- resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
-
- picocolors@1.1.0:
- resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
-
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -8984,8 +8956,8 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
- pid-port@1.0.0:
- resolution: {integrity: sha512-LSNBeKChRPA4Xlrs6+zV588G1hSrFvANtPV5rt/5MPfSPK3V9XPWxx1d29svsrOjngT9ifLisXWCLS7DvO9ZhQ==}
+ pid-port@1.0.2:
+ resolution: {integrity: sha512-Khqp07zX8IJpmIg56bHrLxS3M0iSL4cq6wnMq8YE7r/hSw3Kn4QxYS6QJg8Bs22Z7CSVj7eSsxFuigYVIFWmjg==}
engines: {node: '>=18'}
pify@2.3.0:
@@ -9017,9 +8989,6 @@ packages:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
- pkg-types@1.0.3:
- resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
-
plimit-lit@1.5.0:
resolution: {integrity: sha512-Eb/MqCb1Iv/ok4m1FqIXqvUKPISufcjZ605hl3KM/n8GaX8zfhtgdLwZU3vKjuHGh2O9Rjog/bHTq8ofIShdng==}
@@ -9031,6 +9000,11 @@ packages:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
+ pnpm@10.6.1:
+ resolution: {integrity: sha512-QO4Jr0B/qfu1+/uOHLQPu3TArww+EOkiTXtTx2WFKGFbLJJFDnTPrZHjotyv485AUNgL2nHXV3VtLOK2YhPpow==}
+ engines: {node: '>=18.12'}
+ hasBin: true
+
polished@4.2.2:
resolution: {integrity: sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==}
engines: {node: '>=10'}
@@ -9039,185 +9013,185 @@ packages:
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
engines: {node: '>= 0.4'}
- postcss-calc@9.0.1:
- resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-calc@10.1.1:
+ resolution: {integrity: sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==}
+ engines: {node: ^18.12 || ^20.9 || >=22.0}
peerDependencies:
- postcss: ^8.2.2
+ postcss: ^8.4.38
- postcss-colormin@6.1.0:
- resolution: {integrity: sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-colormin@7.0.2:
+ resolution: {integrity: sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-convert-values@6.1.0:
- resolution: {integrity: sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-convert-values@7.0.4:
+ resolution: {integrity: sha512-e2LSXPqEHVW6aoGbjV9RsSSNDO3A0rZLCBxN24zvxF25WknMPpX8Dm9UxxThyEbaytzggRuZxaGXqaOhxQ514Q==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-discard-comments@6.0.2:
- resolution: {integrity: sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-discard-comments@7.0.3:
+ resolution: {integrity: sha512-q6fjd4WU4afNhWOA2WltHgCbkRhZPgQe7cXF74fuVB/ge4QbM9HEaOIzGSiMvM+g/cOsNAUGdf2JDzqA2F8iLA==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-discard-duplicates@6.0.3:
- resolution: {integrity: sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-discard-duplicates@7.0.1:
+ resolution: {integrity: sha512-oZA+v8Jkpu1ct/xbbrntHRsfLGuzoP+cpt0nJe5ED2FQF8n8bJtn7Bo28jSmBYwqgqnqkuSXJfSUEE7if4nClQ==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-discard-empty@6.0.3:
- resolution: {integrity: sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-discard-empty@7.0.0:
+ resolution: {integrity: sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-discard-overridden@6.0.2:
- resolution: {integrity: sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-discard-overridden@7.0.0:
+ resolution: {integrity: sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-merge-longhand@6.0.5:
- resolution: {integrity: sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-merge-longhand@7.0.4:
+ resolution: {integrity: sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-merge-rules@6.1.1:
- resolution: {integrity: sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-merge-rules@7.0.4:
+ resolution: {integrity: sha512-ZsaamiMVu7uBYsIdGtKJ64PkcQt6Pcpep/uO90EpLS3dxJi6OXamIobTYcImyXGoW0Wpugh7DSD3XzxZS9JCPg==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-minify-font-values@6.1.0:
- resolution: {integrity: sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-minify-font-values@7.0.0:
+ resolution: {integrity: sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-minify-gradients@6.0.3:
- resolution: {integrity: sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-minify-gradients@7.0.0:
+ resolution: {integrity: sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-minify-params@6.1.0:
- resolution: {integrity: sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-minify-params@7.0.2:
+ resolution: {integrity: sha512-nyqVLu4MFl9df32zTsdcLqCFfE/z2+f8GE1KHPxWOAmegSo6lpV2GNy5XQvrzwbLmiU7d+fYay4cwto1oNdAaQ==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-minify-selectors@6.0.4:
- resolution: {integrity: sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-minify-selectors@7.0.4:
+ resolution: {integrity: sha512-JG55VADcNb4xFCf75hXkzc1rNeURhlo7ugf6JjiiKRfMsKlDzN9CXHZDyiG6x/zGchpjQS+UAgb1d4nqXqOpmA==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-charset@6.0.2:
- resolution: {integrity: sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-charset@7.0.0:
+ resolution: {integrity: sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-display-values@6.0.2:
- resolution: {integrity: sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-display-values@7.0.0:
+ resolution: {integrity: sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-positions@6.0.2:
- resolution: {integrity: sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-positions@7.0.0:
+ resolution: {integrity: sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-repeat-style@6.0.2:
- resolution: {integrity: sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-repeat-style@7.0.0:
+ resolution: {integrity: sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-string@6.0.2:
- resolution: {integrity: sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-string@7.0.0:
+ resolution: {integrity: sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-timing-functions@6.0.2:
- resolution: {integrity: sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-timing-functions@7.0.0:
+ resolution: {integrity: sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-unicode@6.1.0:
- resolution: {integrity: sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-unicode@7.0.2:
+ resolution: {integrity: sha512-ztisabK5C/+ZWBdYC+Y9JCkp3M9qBv/XFvDtSw0d/XwfT3UaKeW/YTm/MD/QrPNxuecia46vkfEhewjwcYFjkg==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-url@6.0.2:
- resolution: {integrity: sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-url@7.0.0:
+ resolution: {integrity: sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-normalize-whitespace@6.0.2:
- resolution: {integrity: sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-normalize-whitespace@7.0.0:
+ resolution: {integrity: sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-ordered-values@6.0.2:
- resolution: {integrity: sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-ordered-values@7.0.1:
+ resolution: {integrity: sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-reduce-initial@6.1.0:
- resolution: {integrity: sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-reduce-initial@7.0.2:
+ resolution: {integrity: sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-reduce-transforms@6.0.2:
- resolution: {integrity: sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-reduce-transforms@7.0.0:
+ resolution: {integrity: sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
- postcss-selector-parser@6.0.15:
- resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==}
- engines: {node: '>=4'}
-
postcss-selector-parser@6.0.16:
resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==}
engines: {node: '>=4'}
- postcss-svgo@6.0.3:
- resolution: {integrity: sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==}
- engines: {node: ^14 || ^16 || >= 18}
+ postcss-selector-parser@6.1.2:
+ resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+ engines: {node: '>=4'}
+
+ postcss-selector-parser@7.1.0:
+ resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==}
+ engines: {node: '>=4'}
+
+ postcss-svgo@7.0.1:
+ resolution: {integrity: sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >= 18}
peerDependencies:
postcss: ^8.4.31
- postcss-unique-selectors@6.0.4:
- resolution: {integrity: sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==}
- engines: {node: ^14 || ^16 || >=18.0}
+ postcss-unique-selectors@7.0.3:
+ resolution: {integrity: sha512-J+58u5Ic5T1QjP/LDV9g3Cx4CNOgB5vz+kM6+OxHHhFACdcDeKhBXjQmB7fnIZM12YSTvsL0Opwco83DmacW2g==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
- 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==}
+ postcss@8.5.3:
+ resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
postgres-array@2.0.0:
@@ -9269,6 +9243,11 @@ packages:
engines: {node: '>=14'}
hasBin: true
+ prettier@3.5.3:
+ resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
+ engines: {node: '>=14'}
+ hasBin: true
+
pretty-bytes@5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
engines: {node: '>=6'}
@@ -9281,8 +9260,13 @@ packages:
resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- private-ip@2.3.3:
- resolution: {integrity: sha512-5zyFfekIVUOTVbL92hc8LJOtE/gyGHeREHkJ2yTyByP8Q2YZVoBqLg3EfYLeF0oVvGqtaEX2t2Qovja0/gStXw==}
+ pretty-ms@9.2.0:
+ resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
+ engines: {node: '>=18'}
+
+ private-ip@3.0.2:
+ resolution: {integrity: sha512-2pkOVPGYD/4QyAg95c6E/4bLYXPthT5Xw4ocXYzIIsMBhskOMn6IwkWXmg6ZiA6K58+O6VD/n02r1hDhk7vDPw==}
+ engines: {node: '>=14.16'}
probe-image-size@7.2.3:
resolution: {integrity: sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==}
@@ -9325,8 +9309,8 @@ packages:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
- property-information@6.5.0:
- resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
+ property-information@7.0.0:
+ resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
@@ -9350,9 +9334,6 @@ packages:
engines: {node: '>= 0.10'}
hasBin: true
- pseudomap@1.0.2:
- resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
-
psl@1.13.0:
resolution: {integrity: sha512-BFwmFXiJoFqlUpZ5Qssolv15DMyc84gTBds1BjsV1BfXEo1UyyD7GsmN67n7J77uRhoSNW1AXtXKPLcBFQn9Aw==}
@@ -9490,10 +9471,10 @@ packages:
resolution: {integrity: sha512-rCz0HBIT0LWbIM+///LfRrJoTKftIzzwsYDf0ns5KwaEjejMHQRtphcns+IXFHDNY9pnz6G8l/JbbI6pD4EAIA==}
engines: {node: '>=16.14.0'}
- react-dom@18.3.1:
- resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ react-dom@19.0.0:
+ resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
peerDependencies:
- react: ^18.3.1
+ react: ^19.0.0
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
@@ -9501,8 +9482,8 @@ packages:
react-is@18.2.0:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
- react@18.3.1:
- resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ react@19.0.0:
+ resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'}
read-pkg-up@7.0.1:
@@ -9524,16 +9505,12 @@ packages:
resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- readable-web-to-node-stream@3.0.2:
- resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==}
- engines: {node: '>=8'}
-
readdir-glob@1.1.2:
resolution: {integrity: sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==}
- readdirp@3.6.0:
- resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
- engines: {node: '>=8.10.0'}
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
@@ -9554,9 +9531,6 @@ packages:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
- redis-info@3.1.0:
- resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==}
-
redis-lock@0.1.4:
resolution: {integrity: sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA==}
engines: {node: '>=0.6'}
@@ -9571,8 +9545,14 @@ packages:
regenerator-runtime@0.14.0:
resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
- regex@4.4.0:
- resolution: {integrity: sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==}
+ regex-recursion@6.0.2:
+ resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
+
+ regex-utilities@2.3.0:
+ resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
+
+ regex@6.0.1:
+ resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
regexp.prototype.flags@1.5.0:
resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==}
@@ -9641,9 +9621,6 @@ packages:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
- responselike@2.0.1:
- resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
-
responselike@3.0.0:
resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
engines: {node: '>=14.16'}
@@ -9672,16 +9649,17 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
- rollup@4.26.0:
- resolution: {integrity: sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==}
- engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ rimraf@5.0.10:
+ resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
hasBin: true
- rrweb-cssom@0.6.0:
- resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
+ rollup@4.36.0:
+ resolution: {integrity: sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
- rrweb-cssom@0.7.1:
- resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
+ rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
rss-parser@3.13.0:
resolution: {integrity: sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==}
@@ -9689,12 +9667,8 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
- rxjs@7.8.1:
- resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
-
- safe-array-concat@1.0.0:
- resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==}
- engines: {node: '>=0.4'}
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
safe-array-concat@1.1.2:
resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==}
@@ -9706,9 +9680,6 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
- safe-regex-test@1.0.0:
- resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
-
safe-regex-test@1.0.3:
resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
engines: {node: '>= 0.4'}
@@ -9723,16 +9694,11 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
- 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
+ sanitize-html@2.15.0:
+ resolution: {integrity: sha512-wIjst57vJGpLyBP8ioUbg6ThwJie5SuSIjHxJg53v5Fg+kUK+AXlb7bK3RNXpp315MvwM+0OBGCV6h5pPHsVhA==}
- sass@1.79.4:
- resolution: {integrity: sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==}
+ sass@1.86.0:
+ resolution: {integrity: sha512-zV8vGUld/+mP4KbMLJMX7TyGCuUp7hnkOScgCMsWuHtns8CWBoz+vmEhoGMXsaJrbUP8gj+F1dLvVe79sK8UdA==}
engines: {node: '>=14.0.0'}
hasBin: true
@@ -9743,25 +9709,29 @@ packages:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
- scheduler@0.23.2:
- resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+ scheduler@0.25.0:
+ resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
secure-json-parse@2.7.0:
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
- secure-json-parse@3.0.0:
- resolution: {integrity: sha512-YO+gVWyp97H+nCG/qdC8X819iKx5g+BpnO9nYT4uFq4uyI0rSxwtx5qD9rGfScg7FGLYu/YBf8uOtwQKv+gq8g==}
+ secure-json-parse@3.0.2:
+ resolution: {integrity: sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==}
seedrandom@3.0.5:
resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
+ seek-bzip@2.0.0:
+ resolution: {integrity: sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==}
+ hasBin: true
+
semver-regex@4.0.5:
resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==}
engines: {node: '>=12'}
- semver-truncate@2.0.0:
- resolution: {integrity: sha512-Rh266MLDYNeML5h90ttdMwfXe1+Nc4LAWd9X1KdJe8pPHP4kFmvLZALtsMNHNdvTyQygbEC0D59sIz47DIaq8w==}
- engines: {node: '>=8'}
+ semver-truncate@3.0.0:
+ resolution: {integrity: sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==}
+ engines: {node: '>=12'}
semver@5.7.1:
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
@@ -9822,18 +9792,10 @@ packages:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- shebang-command@1.2.0:
- resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
- engines: {node: '>=0.10.0'}
-
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
- shebang-regex@1.0.0:
- resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
- engines: {node: '>=0.10.0'}
-
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
@@ -9841,8 +9803,8 @@ packages:
shiki@0.14.7:
resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==}
- shiki@1.22.2:
- resolution: {integrity: sha512-3IZau0NdGKXhH2bBlUk4w1IHNxPh6A5B2sUpyY+8utLu2j/h1QpFkAaUA1bAMxOWWGtTWcAh531vnS4NJKS/lA==}
+ shiki@3.2.1:
+ resolution: {integrity: sha512-VML/2o1/KGYkEf/stJJ+s9Ypn7jUKQPomGLGYso4JJFMFxVDyPNsjsI3MB3KLjlMOeH44gyaPdXC6rik2WXvUQ==}
shimmer@1.2.1:
resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==}
@@ -9880,8 +9842,8 @@ packages:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
- sinon@16.1.3:
- resolution: {integrity: sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==}
+ sinon@18.0.1:
+ resolution: {integrity: sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==}
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
@@ -10008,10 +9970,6 @@ packages:
sortablejs@1.14.0:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
- source-map-js@1.2.0:
- resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
- engines: {node: '>=0.10.0'}
-
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -10058,6 +10016,10 @@ packages:
sprintf-js@1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
+ sql-highlight@6.0.0:
+ resolution: {integrity: sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==}
+ engines: {node: '>=14'}
+
sshpk@1.17.0:
resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==}
engines: {node: '>=0.10.0'}
@@ -10082,8 +10044,13 @@ packages:
standard-as-callback@2.1.0:
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
- start-server-and-test@2.0.8:
- resolution: {integrity: sha512-v2fV6NV2F7tL1ocwfI4Wpait+IKjRbT5l3ZZ+ZikXdMLmxYsS8ynGAsCQAUVXkVyGyS+UibsRnvgHkMvJIvCsw==}
+ start-server-and-test@2.0.10:
+ resolution: {integrity: sha512-nZphcfcqGqwk74lbZkqSwClkYz+M5ZPGOMgWxNVJrdztPKN96qe6HooRu6L3TpwITn0lKJJdKACqHbJtqythOQ==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ start-server-and-test@2.0.11:
+ resolution: {integrity: sha512-TN39gLzPhHAflxyOkE/oMfQGj+pj3JgF6qVicFH/JrXt7xXktidKXwqfRga+ve7lVA8+RgPZVc25VrEPRScaDw==}
engines: {node: '>=16'}
hasBin: true
@@ -10091,8 +10058,8 @@ packages:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
- std-env@3.7.0:
- resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
+ std-env@3.8.1:
+ resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==}
stop-iteration-iterator@1.0.0:
resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==}
@@ -10117,8 +10084,8 @@ packages:
react-dom:
optional: true
- storybook@8.4.4:
- resolution: {integrity: sha512-xBOq3q/MuUUg3zM0imMMaK5ziKq3TO388jsnaiemJ4Uf0ZGwcHjM8HDBCDt0s5/CfsOQ49zo1ouZ3aNlu0qsUg==}
+ storybook@8.6.7:
+ resolution: {integrity: sha512-9gktoFMQDSCINNGQH869d/sar9rVtAhr0HchcvDA6bssAqgQJvTphY4qC9lH54SxfTJm/7Sy+BKEngMK+dziJg==}
hasBin: true
peerDependencies:
prettier: ^2 || ^3
@@ -10164,23 +10131,13 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
- string.prototype.trim@1.2.7:
- resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==}
- engines: {node: '>= 0.4'}
-
string.prototype.trim@1.2.9:
resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==}
engines: {node: '>= 0.4'}
- string.prototype.trimend@1.0.6:
- resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==}
-
string.prototype.trimend@1.0.8:
resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==}
- string.prototype.trimstart@1.0.6:
- resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==}
-
string.prototype.trimstart@1.0.8:
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
engines: {node: '>= 0.4'}
@@ -10213,9 +10170,8 @@ packages:
resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
engines: {node: '>=8'}
- strip-eof@1.0.0:
- resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==}
- engines: {node: '>=0.10.0'}
+ strip-dirs@3.0.0:
+ resolution: {integrity: sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==}
strip-final-newline@2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
@@ -10225,6 +10181,10 @@ 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'}
@@ -10241,27 +10201,16 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
- strip-literal@2.1.0:
- resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==}
-
- strip-outer@2.0.0:
- resolution: {integrity: sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
- strtok3@7.0.0:
- resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==}
- engines: {node: '>=14.16'}
-
strtok3@9.0.1:
resolution: {integrity: sha512-ERPW+XkvX9W2A+ov07iy+ZFJpVdik04GhDA4eVogiG9hpC97Kem2iucyzhFxbFRvQ5o2UckFtKZdp1hkGvnrEw==}
engines: {node: '>=16'}
- stylehacks@6.1.1:
- resolution: {integrity: sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==}
- engines: {node: ^14 || ^16 || >=18.0}
+ stylehacks@7.0.4:
+ resolution: {integrity: sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==}
+ engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
peerDependencies:
postcss: ^8.4.31
@@ -10289,16 +10238,16 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
- svgo@3.2.0:
- resolution: {integrity: sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==}
+ svgo@3.3.2:
+ resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
engines: {node: '>=14.0.0'}
hasBin: true
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
- systeminformation@5.23.5:
- resolution: {integrity: sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==}
+ systeminformation@5.25.11:
+ resolution: {integrity: sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==}
engines: {node: '>=8.0.0'}
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
hasBin: true
@@ -10313,16 +10262,23 @@ packages:
tar-stream@3.1.6:
resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==}
+ tar-stream@3.1.7:
+ resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
+
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
+ tar@7.4.3:
+ resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
+ engines: {node: '>=18'}
+
taskkill@5.0.0:
resolution: {integrity: sha512-+HRtZ40Vc+6YfCDWCeAsixwxJgMbPY4HHuTgzPYH3JXvqHWUlsCfy+ylXlAKhFNcuLp4xVeWeFBUhDk+7KYUvQ==}
engines: {node: '>=14.16'}
- terser@5.36.0:
- resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==}
+ terser@5.39.0:
+ resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==}
engines: {node: '>=10'}
hasBin: true
@@ -10330,6 +10286,10 @@ packages:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
engines: {node: '>=8'}
+ test-exclude@7.0.1:
+ resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
+ engines: {node: '>=18'}
+
text-decoding@1.0.0:
resolution: {integrity: sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==}
@@ -10349,8 +10309,8 @@ packages:
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
- three@0.169.0:
- resolution: {integrity: sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w==}
+ three@0.174.0:
+ resolution: {integrity: sha512-p+WG3W6Ov74alh3geCMkGK9NWuT62ee21cV3jEnun201zodVF4tCE5aZa2U122/mkLRmhJJUQmLLW1BH00uQJQ==}
throttle-debounce@5.0.2:
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
@@ -10365,22 +10325,25 @@ packages:
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
- tinybench@2.6.0:
- resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==}
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinycolor2@1.6.0:
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
- tinypool@0.8.4:
- resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
- engines: {node: '>=14.0.0'}
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinypool@1.0.2:
+ resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
tinyrainbow@1.2.0:
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
engines: {node: '>=14.0.0'}
- tinyspy@2.2.0:
- resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==}
+ tinyrainbow@2.0.0:
+ resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
engines: {node: '>=14.0.0'}
tinyspy@3.0.2:
@@ -10423,10 +10386,6 @@ packages:
token-stream@1.0.0:
resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==}
- token-types@5.0.1:
- resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==}
- engines: {node: '>=14.16'}
-
token-types@6.0.0:
resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==}
engines: {node: '>=14.16'}
@@ -10446,13 +10405,10 @@ packages:
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
- tr46@5.0.0:
- resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
+ tr46@5.1.0:
+ resolution: {integrity: sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==}
engines: {node: '>=18'}
- 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
@@ -10464,10 +10420,6 @@ packages:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
engines: {node: '>=8'}
- trim-repeated@2.0.0:
- resolution: {integrity: sha512-QUHBFTJGdOwmp0tbOG505xAgOp/YliZP/6UgafFXYZ26WT1bvQmSMJUvkeVSASuJJHbqsFbynTvkd5W8RBTipg==}
- engines: {node: '>=12'}
-
trough@2.2.0:
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
@@ -10477,6 +10429,12 @@ packages:
peerDependencies:
typescript: '>=4.2.0'
+ ts-api-utils@2.1.0:
+ resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
ts-case-convert@2.1.0:
resolution: {integrity: sha512-Ye79el/pHYXfoew6kqhMwCoxp4NWjKNcm2kBzpmEMIU9dd9aBmHNNFtZ+WTm0rz1ngyDmfqDXDlyUnBXayiD0w==}
@@ -10508,8 +10466,8 @@ packages:
ts-map@1.0.3:
resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==}
- tsc-alias@1.8.10:
- resolution: {integrity: sha512-Ibv4KAWfFkFdKJxnWfVtdOmB0Zi1RJVxcbPGiCDsFpCQSsmpWyuzHG3rQyI5YkobWwxFPEyQfu1hdo4qLG2zPw==}
+ tsc-alias@1.8.11:
+ resolution: {integrity: sha512-2DuEQ58A9Rj2NE2c1+/qaGKlshni9MCK95MJzRGhQG0CYLw0bE/ACgbhhTSf/p1svLelwqafOd8stQate2bYbg==}
hasBin: true
tsconfig-paths@3.15.0:
@@ -10527,14 +10485,14 @@ packages:
tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
- tslib@2.6.3:
- resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
-
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
- tsx@4.4.0:
- resolution: {integrity: sha512-4fwcEjRUxW20ciSaMB8zkpGwCPxuRGnadDuj/pBk5S9uT29zvWz15PK36GrKJo45mSJomDxVejZ73c6lr3811Q==}
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsx@4.19.3:
+ resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==}
engines: {node: '>=18.0.0'}
hasBin: true
@@ -10584,33 +10542,18 @@ packages:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
- typed-array-buffer@1.0.0:
- resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
- engines: {node: '>= 0.4'}
-
typed-array-buffer@1.0.2:
resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==}
engines: {node: '>= 0.4'}
- typed-array-byte-length@1.0.0:
- resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==}
- engines: {node: '>= 0.4'}
-
typed-array-byte-length@1.0.1:
resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==}
engines: {node: '>= 0.4'}
- typed-array-byte-offset@1.0.0:
- resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==}
- engines: {node: '>= 0.4'}
-
typed-array-byte-offset@1.0.2:
resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==}
engines: {node: '>= 0.4'}
- typed-array-length@1.0.4:
- resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
-
typed-array-length@1.0.6:
resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==}
engines: {node: '>= 0.4'}
@@ -10625,24 +10568,25 @@ packages:
peerDependencies:
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x
- typeorm@0.3.20:
- resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==}
+ typeorm@0.3.21:
+ resolution: {integrity: sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==}
engines: {node: '>=16.13.0'}
hasBin: true
peerDependencies:
'@google-cloud/spanner': ^5.18.0
'@sap/hana-client': ^2.12.25
- better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0
+ better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
hdb-pool: ^0.1.6
ioredis: ^5.0.4
mongodb: ^5.8.0
- mssql: ^9.1.1 || ^10.0.1
+ mssql: ^9.1.1 || ^10.0.1 || ^11.0.1
mysql2: ^2.2.5 || ^3.0.1
oracledb: ^6.3.0
pg: ^8.5.1
pg-native: ^3.0.0
pg-query-stream: ^4.0.0
redis: ^3.1.1 || ^4.0.0
+ reflect-metadata: ^0.1.14 || ^0.2.0
sql.js: ^1.4.0
sqlite3: ^5.0.3
ts-node: ^10.7.0
@@ -10688,19 +10632,16 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- typescript@5.4.2:
- resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
+ typescript@5.7.3:
+ resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
engines: {node: '>=14.17'}
hasBin: true
- typescript@5.6.3:
- resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+ typescript@5.8.2:
+ resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
engines: {node: '>=14.17'}
hasBin: true
- ufo@1.3.2:
- resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==}
-
uid2@0.0.4:
resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==}
@@ -10712,27 +10653,38 @@ packages:
resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==}
engines: {node: '>=18'}
- ulid@2.3.0:
- resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==}
+ ulid@2.4.0:
+ resolution: {integrity: sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==}
hasBin: true
unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+ unbzip2-stream@1.4.3:
+ resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==}
+
undefsafe@2.0.5:
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
- undici-types@6.19.8:
- resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+ undici-types@6.20.0:
+ resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
undici@5.28.2:
resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==}
engines: {node: '>=14.0'}
+ undici@5.29.0:
+ resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
+ engines: {node: '>=14.0'}
+
undici@6.20.0:
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==}
@@ -10791,18 +10743,20 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
+ update-browserslist-db@1.1.3:
+ resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
- utf-8-validate@6.0.3:
- resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==}
- engines: {node: '>=6.14.2'}
-
- utf-8-validate@6.0.4:
- resolution: {integrity: sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==}
+ utf-8-validate@6.0.5:
+ resolution: {integrity: sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==}
engines: {node: '>=6.14.2'}
util-deprecate@1.0.2:
@@ -10815,8 +10769,8 @@ packages:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
- uuid@10.0.0:
- resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
+ uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true
uuid@8.3.2:
@@ -10861,30 +10815,35 @@ packages:
vfile@6.0.1:
resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
- vite-node@1.6.0:
- resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ vite-node@3.0.9:
+ resolution: {integrity: sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
vite-plugin-turbosnap@1.0.3:
resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==}
- vite@5.4.11:
- resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ vite@6.2.2:
+ resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
- '@types/node': ^18.0.0 || >=20.0.0
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
- terser: ^5.4.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
+ jiti:
+ optional: true
less:
optional: true
lightningcss:
@@ -10899,27 +10858,34 @@ packages:
optional: true
terser:
optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
- vitest-fetch-mock@0.2.2:
- resolution: {integrity: sha512-XmH6QgTSjCWrqXoPREIdbj40T7i1xnGmAsTAgfckoO75W1IEHKR8hcPCQ7SO16RsdW1t85oUm6pcQRLeBgjVYQ==}
- engines: {node: '>=14.14.0'}
+ vitest-fetch-mock@0.4.5:
+ resolution: {integrity: sha512-nhWdCQIGtaSEUVl96pMm0WggyDGPDv5FUy/Q9Hx3cs2RGmh3Q/uRsLClGbdG3kXBkJ3br5yTUjB2MeW25TwdOA==}
+ engines: {node: '>=18.0.0'}
peerDependencies:
- vitest: '>=0.16.0'
+ vitest: '>=2.0.0'
- vitest@1.6.0:
- resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==}
- engines: {node: ^18.0.0 || >=20.0.0}
+ vitest@3.0.9:
+ resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
- '@types/node': ^18.0.0 || >=20.0.0
- '@vitest/browser': 1.6.0
- '@vitest/ui': 1.6.0
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.0.9
+ '@vitest/ui': 3.0.9
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
+ '@types/debug':
+ optional: true
'@types/node':
optional: true
'@vitest/browser':
@@ -10979,9 +10945,6 @@ packages:
vue-component-type-helpers@2.0.16:
resolution: {integrity: sha512-qisL/iAfdO++7w+SsfYQJVPj6QKvxp4i1MMxvsNO41z/8zu3KuAw9LkhKUfP/kcOWGDxESp+pQObWppXusejCA==}
- vue-component-type-helpers@2.1.10:
- resolution: {integrity: sha512-lfgdSLQKrUmADiSV6PbBvYgQ33KF3Ztv6gP85MfGaGaSGMTXORVaHT1EHfsqCgzRNBstPKYDmvAV9Do5CmJ07A==}
-
vue-component-type-helpers@2.2.8:
resolution: {integrity: sha512-4bjIsC284coDO9om4HPA62M7wfsTvcmZyzdfR0aUlFXqq4tXxM1APyXpNVxPC8QazKw9OhmZNHBVDA6ODaZsrA==}
@@ -11001,11 +10964,11 @@ packages:
peerDependencies:
vue: '>=2'
- vue-eslint-parser@9.4.3:
- resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
- engines: {node: ^14.17.0 || >=16.0.0}
+ vue-eslint-parser@10.1.1:
+ resolution: {integrity: sha512-bh2Z/Au5slro9QJ3neFYLanZtb1jH+W2bKqGHXAoYD4vZgNG3KeotL7JpPv5xzY4UXUXJl7TrIsnzECH63kd3Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: '>=6.0.0'
+ eslint: ^8.57.0 || ^9.0.0
vue-inbrowser-compiler-independent-utils@4.71.1:
resolution: {integrity: sha512-K3wt3iVmNGaFEOUR4JIThQRWfqokxLfnPslD41FDZB2ajXp789+wCqJyGYlIFsvEQ2P61PInw6/ph5iiqg51gg==}
@@ -11015,14 +10978,14 @@ packages:
vue-template-compiler@2.7.14:
resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==}
- vue-tsc@2.1.10:
- resolution: {integrity: sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==}
+ vue-tsc@2.2.8:
+ resolution: {integrity: sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ==}
hasBin: true
peerDependencies:
typescript: '>=5.0.0'
- vue@3.5.12:
- resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==}
+ vue@3.5.13:
+ resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
@@ -11038,14 +11001,23 @@ packages:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
- wait-on@8.0.1:
- resolution: {integrity: sha512-1wWQOyR2LVVtaqrcIL2+OM+x7bkpmzVROa0Nf6FryXkS+er5Sa1kzFGjzZRqLnHa3n1rACFLeTwUqE1ETL9Mig==}
+ wait-on@8.0.2:
+ resolution: {integrity: sha512-qHlU6AawrgAIHlueGQHQ+ETcPLAauXbnoTKl3RKq20W0T8x0DKVAo5xWIYjHSyvHxQlcYbFdR0jp4T9bDVITFA==}
+ engines: {node: '>=12.0.0'}
+ hasBin: true
+
+ wait-on@8.0.3:
+ resolution: {integrity: sha512-nQFqAFzZDeRxsu7S3C7LbuxslHhk+gnJZHyethuGKAn2IVleIbTB9I3vJSQiSR+DifUqmdzfPMoMPJfLqMF2vw==}
engines: {node: '>=12.0.0'}
hasBin: true
walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
+ wanakana@5.3.1:
+ resolution: {integrity: sha512-OSDqupzTlzl2LGyqTdhcXcl6ezMiFhcUwLBP8YKaBIbMYW1wAwDvupw2T9G9oVaKT9RmaSpyTXjxddFPUcFFIw==}
+ engines: {node: '>=12'}
+
web-push@3.6.7:
resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==}
engines: {node: '>= 16'}
@@ -11077,10 +11049,6 @@ packages:
webpack-virtual-modules@0.5.0:
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
- whatwg-encoding@2.0.0:
- resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
- engines: {node: '>=12'}
-
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
@@ -11093,8 +11061,8 @@ packages:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
- whatwg-url@14.0.0:
- resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==}
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
engines: {node: '>=18'}
whatwg-url@5.0.0:
@@ -11131,8 +11099,8 @@ packages:
engines: {node: ^16.13.0 || >=18.0.0}
hasBin: true
- why-is-node-running@2.2.2:
- resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
@@ -11175,8 +11143,8 @@ packages:
utf-8-validate:
optional: true
- ws@8.18.0:
- resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
+ ws@8.18.1:
+ resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@@ -11224,15 +11192,16 @@ packages:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
- yallist@2.1.2:
- resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==}
-
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ yallist@5.0.0:
+ resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+ engines: {node: '>=18'}
+
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
@@ -11260,18 +11229,22 @@ packages:
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
+ yauzl@3.2.0:
+ resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==}
+ engines: {node: '>=12'}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- yocto-queue@1.0.0:
- resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
- engines: {node: '>=12.20'}
-
yoctocolors-cjs@2.1.2:
resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==}
engines: {node: '>=18'}
+ yoctocolors@2.1.1:
+ resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
+ engines: {node: '>=18'}
+
zip-stream@6.0.1:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'}
@@ -11296,25 +11269,74 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
+ '@ampproject/remapping@2.3.0':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@analytics/cookie-utils@0.2.12':
+ dependencies:
+ '@analytics/global-storage-utils': 0.1.7
+
+ '@analytics/core@0.12.17(@types/dlv@1.1.5)':
+ dependencies:
+ '@analytics/global-storage-utils': 0.1.7
+ '@analytics/type-utils': 0.6.2
+ analytics-utils: 1.0.14(@types/dlv@1.1.5)
+ transitivePeerDependencies:
+ - '@types/dlv'
+
+ '@analytics/global-storage-utils@0.1.7':
+ dependencies:
+ '@analytics/type-utils': 0.6.2
+
+ '@analytics/google-analytics@1.1.0': {}
+
+ '@analytics/localstorage-utils@0.1.10':
+ dependencies:
+ '@analytics/global-storage-utils': 0.1.7
+
+ '@analytics/session-storage-utils@0.0.7':
+ dependencies:
+ '@analytics/global-storage-utils': 0.1.7
+
+ '@analytics/storage-utils@0.4.2':
+ dependencies:
+ '@analytics/cookie-utils': 0.2.12
+ '@analytics/global-storage-utils': 0.1.7
+ '@analytics/localstorage-utils': 0.1.10
+ '@analytics/session-storage-utils': 0.0.7
+ '@analytics/type-utils': 0.6.2
+
+ '@analytics/type-utils@0.6.2': {}
+
'@apidevtools/swagger-methods@3.0.2': {}
+ '@asamuzakjp/css-color@3.1.1':
+ dependencies:
+ '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
+ lru-cache: 10.4.3
+
'@aws-crypto/crc32@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
- '@aws-sdk/types': 3.609.0
+ '@aws-sdk/types': 3.734.0
tslib: 2.7.0
'@aws-crypto/crc32c@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
- '@aws-sdk/types': 3.609.0
+ '@aws-sdk/types': 3.734.0
tslib: 2.7.0
'@aws-crypto/sha1-browser@5.2.0':
dependencies:
'@aws-crypto/supports-web-crypto': 5.2.0
'@aws-crypto/util': 5.2.0
- '@aws-sdk/types': 3.609.0
+ '@aws-sdk/types': 3.734.0
'@aws-sdk/util-locate-window': 3.208.0
'@smithy/util-utf8': 2.0.0
tslib: 2.7.0
@@ -11324,7 +11346,7 @@ snapshots:
'@aws-crypto/sha256-js': 5.2.0
'@aws-crypto/supports-web-crypto': 5.2.0
'@aws-crypto/util': 5.2.0
- '@aws-sdk/types': 3.609.0
+ '@aws-sdk/types': 3.734.0
'@aws-sdk/util-locate-window': 3.208.0
'@smithy/util-utf8': 2.0.0
tslib: 2.7.0
@@ -11332,7 +11354,7 @@ snapshots:
'@aws-crypto/sha256-js@5.2.0':
dependencies:
'@aws-crypto/util': 5.2.0
- '@aws-sdk/types': 3.609.0
+ '@aws-sdk/types': 3.734.0
tslib: 2.7.0
'@aws-crypto/supports-web-crypto@5.2.0':
@@ -11341,468 +11363,430 @@ snapshots:
'@aws-crypto/util@5.2.0':
dependencies:
- '@aws-sdk/types': 3.609.0
+ '@aws-sdk/types': 3.734.0
'@smithy/util-utf8': 2.0.0
tslib: 2.7.0
- '@aws-sdk/client-s3@3.620.0':
+ '@aws-sdk/client-s3@3.772.0':
dependencies:
'@aws-crypto/sha1-browser': 5.2.0
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
- '@aws-sdk/client-sso-oidc': 3.620.0(@aws-sdk/client-sts@3.620.0)
- '@aws-sdk/client-sts': 3.620.0
- '@aws-sdk/core': 3.620.0
- '@aws-sdk/credential-provider-node': 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)
- '@aws-sdk/middleware-bucket-endpoint': 3.620.0
- '@aws-sdk/middleware-expect-continue': 3.620.0
- '@aws-sdk/middleware-flexible-checksums': 3.620.0
- '@aws-sdk/middleware-host-header': 3.620.0
- '@aws-sdk/middleware-location-constraint': 3.609.0
- '@aws-sdk/middleware-logger': 3.609.0
- '@aws-sdk/middleware-recursion-detection': 3.620.0
- '@aws-sdk/middleware-sdk-s3': 3.620.0
- '@aws-sdk/middleware-signing': 3.620.0
- '@aws-sdk/middleware-ssec': 3.609.0
- '@aws-sdk/middleware-user-agent': 3.620.0
- '@aws-sdk/region-config-resolver': 3.614.0
- '@aws-sdk/signature-v4-multi-region': 3.620.0
- '@aws-sdk/types': 3.609.0
- '@aws-sdk/util-endpoints': 3.614.0
- '@aws-sdk/util-user-agent-browser': 3.609.0
- '@aws-sdk/util-user-agent-node': 3.614.0
- '@aws-sdk/xml-builder': 3.609.0
- '@smithy/config-resolver': 3.0.5
- '@smithy/core': 2.3.1
- '@smithy/eventstream-serde-browser': 3.0.5
- '@smithy/eventstream-serde-config-resolver': 3.0.3
- '@smithy/eventstream-serde-node': 3.0.4
- '@smithy/fetch-http-handler': 3.2.4
- '@smithy/hash-blob-browser': 3.1.2
- '@smithy/hash-node': 3.0.3
- '@smithy/hash-stream-node': 3.1.2
- '@smithy/invalid-dependency': 3.0.3
- '@smithy/md5-js': 3.0.3
- '@smithy/middleware-content-length': 3.0.5
- '@smithy/middleware-endpoint': 3.1.0
- '@smithy/middleware-retry': 3.0.13
- '@smithy/middleware-serde': 3.0.3
- '@smithy/middleware-stack': 3.0.3
- '@smithy/node-config-provider': 3.1.4
- '@smithy/node-http-handler': 3.1.4
- '@smithy/protocol-http': 4.1.0
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- '@smithy/url-parser': 3.0.3
- '@smithy/util-base64': 3.0.0
- '@smithy/util-body-length-browser': 3.0.0
- '@smithy/util-body-length-node': 3.0.0
- '@smithy/util-defaults-mode-browser': 3.0.13
- '@smithy/util-defaults-mode-node': 3.0.13
- '@smithy/util-endpoints': 2.0.5
- '@smithy/util-retry': 3.0.3
- '@smithy/util-stream': 3.1.3
- '@smithy/util-utf8': 3.0.0
- '@smithy/util-waiter': 3.1.2
- tslib: 2.6.3
- transitivePeerDependencies:
- - aws-crt
-
- '@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0)':
- dependencies:
- '@aws-crypto/sha256-browser': 5.2.0
- '@aws-crypto/sha256-js': 5.2.0
- '@aws-sdk/client-sts': 3.620.0
- '@aws-sdk/core': 3.620.0
- '@aws-sdk/credential-provider-node': 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)
- '@aws-sdk/middleware-host-header': 3.620.0
- '@aws-sdk/middleware-logger': 3.609.0
- '@aws-sdk/middleware-recursion-detection': 3.620.0
- '@aws-sdk/middleware-user-agent': 3.620.0
- '@aws-sdk/region-config-resolver': 3.614.0
- '@aws-sdk/types': 3.609.0
- '@aws-sdk/util-endpoints': 3.614.0
- '@aws-sdk/util-user-agent-browser': 3.609.0
- '@aws-sdk/util-user-agent-node': 3.614.0
- '@smithy/config-resolver': 3.0.5
- '@smithy/core': 2.3.1
- '@smithy/fetch-http-handler': 3.2.4
- '@smithy/hash-node': 3.0.3
- '@smithy/invalid-dependency': 3.0.3
- '@smithy/middleware-content-length': 3.0.5
- '@smithy/middleware-endpoint': 3.1.0
- '@smithy/middleware-retry': 3.0.13
- '@smithy/middleware-serde': 3.0.3
- '@smithy/middleware-stack': 3.0.3
- '@smithy/node-config-provider': 3.1.4
- '@smithy/node-http-handler': 3.1.4
- '@smithy/protocol-http': 4.1.0
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- '@smithy/url-parser': 3.0.3
- '@smithy/util-base64': 3.0.0
- '@smithy/util-body-length-browser': 3.0.0
- '@smithy/util-body-length-node': 3.0.0
- '@smithy/util-defaults-mode-browser': 3.0.13
- '@smithy/util-defaults-mode-node': 3.0.13
- '@smithy/util-endpoints': 2.0.5
- '@smithy/util-middleware': 3.0.3
- '@smithy/util-retry': 3.0.3
- '@smithy/util-utf8': 3.0.0
- tslib: 2.7.0
- transitivePeerDependencies:
- - aws-crt
-
- '@aws-sdk/client-sso@3.620.0':
- dependencies:
- '@aws-crypto/sha256-browser': 5.2.0
- '@aws-crypto/sha256-js': 5.2.0
- '@aws-sdk/core': 3.620.0
- '@aws-sdk/middleware-host-header': 3.620.0
- '@aws-sdk/middleware-logger': 3.609.0
- '@aws-sdk/middleware-recursion-detection': 3.620.0
- '@aws-sdk/middleware-user-agent': 3.620.0
- '@aws-sdk/region-config-resolver': 3.614.0
- '@aws-sdk/types': 3.609.0
- '@aws-sdk/util-endpoints': 3.614.0
- '@aws-sdk/util-user-agent-browser': 3.609.0
- '@aws-sdk/util-user-agent-node': 3.614.0
- '@smithy/config-resolver': 3.0.5
- '@smithy/core': 2.3.1
- '@smithy/fetch-http-handler': 3.2.4
- '@smithy/hash-node': 3.0.3
- '@smithy/invalid-dependency': 3.0.3
- '@smithy/middleware-content-length': 3.0.5
- '@smithy/middleware-endpoint': 3.1.0
- '@smithy/middleware-retry': 3.0.13
- '@smithy/middleware-serde': 3.0.3
- '@smithy/middleware-stack': 3.0.3
- '@smithy/node-config-provider': 3.1.4
- '@smithy/node-http-handler': 3.1.4
- '@smithy/protocol-http': 4.1.0
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- '@smithy/url-parser': 3.0.3
- '@smithy/util-base64': 3.0.0
- '@smithy/util-body-length-browser': 3.0.0
- '@smithy/util-body-length-node': 3.0.0
- '@smithy/util-defaults-mode-browser': 3.0.13
- '@smithy/util-defaults-mode-node': 3.0.13
- '@smithy/util-endpoints': 2.0.5
- '@smithy/util-middleware': 3.0.3
- '@smithy/util-retry': 3.0.3
- '@smithy/util-utf8': 3.0.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/credential-provider-node': 3.772.0
+ '@aws-sdk/middleware-bucket-endpoint': 3.734.0
+ '@aws-sdk/middleware-expect-continue': 3.734.0
+ '@aws-sdk/middleware-flexible-checksums': 3.758.0
+ '@aws-sdk/middleware-host-header': 3.734.0
+ '@aws-sdk/middleware-location-constraint': 3.734.0
+ '@aws-sdk/middleware-logger': 3.734.0
+ '@aws-sdk/middleware-recursion-detection': 3.772.0
+ '@aws-sdk/middleware-sdk-s3': 3.758.0
+ '@aws-sdk/middleware-ssec': 3.734.0
+ '@aws-sdk/middleware-user-agent': 3.758.0
+ '@aws-sdk/region-config-resolver': 3.734.0
+ '@aws-sdk/signature-v4-multi-region': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@aws-sdk/util-endpoints': 3.743.0
+ '@aws-sdk/util-user-agent-browser': 3.734.0
+ '@aws-sdk/util-user-agent-node': 3.758.0
+ '@aws-sdk/xml-builder': 3.734.0
+ '@smithy/config-resolver': 4.1.0
+ '@smithy/core': 3.2.0
+ '@smithy/eventstream-serde-browser': 4.0.2
+ '@smithy/eventstream-serde-config-resolver': 4.1.0
+ '@smithy/eventstream-serde-node': 4.0.2
+ '@smithy/fetch-http-handler': 5.0.2
+ '@smithy/hash-blob-browser': 4.0.2
+ '@smithy/hash-node': 4.0.2
+ '@smithy/hash-stream-node': 4.0.2
+ '@smithy/invalid-dependency': 4.0.2
+ '@smithy/md5-js': 4.0.2
+ '@smithy/middleware-content-length': 4.0.2
+ '@smithy/middleware-endpoint': 4.1.0
+ '@smithy/middleware-retry': 4.1.0
+ '@smithy/middleware-serde': 4.0.3
+ '@smithy/middleware-stack': 4.0.2
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/node-http-handler': 4.0.4
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
+ '@smithy/url-parser': 4.0.2
+ '@smithy/util-base64': 4.0.0
+ '@smithy/util-body-length-browser': 4.0.0
+ '@smithy/util-body-length-node': 4.0.0
+ '@smithy/util-defaults-mode-browser': 4.0.8
+ '@smithy/util-defaults-mode-node': 4.0.8
+ '@smithy/util-endpoints': 3.0.2
+ '@smithy/util-middleware': 4.0.2
+ '@smithy/util-retry': 4.0.2
+ '@smithy/util-stream': 4.2.0
+ '@smithy/util-utf8': 4.0.0
+ '@smithy/util-waiter': 4.0.3
tslib: 2.7.0
transitivePeerDependencies:
- aws-crt
- '@aws-sdk/client-sts@3.620.0':
+ '@aws-sdk/client-sso@3.772.0':
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
- '@aws-sdk/client-sso-oidc': 3.620.0(@aws-sdk/client-sts@3.620.0)
- '@aws-sdk/core': 3.620.0
- '@aws-sdk/credential-provider-node': 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)
- '@aws-sdk/middleware-host-header': 3.620.0
- '@aws-sdk/middleware-logger': 3.609.0
- '@aws-sdk/middleware-recursion-detection': 3.620.0
- '@aws-sdk/middleware-user-agent': 3.620.0
- '@aws-sdk/region-config-resolver': 3.614.0
- '@aws-sdk/types': 3.609.0
- '@aws-sdk/util-endpoints': 3.614.0
- '@aws-sdk/util-user-agent-browser': 3.609.0
- '@aws-sdk/util-user-agent-node': 3.614.0
- '@smithy/config-resolver': 3.0.5
- '@smithy/core': 2.3.1
- '@smithy/fetch-http-handler': 3.2.4
- '@smithy/hash-node': 3.0.3
- '@smithy/invalid-dependency': 3.0.3
- '@smithy/middleware-content-length': 3.0.5
- '@smithy/middleware-endpoint': 3.1.0
- '@smithy/middleware-retry': 3.0.13
- '@smithy/middleware-serde': 3.0.3
- '@smithy/middleware-stack': 3.0.3
- '@smithy/node-config-provider': 3.1.4
- '@smithy/node-http-handler': 3.1.4
- '@smithy/protocol-http': 4.1.0
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- '@smithy/url-parser': 3.0.3
- '@smithy/util-base64': 3.0.0
- '@smithy/util-body-length-browser': 3.0.0
- '@smithy/util-body-length-node': 3.0.0
- '@smithy/util-defaults-mode-browser': 3.0.13
- '@smithy/util-defaults-mode-node': 3.0.13
- '@smithy/util-endpoints': 2.0.5
- '@smithy/util-middleware': 3.0.3
- '@smithy/util-retry': 3.0.3
- '@smithy/util-utf8': 3.0.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/middleware-host-header': 3.734.0
+ '@aws-sdk/middleware-logger': 3.734.0
+ '@aws-sdk/middleware-recursion-detection': 3.772.0
+ '@aws-sdk/middleware-user-agent': 3.758.0
+ '@aws-sdk/region-config-resolver': 3.734.0
+ '@aws-sdk/types': 3.734.0
+ '@aws-sdk/util-endpoints': 3.743.0
+ '@aws-sdk/util-user-agent-browser': 3.734.0
+ '@aws-sdk/util-user-agent-node': 3.758.0
+ '@smithy/config-resolver': 4.1.0
+ '@smithy/core': 3.2.0
+ '@smithy/fetch-http-handler': 5.0.2
+ '@smithy/hash-node': 4.0.2
+ '@smithy/invalid-dependency': 4.0.2
+ '@smithy/middleware-content-length': 4.0.2
+ '@smithy/middleware-endpoint': 4.1.0
+ '@smithy/middleware-retry': 4.1.0
+ '@smithy/middleware-serde': 4.0.3
+ '@smithy/middleware-stack': 4.0.2
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/node-http-handler': 4.0.4
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
+ '@smithy/url-parser': 4.0.2
+ '@smithy/util-base64': 4.0.0
+ '@smithy/util-body-length-browser': 4.0.0
+ '@smithy/util-body-length-node': 4.0.0
+ '@smithy/util-defaults-mode-browser': 4.0.8
+ '@smithy/util-defaults-mode-node': 4.0.8
+ '@smithy/util-endpoints': 3.0.2
+ '@smithy/util-middleware': 4.0.2
+ '@smithy/util-retry': 4.0.2
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
transitivePeerDependencies:
- aws-crt
- '@aws-sdk/core@3.620.0':
+ '@aws-sdk/core@3.758.0':
dependencies:
- '@smithy/core': 2.3.1
- '@smithy/protocol-http': 4.1.0
- '@smithy/signature-v4': 4.1.0
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- fast-xml-parser: 4.2.5
+ '@aws-sdk/types': 3.734.0
+ '@smithy/core': 3.2.0
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/property-provider': 4.0.2
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/signature-v4': 5.0.2
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-middleware': 4.0.2
+ fast-xml-parser: 4.4.1
tslib: 2.7.0
- '@aws-sdk/credential-provider-env@3.609.0':
+ '@aws-sdk/credential-provider-env@3.758.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/property-provider': 3.1.3
- '@smithy/types': 3.3.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/property-provider': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/credential-provider-http@3.620.0':
+ '@aws-sdk/credential-provider-http@3.758.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/fetch-http-handler': 3.2.4
- '@smithy/node-http-handler': 3.1.4
- '@smithy/property-provider': 3.1.3
- '@smithy/protocol-http': 4.1.0
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- '@smithy/util-stream': 3.1.3
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/fetch-http-handler': 5.0.2
+ '@smithy/node-http-handler': 4.0.4
+ '@smithy/property-provider': 4.0.2
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-stream': 4.2.0
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)':
+ '@aws-sdk/credential-provider-ini@3.772.0':
dependencies:
- '@aws-sdk/client-sts': 3.620.0
- '@aws-sdk/credential-provider-env': 3.609.0
- '@aws-sdk/credential-provider-http': 3.620.0
- '@aws-sdk/credential-provider-process': 3.614.0
- '@aws-sdk/credential-provider-sso': 3.620.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))
- '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.620.0)
- '@aws-sdk/types': 3.609.0
- '@smithy/credential-provider-imds': 3.2.0
- '@smithy/property-provider': 3.1.3
- '@smithy/shared-ini-file-loader': 3.1.4
- '@smithy/types': 3.3.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/credential-provider-env': 3.758.0
+ '@aws-sdk/credential-provider-http': 3.758.0
+ '@aws-sdk/credential-provider-process': 3.758.0
+ '@aws-sdk/credential-provider-sso': 3.772.0
+ '@aws-sdk/credential-provider-web-identity': 3.772.0
+ '@aws-sdk/nested-clients': 3.772.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/credential-provider-imds': 4.0.2
+ '@smithy/property-provider': 4.0.2
+ '@smithy/shared-ini-file-loader': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
transitivePeerDependencies:
- - '@aws-sdk/client-sso-oidc'
- aws-crt
- '@aws-sdk/credential-provider-node@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)':
+ '@aws-sdk/credential-provider-node@3.772.0':
dependencies:
- '@aws-sdk/credential-provider-env': 3.609.0
- '@aws-sdk/credential-provider-http': 3.620.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)
- '@aws-sdk/credential-provider-process': 3.614.0
- '@aws-sdk/credential-provider-sso': 3.620.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))
- '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.620.0)
- '@aws-sdk/types': 3.609.0
- '@smithy/credential-provider-imds': 3.2.0
- '@smithy/property-provider': 3.1.3
- '@smithy/shared-ini-file-loader': 3.1.4
- '@smithy/types': 3.3.0
+ '@aws-sdk/credential-provider-env': 3.758.0
+ '@aws-sdk/credential-provider-http': 3.758.0
+ '@aws-sdk/credential-provider-ini': 3.772.0
+ '@aws-sdk/credential-provider-process': 3.758.0
+ '@aws-sdk/credential-provider-sso': 3.772.0
+ '@aws-sdk/credential-provider-web-identity': 3.772.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/credential-provider-imds': 4.0.2
+ '@smithy/property-provider': 4.0.2
+ '@smithy/shared-ini-file-loader': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
transitivePeerDependencies:
- - '@aws-sdk/client-sso-oidc'
- - '@aws-sdk/client-sts'
- aws-crt
- '@aws-sdk/credential-provider-process@3.614.0':
+ '@aws-sdk/credential-provider-process@3.758.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/property-provider': 3.1.3
- '@smithy/shared-ini-file-loader': 3.1.4
- '@smithy/types': 3.3.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/property-provider': 4.0.2
+ '@smithy/shared-ini-file-loader': 4.0.2
+ '@smithy/types': 4.2.0
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))':
+ '@aws-sdk/credential-provider-sso@3.772.0':
dependencies:
- '@aws-sdk/client-sso': 3.620.0
- '@aws-sdk/token-providers': 3.614.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))
- '@aws-sdk/types': 3.609.0
- '@smithy/property-provider': 3.1.3
- '@smithy/shared-ini-file-loader': 3.1.4
- '@smithy/types': 3.3.0
+ '@aws-sdk/client-sso': 3.772.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/token-providers': 3.772.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/property-provider': 4.0.2
+ '@smithy/shared-ini-file-loader': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
transitivePeerDependencies:
- - '@aws-sdk/client-sso-oidc'
- aws-crt
- '@aws-sdk/credential-provider-web-identity@3.609.0(@aws-sdk/client-sts@3.620.0)':
+ '@aws-sdk/credential-provider-web-identity@3.772.0':
dependencies:
- '@aws-sdk/client-sts': 3.620.0
- '@aws-sdk/types': 3.609.0
- '@smithy/property-provider': 3.1.3
- '@smithy/types': 3.3.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/nested-clients': 3.772.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/property-provider': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
+ transitivePeerDependencies:
+ - aws-crt
- '@aws-sdk/lib-storage@3.620.0(@aws-sdk/client-s3@3.620.0)':
+ '@aws-sdk/lib-storage@3.772.0(@aws-sdk/client-s3@3.772.0)':
dependencies:
- '@aws-sdk/client-s3': 3.620.0
- '@smithy/abort-controller': 3.1.1
- '@smithy/middleware-endpoint': 3.1.0
- '@smithy/smithy-client': 3.1.11
+ '@aws-sdk/client-s3': 3.772.0
+ '@smithy/abort-controller': 4.0.2
+ '@smithy/middleware-endpoint': 4.1.0
+ '@smithy/smithy-client': 4.2.0
buffer: 5.6.0
events: 3.3.0
stream-browserify: 3.0.0
- tslib: 2.6.3
+ tslib: 2.7.0
- '@aws-sdk/middleware-bucket-endpoint@3.620.0':
+ '@aws-sdk/middleware-bucket-endpoint@3.734.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@aws-sdk/util-arn-parser': 3.568.0
- '@smithy/node-config-provider': 3.1.4
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
- '@smithy/util-config-provider': 3.0.0
+ '@aws-sdk/types': 3.734.0
+ '@aws-sdk/util-arn-parser': 3.723.0
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-config-provider': 4.0.0
tslib: 2.7.0
- '@aws-sdk/middleware-expect-continue@3.620.0':
+ '@aws-sdk/middleware-expect-continue@3.734.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/middleware-flexible-checksums@3.620.0':
+ '@aws-sdk/middleware-flexible-checksums@3.758.0':
dependencies:
'@aws-crypto/crc32': 5.2.0
'@aws-crypto/crc32c': 5.2.0
- '@aws-sdk/types': 3.609.0
- '@smithy/is-array-buffer': 3.0.0
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
- '@smithy/util-utf8': 3.0.0
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/is-array-buffer': 4.0.0
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-middleware': 4.0.2
+ '@smithy/util-stream': 4.2.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
- '@aws-sdk/middleware-host-header@3.620.0':
+ '@aws-sdk/middleware-host-header@3.734.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/middleware-location-constraint@3.609.0':
+ '@aws-sdk/middleware-location-constraint@3.734.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/types': 3.3.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/middleware-logger@3.609.0':
+ '@aws-sdk/middleware-logger@3.734.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/types': 3.3.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/middleware-recursion-detection@3.620.0':
+ '@aws-sdk/middleware-recursion-detection@3.772.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/middleware-sdk-s3@3.620.0':
+ '@aws-sdk/middleware-sdk-s3@3.758.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@aws-sdk/util-arn-parser': 3.568.0
- '@smithy/node-config-provider': 3.1.4
- '@smithy/protocol-http': 4.1.0
- '@smithy/signature-v4': 4.1.0
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- '@smithy/util-config-provider': 3.0.0
- '@smithy/util-stream': 3.1.3
- '@smithy/util-utf8': 3.0.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@aws-sdk/util-arn-parser': 3.723.0
+ '@smithy/core': 3.2.0
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/signature-v4': 5.0.2
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-config-provider': 4.0.0
+ '@smithy/util-middleware': 4.0.2
+ '@smithy/util-stream': 4.2.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
- '@aws-sdk/middleware-signing@3.620.0':
+ '@aws-sdk/middleware-ssec@3.734.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/property-provider': 3.1.3
- '@smithy/protocol-http': 4.1.0
- '@smithy/signature-v4': 4.1.0
- '@smithy/types': 3.3.0
- '@smithy/util-middleware': 3.0.3
+ '@aws-sdk/types': 3.734.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/middleware-ssec@3.609.0':
+ '@aws-sdk/middleware-user-agent@3.758.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/types': 3.3.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@aws-sdk/util-endpoints': 3.743.0
+ '@smithy/core': 3.2.0
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/middleware-user-agent@3.620.0':
+ '@aws-sdk/nested-clients@3.772.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@aws-sdk/util-endpoints': 3.614.0
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
+ '@aws-crypto/sha256-browser': 5.2.0
+ '@aws-crypto/sha256-js': 5.2.0
+ '@aws-sdk/core': 3.758.0
+ '@aws-sdk/middleware-host-header': 3.734.0
+ '@aws-sdk/middleware-logger': 3.734.0
+ '@aws-sdk/middleware-recursion-detection': 3.772.0
+ '@aws-sdk/middleware-user-agent': 3.758.0
+ '@aws-sdk/region-config-resolver': 3.734.0
+ '@aws-sdk/types': 3.734.0
+ '@aws-sdk/util-endpoints': 3.743.0
+ '@aws-sdk/util-user-agent-browser': 3.734.0
+ '@aws-sdk/util-user-agent-node': 3.758.0
+ '@smithy/config-resolver': 4.1.0
+ '@smithy/core': 3.2.0
+ '@smithy/fetch-http-handler': 5.0.2
+ '@smithy/hash-node': 4.0.2
+ '@smithy/invalid-dependency': 4.0.2
+ '@smithy/middleware-content-length': 4.0.2
+ '@smithy/middleware-endpoint': 4.1.0
+ '@smithy/middleware-retry': 4.1.0
+ '@smithy/middleware-serde': 4.0.3
+ '@smithy/middleware-stack': 4.0.2
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/node-http-handler': 4.0.4
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
+ '@smithy/url-parser': 4.0.2
+ '@smithy/util-base64': 4.0.0
+ '@smithy/util-body-length-browser': 4.0.0
+ '@smithy/util-body-length-node': 4.0.0
+ '@smithy/util-defaults-mode-browser': 4.0.8
+ '@smithy/util-defaults-mode-node': 4.0.8
+ '@smithy/util-endpoints': 3.0.2
+ '@smithy/util-middleware': 4.0.2
+ '@smithy/util-retry': 4.0.2
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
+ transitivePeerDependencies:
+ - aws-crt
- '@aws-sdk/region-config-resolver@3.614.0':
+ '@aws-sdk/region-config-resolver@3.734.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/node-config-provider': 3.1.4
- '@smithy/types': 3.3.0
- '@smithy/util-config-provider': 3.0.0
- '@smithy/util-middleware': 3.0.3
+ '@aws-sdk/types': 3.734.0
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/types': 4.2.0
+ '@smithy/util-config-provider': 4.0.0
+ '@smithy/util-middleware': 4.0.2
tslib: 2.7.0
- '@aws-sdk/signature-v4-multi-region@3.620.0':
+ '@aws-sdk/signature-v4-multi-region@3.758.0':
dependencies:
- '@aws-sdk/middleware-sdk-s3': 3.620.0
- '@aws-sdk/types': 3.609.0
- '@smithy/protocol-http': 4.1.0
- '@smithy/signature-v4': 4.1.0
- '@smithy/types': 3.3.0
+ '@aws-sdk/middleware-sdk-s3': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/signature-v4': 5.0.2
+ '@smithy/types': 4.2.0
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))':
+ '@aws-sdk/token-providers@3.772.0':
dependencies:
- '@aws-sdk/client-sso-oidc': 3.620.0(@aws-sdk/client-sts@3.620.0)
- '@aws-sdk/types': 3.609.0
- '@smithy/property-provider': 3.1.3
- '@smithy/shared-ini-file-loader': 3.1.4
- '@smithy/types': 3.3.0
+ '@aws-sdk/nested-clients': 3.772.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/property-provider': 4.0.2
+ '@smithy/shared-ini-file-loader': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
+ transitivePeerDependencies:
+ - aws-crt
- '@aws-sdk/types@3.609.0':
+ '@aws-sdk/types@3.734.0':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/util-arn-parser@3.568.0':
+ '@aws-sdk/util-arn-parser@3.723.0':
dependencies:
tslib: 2.7.0
- '@aws-sdk/util-endpoints@3.614.0':
+ '@aws-sdk/util-endpoints@3.743.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/types': 3.3.0
- '@smithy/util-endpoints': 2.0.5
+ '@aws-sdk/types': 3.734.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-endpoints': 3.0.2
tslib: 2.7.0
'@aws-sdk/util-locate-window@3.208.0':
dependencies:
tslib: 2.7.0
- '@aws-sdk/util-user-agent-browser@3.609.0':
+ '@aws-sdk/util-user-agent-browser@3.734.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/types': 3.3.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/types': 4.2.0
bowser: 2.11.0
tslib: 2.7.0
- '@aws-sdk/util-user-agent-node@3.614.0':
+ '@aws-sdk/util-user-agent-node@3.758.0':
dependencies:
- '@aws-sdk/types': 3.609.0
- '@smithy/node-config-provider': 3.1.4
- '@smithy/types': 3.3.0
+ '@aws-sdk/middleware-user-agent': 3.758.0
+ '@aws-sdk/types': 3.734.0
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@aws-sdk/xml-builder@3.609.0':
+ '@aws-sdk/xml-builder@3.734.0':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
'@babel/code-frame@7.23.5':
@@ -11813,7 +11797,7 @@ snapshots:
'@babel/code-frame@7.24.7':
dependencies:
'@babel/highlight': 7.24.7
- picocolors: 1.0.1
+ picocolors: 1.1.1
'@babel/compat-data@7.23.5': {}
@@ -11822,15 +11806,15 @@ snapshots:
'@babel/core@7.23.5':
dependencies:
'@ampproject/remapping': 2.2.1
- '@babel/code-frame': 7.23.5
+ '@babel/code-frame': 7.24.7
'@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/parser': 7.25.7
'@babel/template': 7.22.15
- '@babel/traverse': 7.23.5
- '@babel/types': 7.24.7
+ '@babel/traverse': 7.24.7
+ '@babel/types': 7.25.7
convert-source-map: 2.0.0
debug: 4.3.7(supports-color@8.1.1)
gensync: 1.0.0-beta.2
@@ -11861,7 +11845,7 @@ snapshots:
'@babel/generator@7.24.7':
dependencies:
- '@babel/types': 7.24.7
+ '@babel/types': 7.25.7
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
jsesc: 2.5.2
@@ -11870,7 +11854,7 @@ snapshots:
dependencies:
'@babel/compat-data': 7.23.5
'@babel/helper-validator-option': 7.23.5
- browserslist: 4.22.2
+ browserslist: 4.23.0
lru-cache: 5.1.1
semver: 6.3.1
@@ -11913,7 +11897,7 @@ snapshots:
'@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-validator-identifier': 7.25.7
'@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)':
dependencies:
@@ -11973,7 +11957,7 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
chalk: 2.4.2
js-tokens: 4.0.0
- picocolors: 1.0.1
+ picocolors: 1.1.1
'@babel/parser@7.24.7':
dependencies:
@@ -12147,21 +12131,6 @@ snapshots:
'@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.25.7
- '@babel/types': 7.25.7
- debug: 4.3.7(supports-color@8.1.1)
- globals: 11.12.0
- transitivePeerDependencies:
- - supports-color
-
'@babel/traverse@7.24.7':
dependencies:
'@babel/code-frame': 7.24.7
@@ -12191,22 +12160,7 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
- '@bull-board/api@6.5.0(@bull-board/ui@6.5.0)':
- dependencies:
- '@bull-board/ui': 6.5.0
- redis-info: 3.1.0
-
- '@bull-board/fastify@6.5.0':
- dependencies:
- '@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.5.0':
- dependencies:
- '@bull-board/api': 6.5.0(@bull-board/ui@6.5.0)
+ '@bcoe/v8-coverage@1.0.2': {}
'@bundled-es-modules/cookie@2.0.1':
dependencies:
@@ -12223,74 +12177,96 @@ snapshots:
'@canvas/image-data@1.0.0': {}
+ '@chainsafe/is-ip@2.1.0': {}
+
'@colors/colors@1.5.0':
optional: true
- '@cropper/element-canvas@2.0.0-rc.2':
+ '@cropper/element-canvas@2.0.0':
+ dependencies:
+ '@cropper/element': 2.0.0
+ '@cropper/utils': 2.0.0
+
+ '@cropper/element-crosshair@2.0.0':
+ dependencies:
+ '@cropper/element': 2.0.0
+ '@cropper/utils': 2.0.0
+
+ '@cropper/element-grid@2.0.0':
+ dependencies:
+ '@cropper/element': 2.0.0
+ '@cropper/utils': 2.0.0
+
+ '@cropper/element-handle@2.0.0':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@cropper/element': 2.0.0
+ '@cropper/utils': 2.0.0
- '@cropper/element-crosshair@2.0.0-rc.2':
+ '@cropper/element-image@2.0.0':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@cropper/element': 2.0.0
+ '@cropper/element-canvas': 2.0.0
+ '@cropper/utils': 2.0.0
- '@cropper/element-grid@2.0.0-rc.2':
+ '@cropper/element-selection@2.0.0':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@cropper/element': 2.0.0
+ '@cropper/element-canvas': 2.0.0
+ '@cropper/element-image': 2.0.0
+ '@cropper/utils': 2.0.0
- '@cropper/element-handle@2.0.0-rc.2':
+ '@cropper/element-shade@2.0.0':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@cropper/element': 2.0.0
+ '@cropper/element-canvas': 2.0.0
+ '@cropper/element-selection': 2.0.0
+ '@cropper/utils': 2.0.0
- '@cropper/element-image@2.0.0-rc.2':
+ '@cropper/element-viewer@2.0.0':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/element-canvas': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@cropper/element': 2.0.0
+ '@cropper/element-canvas': 2.0.0
+ '@cropper/element-image': 2.0.0
+ '@cropper/element-selection': 2.0.0
+ '@cropper/utils': 2.0.0
- '@cropper/element-selection@2.0.0-rc.2':
+ '@cropper/element@2.0.0':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/element-canvas': 2.0.0-rc.2
- '@cropper/element-image': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@cropper/utils': 2.0.0
- '@cropper/element-shade@2.0.0-rc.2':
+ '@cropper/elements@2.0.0':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/element-canvas': 2.0.0-rc.2
- '@cropper/element-selection': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@cropper/element': 2.0.0
+ '@cropper/element-canvas': 2.0.0
+ '@cropper/element-crosshair': 2.0.0
+ '@cropper/element-grid': 2.0.0
+ '@cropper/element-handle': 2.0.0
+ '@cropper/element-image': 2.0.0
+ '@cropper/element-selection': 2.0.0
+ '@cropper/element-shade': 2.0.0
+ '@cropper/element-viewer': 2.0.0
+
+ '@cropper/utils@2.0.0': {}
+
+ '@csstools/color-helpers@5.0.2': {}
- '@cropper/element-viewer@2.0.0-rc.2':
+ '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/element-canvas': 2.0.0-rc.2
- '@cropper/element-image': 2.0.0-rc.2
- '@cropper/element-selection': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
- '@cropper/element@2.0.0-rc.2':
+ '@csstools/css-color-parser@3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)':
dependencies:
- '@cropper/utils': 2.0.0-rc.2
+ '@csstools/color-helpers': 5.0.2
+ '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
- '@cropper/elements@2.0.0-rc.2':
+ '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)':
dependencies:
- '@cropper/element': 2.0.0-rc.2
- '@cropper/element-canvas': 2.0.0-rc.2
- '@cropper/element-crosshair': 2.0.0-rc.2
- '@cropper/element-grid': 2.0.0-rc.2
- '@cropper/element-handle': 2.0.0-rc.2
- '@cropper/element-image': 2.0.0-rc.2
- '@cropper/element-selection': 2.0.0-rc.2
- '@cropper/element-shade': 2.0.0-rc.2
- '@cropper/element-viewer': 2.0.0-rc.2
+ '@csstools/css-tokenizer': 3.0.3
- '@cropper/utils@2.0.0-rc.2': {}
+ '@csstools/css-tokenizer@3.0.3': {}
'@cypress/request@3.0.6':
dependencies:
@@ -12300,7 +12276,7 @@ snapshots:
combined-stream: 1.0.8
extend: 3.0.2
forever-agent: 0.6.1
- form-data: 4.0.1
+ form-data: 4.0.2
http-signature: 1.4.0
is-typedarray: 1.0.0
isstream: 0.1.2
@@ -12342,211 +12318,154 @@ snapshots:
tslib: 2.7.0
optional: true
- '@esbuild/aix-ppc64@0.21.5':
+ '@esbuild/aix-ppc64@0.25.0':
optional: true
- '@esbuild/aix-ppc64@0.24.0':
+ '@esbuild/aix-ppc64@0.25.1':
optional: true
- '@esbuild/android-arm64@0.18.20':
+ '@esbuild/android-arm64@0.25.0':
optional: true
- '@esbuild/android-arm64@0.21.5':
+ '@esbuild/android-arm64@0.25.1':
optional: true
- '@esbuild/android-arm64@0.24.0':
+ '@esbuild/android-arm@0.25.0':
optional: true
- '@esbuild/android-arm@0.18.20':
+ '@esbuild/android-arm@0.25.1':
optional: true
- '@esbuild/android-arm@0.21.5':
+ '@esbuild/android-x64@0.25.0':
optional: true
- '@esbuild/android-arm@0.24.0':
+ '@esbuild/android-x64@0.25.1':
optional: true
- '@esbuild/android-x64@0.18.20':
+ '@esbuild/darwin-arm64@0.25.0':
optional: true
- '@esbuild/android-x64@0.21.5':
+ '@esbuild/darwin-arm64@0.25.1':
optional: true
- '@esbuild/android-x64@0.24.0':
+ '@esbuild/darwin-x64@0.25.0':
optional: true
- '@esbuild/darwin-arm64@0.18.20':
+ '@esbuild/darwin-x64@0.25.1':
optional: true
- '@esbuild/darwin-arm64@0.21.5':
+ '@esbuild/freebsd-arm64@0.25.0':
optional: true
- '@esbuild/darwin-arm64@0.24.0':
+ '@esbuild/freebsd-arm64@0.25.1':
optional: true
- '@esbuild/darwin-x64@0.18.20':
+ '@esbuild/freebsd-x64@0.25.0':
optional: true
- '@esbuild/darwin-x64@0.21.5':
+ '@esbuild/freebsd-x64@0.25.1':
optional: true
- '@esbuild/darwin-x64@0.24.0':
+ '@esbuild/linux-arm64@0.25.0':
optional: true
- '@esbuild/freebsd-arm64@0.18.20':
+ '@esbuild/linux-arm64@0.25.1':
optional: true
- '@esbuild/freebsd-arm64@0.21.5':
+ '@esbuild/linux-arm@0.25.0':
optional: true
- '@esbuild/freebsd-arm64@0.24.0':
+ '@esbuild/linux-arm@0.25.1':
optional: true
- '@esbuild/freebsd-x64@0.18.20':
+ '@esbuild/linux-ia32@0.25.0':
optional: true
- '@esbuild/freebsd-x64@0.21.5':
+ '@esbuild/linux-ia32@0.25.1':
optional: true
- '@esbuild/freebsd-x64@0.24.0':
+ '@esbuild/linux-loong64@0.25.0':
optional: true
- '@esbuild/linux-arm64@0.18.20':
+ '@esbuild/linux-loong64@0.25.1':
optional: true
- '@esbuild/linux-arm64@0.21.5':
+ '@esbuild/linux-mips64el@0.25.0':
optional: true
- '@esbuild/linux-arm64@0.24.0':
+ '@esbuild/linux-mips64el@0.25.1':
optional: true
- '@esbuild/linux-arm@0.18.20':
+ '@esbuild/linux-ppc64@0.25.0':
optional: true
- '@esbuild/linux-arm@0.21.5':
+ '@esbuild/linux-ppc64@0.25.1':
optional: true
- '@esbuild/linux-arm@0.24.0':
+ '@esbuild/linux-riscv64@0.25.0':
optional: true
- '@esbuild/linux-ia32@0.18.20':
+ '@esbuild/linux-riscv64@0.25.1':
optional: true
- '@esbuild/linux-ia32@0.21.5':
+ '@esbuild/linux-s390x@0.25.0':
optional: true
- '@esbuild/linux-ia32@0.24.0':
+ '@esbuild/linux-s390x@0.25.1':
optional: true
- '@esbuild/linux-loong64@0.18.20':
+ '@esbuild/linux-x64@0.25.0':
optional: true
- '@esbuild/linux-loong64@0.21.5':
+ '@esbuild/linux-x64@0.25.1':
optional: true
- '@esbuild/linux-loong64@0.24.0':
+ '@esbuild/netbsd-arm64@0.25.0':
optional: true
- '@esbuild/linux-mips64el@0.18.20':
+ '@esbuild/netbsd-arm64@0.25.1':
optional: true
- '@esbuild/linux-mips64el@0.21.5':
+ '@esbuild/netbsd-x64@0.25.0':
optional: true
- '@esbuild/linux-mips64el@0.24.0':
+ '@esbuild/netbsd-x64@0.25.1':
optional: true
- '@esbuild/linux-ppc64@0.18.20':
+ '@esbuild/openbsd-arm64@0.25.0':
optional: true
- '@esbuild/linux-ppc64@0.21.5':
+ '@esbuild/openbsd-arm64@0.25.1':
optional: true
- '@esbuild/linux-ppc64@0.24.0':
+ '@esbuild/openbsd-x64@0.25.0':
optional: true
- '@esbuild/linux-riscv64@0.18.20':
+ '@esbuild/openbsd-x64@0.25.1':
optional: true
- '@esbuild/linux-riscv64@0.21.5':
+ '@esbuild/sunos-x64@0.25.0':
optional: true
- '@esbuild/linux-riscv64@0.24.0':
+ '@esbuild/sunos-x64@0.25.1':
optional: true
- '@esbuild/linux-s390x@0.18.20':
+ '@esbuild/win32-arm64@0.25.0':
optional: true
- '@esbuild/linux-s390x@0.21.5':
+ '@esbuild/win32-arm64@0.25.1':
optional: true
- '@esbuild/linux-s390x@0.24.0':
+ '@esbuild/win32-ia32@0.25.0':
optional: true
- '@esbuild/linux-x64@0.18.20':
+ '@esbuild/win32-ia32@0.25.1':
optional: true
- '@esbuild/linux-x64@0.21.5':
+ '@esbuild/win32-x64@0.25.0':
optional: true
- '@esbuild/linux-x64@0.24.0':
- optional: true
-
- '@esbuild/netbsd-x64@0.18.20':
- optional: true
-
- '@esbuild/netbsd-x64@0.21.5':
- optional: true
-
- '@esbuild/netbsd-x64@0.24.0':
- optional: true
-
- '@esbuild/openbsd-arm64@0.24.0':
- optional: true
-
- '@esbuild/openbsd-x64@0.18.20':
- optional: true
-
- '@esbuild/openbsd-x64@0.21.5':
- optional: true
-
- '@esbuild/openbsd-x64@0.24.0':
- optional: true
-
- '@esbuild/sunos-x64@0.18.20':
- optional: true
-
- '@esbuild/sunos-x64@0.21.5':
- optional: true
-
- '@esbuild/sunos-x64@0.24.0':
- optional: true
-
- '@esbuild/win32-arm64@0.18.20':
- optional: true
-
- '@esbuild/win32-arm64@0.21.5':
- optional: true
-
- '@esbuild/win32-arm64@0.24.0':
- optional: true
-
- '@esbuild/win32-ia32@0.18.20':
- optional: true
-
- '@esbuild/win32-ia32@0.21.5':
- optional: true
-
- '@esbuild/win32-ia32@0.24.0':
- optional: true
-
- '@esbuild/win32-x64@0.18.20':
- optional: true
-
- '@esbuild/win32-x64@0.21.5':
- optional: true
-
- '@esbuild/win32-x64@0.24.0':
+ '@esbuild/win32-x64@0.25.1':
optional: true
'@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)':
@@ -12559,12 +12478,15 @@ snapshots:
eslint: 9.14.0
eslint-visitor-keys: 3.4.3
+ '@eslint-community/eslint-utils@4.4.0(eslint@9.22.0)':
+ dependencies:
+ eslint: 9.22.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.18.0':
@@ -12575,12 +12497,26 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@eslint/config-array@0.19.2':
+ dependencies:
+ '@eslint/object-schema': 2.1.6
+ debug: 4.3.7(supports-color@8.1.1)
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.1.0': {}
+
+ '@eslint/core@0.12.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
'@eslint/core@0.7.0': {}
'@eslint/eslintrc@2.1.4':
dependencies:
ajv: 6.12.6
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.7(supports-color@8.1.1)
espree: 9.6.1
globals: 13.24.0
ignore: 5.3.1
@@ -12605,19 +12541,42 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@eslint/eslintrc@3.3.1':
+ dependencies:
+ ajv: 6.12.6
+ 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
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
'@eslint/js@8.57.0': {}
'@eslint/js@9.14.0': {}
+ '@eslint/js@9.22.0': {}
+
'@eslint/object-schema@2.1.4': {}
+ '@eslint/object-schema@2.1.6': {}
+
'@eslint/plugin-kit@0.2.3':
dependencies:
levn: 0.4.1
+ '@eslint/plugin-kit@0.2.7':
+ dependencies:
+ '@eslint/core': 0.12.0
+ levn: 0.4.1
+
'@fastify/accept-negotiator@2.0.0': {}
- '@fastify/accepts@5.0.1':
+ '@fastify/accepts@5.0.2':
dependencies:
accepts: 1.3.8
fastify-plugin: 5.0.1
@@ -12636,23 +12595,23 @@ snapshots:
'@fastify/busboy@3.0.0': {}
- '@fastify/cookie@11.0.1':
+ '@fastify/cookie@11.0.2':
dependencies:
cookie: 1.0.2
fastify-plugin: 5.0.1
- '@fastify/cors@10.0.1':
+ '@fastify/cors@10.1.0':
dependencies:
fastify-plugin: 5.0.1
- mnemonist: 0.39.8
+ mnemonist: 0.40.0
'@fastify/deepmerge@2.0.0': {}
'@fastify/error@4.0.0': {}
- '@fastify/express@4.0.1':
+ '@fastify/express@4.0.2':
dependencies:
- express: 4.21.0
+ express: 4.21.1
fastify-plugin: 5.0.1
transitivePeerDependencies:
- supports-color
@@ -12661,12 +12620,14 @@ snapshots:
dependencies:
fast-json-stringify: 6.0.0
- '@fastify/http-proxy@10.0.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)':
+ '@fastify/forwarded@3.0.0': {}
+
+ '@fastify/http-proxy@10.0.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies:
'@fastify/reply-from': 11.0.1
fast-querystring: 1.1.2
fastify-plugin: 5.0.1
- ws: 8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+ ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -12675,13 +12636,18 @@ snapshots:
dependencies:
fast-deep-equal: 3.1.3
- '@fastify/multipart@9.0.1':
+ '@fastify/multipart@9.0.3':
dependencies:
'@fastify/busboy': 3.0.0
'@fastify/deepmerge': 2.0.0
'@fastify/error': 4.0.0
fastify-plugin: 5.0.1
- secure-json-parse: 3.0.0
+ secure-json-parse: 3.0.2
+
+ '@fastify/proxy-addr@5.0.0':
+ dependencies:
+ '@fastify/forwarded': 3.0.0
+ ipaddr.js: 2.2.0
'@fastify/reply-from@11.0.1':
dependencies:
@@ -12693,7 +12659,7 @@ snapshots:
toad-cache: 3.7.0
undici: 6.20.0
- '@fastify/send@3.1.1':
+ '@fastify/send@3.3.1':
dependencies:
'@lukeed/ms': 2.0.2
escape-html: 1.0.3
@@ -12701,16 +12667,16 @@ snapshots:
http-errors: 2.0.0
mime: 3.0.0
- '@fastify/static@8.0.2':
+ '@fastify/static@8.1.1':
dependencies:
'@fastify/accept-negotiator': 2.0.0
- '@fastify/send': 3.1.1
+ '@fastify/send': 3.3.1
content-disposition: 0.5.4
fastify-plugin: 5.0.1
fastq: 1.17.1
- glob: 11.0.0
+ glob: 11.0.1
- '@fastify/view@10.0.1':
+ '@fastify/view@10.0.2':
dependencies:
fastify-plugin: 5.0.1
toad-cache: 3.7.0
@@ -12749,7 +12715,7 @@ snapshots:
'@humanwhocodes/config-array@0.11.14':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.7(supports-color@8.1.1)
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -12764,6 +12730,8 @@ snapshots:
'@humanwhocodes/retry@0.4.1': {}
+ '@humanwhocodes/retry@0.4.2': {}
+
'@img/sharp-darwin-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.4
@@ -12839,16 +12807,16 @@ snapshots:
'@img/sharp-win32-x64@0.33.5':
optional: true
- '@inquirer/confirm@5.0.2(@types/node@22.9.0)':
+ '@inquirer/confirm@5.0.2(@types/node@22.13.11)':
dependencies:
- '@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': 10.1.0(@types/node@22.13.11)
+ '@inquirer/type': 3.0.1(@types/node@22.13.11)
+ '@types/node': 22.13.11
- '@inquirer/core@10.1.0(@types/node@22.9.0)':
+ '@inquirer/core@10.1.0(@types/node@22.13.11)':
dependencies:
'@inquirer/figures': 1.0.8
- '@inquirer/type': 3.0.1(@types/node@22.9.0)
+ '@inquirer/type': 3.0.1(@types/node@22.13.11)
ansi-escapes: 4.3.2
cli-width: 4.1.0
mute-stream: 2.0.0
@@ -12861,9 +12829,9 @@ snapshots:
'@inquirer/figures@1.0.8': {}
- '@inquirer/type@3.0.1(@types/node@22.9.0)':
+ '@inquirer/type@3.0.1(@types/node@22.13.11)':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@ioredis/commands@1.2.0': {}
@@ -12876,6 +12844,10 @@ snapshots:
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.2
+
'@istanbuljs/load-nyc-config@1.1.0':
dependencies:
camelcase: 5.3.1
@@ -12889,7 +12861,7 @@ snapshots:
'@jest/console@29.7.0':
dependencies:
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
chalk: 4.1.2
jest-message-util: 29.7.0
jest-util: 29.7.0
@@ -12902,14 +12874,14 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
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@22.9.0)
+ jest-config: 29.7.0(@types/node@22.13.11)
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@@ -12938,7 +12910,7 @@ snapshots:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
jest-mock: 29.7.0
'@jest/expect-utils@29.7.0':
@@ -12956,7 +12928,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -12978,7 +12950,7 @@ snapshots:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.18
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
chalk: 4.1.2
collect-v8-coverage: 1.0.1
exit: 0.1.2
@@ -13048,24 +13020,23 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.4
'@types/istanbul-reports': 3.0.1
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/yargs': 17.0.19
chalk: 4.1.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))':
+ '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))':
dependencies:
- glob: 7.2.3
- glob-promise: 4.2.2(glob@7.2.3)
+ glob: 10.3.10
magic-string: 0.27.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)
+ react-docgen-typescript: 2.2.2(typescript@5.8.2)
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
optionalDependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/trace-mapping': 0.3.25
'@jridgewell/resolve-uri@3.1.0': {}
@@ -13079,8 +13050,6 @@ snapshots:
'@jridgewell/sourcemap-codec@1.4.14': {}
- '@jridgewell/sourcemap-codec@1.4.15': {}
-
'@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/trace-mapping@0.3.18':
@@ -13091,7 +13060,7 @@ snapshots:
'@jridgewell/trace-mapping@0.3.25':
dependencies:
'@jridgewell/resolve-uri': 3.1.0
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.0
'@jsdevtools/ono@7.1.3': {}
@@ -13109,57 +13078,58 @@ snapshots:
dependencies:
'@mcaptcha/core-glue': 0.1.0-alpha-5
- '@mdx-js/react@3.0.1(@types/react@18.0.28)(react@18.3.1)':
+ '@mdx-js/react@3.0.1(@types/react@18.0.28)(react@19.0.0)':
dependencies:
'@types/mdx': 2.0.3
'@types/react': 18.0.28
- react: 18.3.1
+ react: 19.0.0
- '@microsoft/api-extractor-model@7.29.8(@types/node@22.9.0)':
+ '@microsoft/api-extractor-model@7.30.3(@types/node@22.13.9)':
dependencies:
- '@microsoft/tsdoc': 0.15.0
- '@microsoft/tsdoc-config': 0.17.0
- '@rushstack/node-core-library': 5.9.0(@types/node@22.9.0)
+ '@microsoft/tsdoc': 0.15.1
+ '@microsoft/tsdoc-config': 0.17.1
+ '@rushstack/node-core-library': 5.11.0(@types/node@22.13.9)
transitivePeerDependencies:
- '@types/node'
- '@microsoft/api-extractor@7.47.11(@types/node@22.9.0)':
+ '@microsoft/api-extractor@7.51.1(@types/node@22.13.9)':
dependencies:
- '@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@22.9.0)
+ '@microsoft/api-extractor-model': 7.30.3(@types/node@22.13.9)
+ '@microsoft/tsdoc': 0.15.1
+ '@microsoft/tsdoc-config': 0.17.1
+ '@rushstack/node-core-library': 5.11.0(@types/node@22.13.9)
'@rushstack/rig-package': 0.5.3
- '@rushstack/terminal': 0.14.2(@types/node@22.9.0)
- '@rushstack/ts-command-line': 4.23.0(@types/node@22.9.0)
+ '@rushstack/terminal': 0.15.0(@types/node@22.13.9)
+ '@rushstack/ts-command-line': 4.23.5(@types/node@22.13.9)
lodash: 4.17.21
minimatch: 3.0.8
resolve: 1.22.8
semver: 7.5.4
source-map: 0.6.1
- typescript: 5.4.2
+ typescript: 5.7.3
transitivePeerDependencies:
- '@types/node'
- '@microsoft/tsdoc-config@0.17.0':
+ '@microsoft/tsdoc-config@0.17.1':
dependencies:
- '@microsoft/tsdoc': 0.15.0
+ '@microsoft/tsdoc': 0.15.1
ajv: 8.12.0
jju: 1.4.0
resolve: 1.22.8
- '@microsoft/tsdoc@0.15.0': {}
+ '@microsoft/tsdoc@0.15.1': {}
'@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.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)':
+ '@misskey-dev/eslint-plugin@2.1.0(@eslint/compat@1.1.1)(@stylistic/eslint-plugin@4.2.0(eslint@9.22.0)(typescript@5.8.2))(@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2))(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0))(eslint@9.22.0)(globals@16.0.0)':
dependencies:
'@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: 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
+ '@stylistic/eslint-plugin': 4.2.0(eslint@9.22.0)(typescript@5.8.2)
+ '@typescript-eslint/eslint-plugin': 8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)
+ '@typescript-eslint/parser': 8.26.0(eslint@9.22.0)(typescript@5.8.2)
+ eslint: 9.22.0
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)
+ globals: 16.0.0
'@misskey-dev/sharp-read-bmp@1.2.0':
dependencies:
@@ -13167,27 +13137,15 @@ snapshots:
decode-ico: 0.4.1
sharp: 0.33.5
- '@misskey-dev/summaly@5.1.0':
+ '@misskey-dev/summaly@5.2.0':
dependencies:
- cheerio: 1.0.0-rc.12
+ cheerio: 1.0.0
escape-regexp: 0.0.1
- got: 12.6.1
- html-entities: 2.3.2
+ got: 14.4.6
+ html-entities: 2.5.2
iconv-lite: 0.6.3
- jschardet: 3.0.0
- private-ip: 2.3.3
- trace-redirect: 1.0.6
-
- '@mole-inc/bin-wrapper@8.0.1':
- dependencies:
- bin-check: 4.1.0
- bin-version-check: 5.0.0
- content-disposition: 0.5.4
- ext-name: 5.0.0
- file-type: 17.1.6
- filenamify: 5.1.1
- got: 11.8.5
- os-filter-obj: 2.0.0
+ jschardet: 3.1.4
+ private-ip: 3.0.2
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2':
optional: true
@@ -13207,7 +13165,7 @@ snapshots:
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2':
optional: true
- '@mswjs/interceptors@0.36.10':
+ '@mswjs/interceptors@0.37.6':
dependencies:
'@open-draft/deferred-promise': 2.2.0
'@open-draft/logger': 0.3.0
@@ -13216,51 +13174,49 @@ snapshots:
outvariant: 1.4.3
strict-event-emitter: 0.5.1
- '@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1)':
+ '@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies:
iterare: 1.2.1
reflect-metadata: 0.2.2
- rxjs: 7.8.1
- tslib: 2.7.0
+ rxjs: 7.8.2
+ tslib: 2.8.1
uid: 2.0.2
- '@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/core@11.0.12(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)':
dependencies:
- '@nestjs/common': 10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1)
- '@nuxtjs/opencollective': 0.3.2(encoding@0.1.13)
+ '@nestjs/common': 11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nuxt/opencollective': 0.4.1
fast-safe-stringify: 2.1.1
iterare: 1.2.1
- path-to-regexp: 3.3.0
+ path-to-regexp: 8.2.0
reflect-metadata: 0.2.2
- rxjs: 7.8.1
- tslib: 2.7.0
+ rxjs: 7.8.2
+ tslib: 2.8.1
uid: 2.0.2
optionalDependencies:
- '@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.15(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12)
- '@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)':
+ '@nestjs/platform-express@10.4.15(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12)':
dependencies:
- '@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/common': 11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.0.12(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
body-parser: 1.20.3
cors: 2.8.5
- express: 4.21.1
+ express: 4.21.2
multer: 1.4.4-lts.1
- tslib: 2.7.0
+ tslib: 2.8.1
transitivePeerDependencies:
- supports-color
- '@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))':
+ '@nestjs/testing@11.0.12(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12))':
dependencies:
- '@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
+ '@nestjs/common': 11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.0.12(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ tslib: 2.8.1
optionalDependencies:
- '@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)
+ '@nestjs/platform-express': 10.4.15(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12)
- '@noble/hashes@1.5.0': {}
+ '@noble/hashes@1.6.1': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
@@ -13288,13 +13244,9 @@ snapshots:
dependencies:
semver: 7.6.3
- '@nuxtjs/opencollective@0.3.2(encoding@0.1.13)':
+ '@nuxt/opencollective@0.4.1':
dependencies:
- chalk: 4.1.2
- consola: 2.15.3
- node-fetch: 2.7.0(encoding@0.1.13)
- transitivePeerDependencies:
- - encoding
+ consola: 3.4.2
'@one-ini/wasm@0.1.1': {}
@@ -13307,250 +13259,242 @@ snapshots:
'@open-draft/until@2.1.0': {}
- '@opentelemetry/api-logs@0.52.1':
- dependencies:
- '@opentelemetry/api': 1.9.0
-
'@opentelemetry/api-logs@0.53.0':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/api-logs@0.54.2':
+ '@opentelemetry/api-logs@0.57.1':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/api@1.9.0': {}
-
- '@opentelemetry/context-async-hooks@1.25.1(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/api-logs@0.57.2':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/semantic-conventions': 1.25.1
+ '@opentelemetry/api@1.9.0': {}
- '@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/context-async-hooks@1.30.1(@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)':
+ '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/semantic-conventions': 1.27.0
+ '@opentelemetry/semantic-conventions': 1.28.0
- '@opentelemetry/instrumentation-amqplib@0.43.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-amqplib@0.46.1(@opentelemetry/api@1.9.0)':
dependencies:
'@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/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-connect@0.40.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-connect@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.54.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
'@types/connect': 3.4.36
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-dataloader@0.12.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-dataloader@0.16.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-express@0.44.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-express@0.47.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.54.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-fastify@0.41.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-fastify@0.44.1(@opentelemetry/api@1.9.0)':
dependencies:
'@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/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-fs@0.16.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-fs@0.19.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.54.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-generic-pool@0.39.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-generic-pool@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/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-graphql@0.44.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-graphql@0.47.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-hapi@0.41.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-hapi@0.45.1(@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)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-http@0.53.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-http@0.57.1(@opentelemetry/api@1.9.0)':
dependencies:
'@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
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.28.0
+ forwarded-parse: 2.1.2
semver: 7.6.3
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-ioredis@0.43.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-ioredis@0.47.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/redis-common': 0.36.2
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-kafkajs@0.4.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-kafkajs@0.7.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.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)':
+ '@opentelemetry/instrumentation-knex@0.44.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-koa@0.43.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-koa@0.47.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)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-lru-memoizer@0.40.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-lru-memoizer@0.44.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-mongodb@0.48.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-mongodb@0.51.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-mongoose@0.42.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-mongoose@0.46.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)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-mysql2@0.41.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-mysql2@0.45.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@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.41.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-mysql@0.45.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
'@types/mysql': 2.15.26
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-nestjs-core@0.40.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-nestjs-core@0.44.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-pg@0.44.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-pg@0.50.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.27.0
'@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0)
'@types/pg': 8.6.1
'@types/pg-pool': 2.0.6
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-redis-4@0.42.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-redis-4@0.46.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/redis-common': 0.36.2
'@opentelemetry/semantic-conventions': 1.28.0
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation-tedious@0.15.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation-tedious@0.18.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.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)':
+ '@opentelemetry/instrumentation-undici@0.10.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)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/api-logs': 0.52.1
- '@types/shimmer': 1.0.5
+ '@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
@@ -13558,10 +13502,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation@0.57.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/api-logs': 0.53.0
+ '@opentelemetry/api-logs': 0.57.1
'@types/shimmer': 1.2.0
import-in-the-middle: 1.11.2
require-in-the-middle: 7.3.0
@@ -13570,10 +13514,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/api-logs': 0.54.2
+ '@opentelemetry/api-logs': 0.57.2
'@types/shimmer': 1.2.0
import-in-the-middle: 1.11.2
require-in-the-middle: 7.3.0
@@ -13584,20 +13528,18 @@ snapshots:
'@opentelemetry/redis-common@0.36.2': {}
- '@opentelemetry/resources@1.28.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.27.0
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.28.0
- '@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@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/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/semantic-conventions@1.27.0': {}
@@ -13606,7 +13548,68 @@ snapshots:
'@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+
+ '@parcel/watcher-android-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-darwin-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-darwin-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-x64-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher@2.5.1':
+ dependencies:
+ detect-libc: 1.0.3
+ is-glob: 4.0.3
+ micromatch: 4.0.8
+ node-addon-api: 7.1.0
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.5.1
+ '@parcel/watcher-darwin-arm64': 2.5.1
+ '@parcel/watcher-darwin-x64': 2.5.1
+ '@parcel/watcher-freebsd-x64': 2.5.1
+ '@parcel/watcher-linux-arm-glibc': 2.5.1
+ '@parcel/watcher-linux-arm-musl': 2.5.1
+ '@parcel/watcher-linux-arm64-glibc': 2.5.1
+ '@parcel/watcher-linux-arm64-musl': 2.5.1
+ '@parcel/watcher-linux-x64-glibc': 2.5.1
+ '@parcel/watcher-linux-x64-musl': 2.5.1
+ '@parcel/watcher-win32-arm64': 2.5.1
+ '@parcel/watcher-win32-ia32': 2.5.1
+ '@parcel/watcher-win32-x64': 2.5.1
+ optional: true
'@peculiar/asn1-android@2.3.10':
dependencies:
@@ -13655,24 +13658,23 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
- '@prisma/instrumentation@5.19.1':
+ '@prisma/instrumentation@5.22.0':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
- '@readme/better-ajv-errors@1.6.0(ajv@8.17.1)':
+ '@readme/better-ajv-errors@2.3.2(ajv@8.17.1)':
dependencies:
'@babel/code-frame': 7.24.7
'@babel/runtime': 7.23.4
'@humanwhocodes/momoa': 2.0.4
ajv: 8.17.1
- chalk: 4.1.2
- json-to-ast: 2.1.0
jsonpointer: 5.0.1
leven: 3.1.0
+ picocolors: 1.1.1
'@readme/json-schema-ref-parser@1.2.0':
dependencies:
@@ -13681,11 +13683,11 @@ snapshots:
call-me-maybe: 1.0.2
js-yaml: 4.1.0
- '@readme/openapi-parser@2.6.0(openapi-types@12.1.3)':
+ '@readme/openapi-parser@2.7.0(openapi-types@12.1.3)':
dependencies:
'@apidevtools/swagger-methods': 3.0.2
'@jsdevtools/ono': 7.1.3
- '@readme/better-ajv-errors': 1.6.0(ajv@8.17.1)
+ '@readme/better-ajv-errors': 2.3.2(ajv@8.17.1)
'@readme/json-schema-ref-parser': 1.2.0
'@readme/openapi-schemas': 3.1.0
ajv: 8.17.1
@@ -13695,113 +13697,116 @@ snapshots:
'@readme/openapi-schemas@3.1.0': {}
- '@rollup/plugin-json@6.1.0(rollup@4.26.0)':
+ '@rollup/plugin-json@6.1.0(rollup@4.36.0)':
dependencies:
- '@rollup/pluginutils': 5.1.3(rollup@4.26.0)
+ '@rollup/pluginutils': 5.1.4(rollup@4.36.0)
optionalDependencies:
- rollup: 4.26.0
+ rollup: 4.36.0
- '@rollup/plugin-replace@5.0.7(rollup@4.26.0)':
+ '@rollup/plugin-replace@6.0.2(rollup@4.36.0)':
dependencies:
- '@rollup/pluginutils': 5.1.3(rollup@4.26.0)
- magic-string: 0.30.10
+ '@rollup/pluginutils': 5.1.4(rollup@4.36.0)
+ magic-string: 0.30.17
optionalDependencies:
- rollup: 4.26.0
+ rollup: 4.36.0
- '@rollup/pluginutils@5.1.3(rollup@4.26.0)':
+ '@rollup/pluginutils@5.1.4(rollup@4.36.0)':
dependencies:
'@types/estree': 1.0.6
estree-walker: 2.0.2
picomatch: 4.0.2
optionalDependencies:
- rollup: 4.26.0
+ rollup: 4.36.0
+
+ '@rollup/rollup-android-arm-eabi@4.36.0':
+ optional: true
- '@rollup/rollup-android-arm-eabi@4.26.0':
+ '@rollup/rollup-android-arm64@4.36.0':
optional: true
- '@rollup/rollup-android-arm64@4.26.0':
+ '@rollup/rollup-darwin-arm64@4.36.0':
optional: true
- '@rollup/rollup-darwin-arm64@4.26.0':
+ '@rollup/rollup-darwin-x64@4.36.0':
optional: true
- '@rollup/rollup-darwin-x64@4.26.0':
+ '@rollup/rollup-freebsd-arm64@4.36.0':
optional: true
- '@rollup/rollup-freebsd-arm64@4.26.0':
+ '@rollup/rollup-freebsd-x64@4.36.0':
optional: true
- '@rollup/rollup-freebsd-x64@4.26.0':
+ '@rollup/rollup-linux-arm-gnueabihf@4.36.0':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.26.0':
+ '@rollup/rollup-linux-arm-musleabihf@4.36.0':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.26.0':
+ '@rollup/rollup-linux-arm64-gnu@4.36.0':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.26.0':
+ '@rollup/rollup-linux-arm64-musl@4.36.0':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.26.0':
+ '@rollup/rollup-linux-loongarch64-gnu@4.36.0':
optional: true
- '@rollup/rollup-linux-powerpc64le-gnu@4.26.0':
+ '@rollup/rollup-linux-powerpc64le-gnu@4.36.0':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.26.0':
+ '@rollup/rollup-linux-riscv64-gnu@4.36.0':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.26.0':
+ '@rollup/rollup-linux-s390x-gnu@4.36.0':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.26.0':
+ '@rollup/rollup-linux-x64-gnu@4.36.0':
optional: true
- '@rollup/rollup-linux-x64-musl@4.26.0':
+ '@rollup/rollup-linux-x64-musl@4.36.0':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.26.0':
+ '@rollup/rollup-win32-arm64-msvc@4.36.0':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.26.0':
+ '@rollup/rollup-win32-ia32-msvc@4.36.0':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.26.0':
+ '@rollup/rollup-win32-x64-msvc@4.36.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@22.9.0)':
+ '@rushstack/node-core-library@5.11.0(@types/node@22.13.9)':
dependencies:
ajv: 8.13.0
ajv-draft-04: 1.0.0(ajv@8.13.0)
ajv-formats: 3.0.1(ajv@8.13.0)
- fs-extra: 7.0.1
+ fs-extra: 11.3.0
import-lazy: 4.0.0
jju: 1.4.0
resolve: 1.22.8
semver: 7.5.4
optionalDependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.9
'@rushstack/rig-package@0.5.3':
dependencies:
resolve: 1.22.8
strip-json-comments: 3.1.1
- '@rushstack/terminal@0.14.2(@types/node@22.9.0)':
+ '@rushstack/terminal@0.15.0(@types/node@22.13.9)':
dependencies:
- '@rushstack/node-core-library': 5.9.0(@types/node@22.9.0)
+ '@rushstack/node-core-library': 5.11.0(@types/node@22.13.9)
supports-color: 8.1.1
optionalDependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.9
- '@rushstack/ts-command-line@4.23.0(@types/node@22.9.0)':
+ '@rushstack/ts-command-line@4.23.5(@types/node@22.13.9)':
dependencies:
- '@rushstack/terminal': 0.14.2(@types/node@22.9.0)
+ '@rushstack/terminal': 0.15.0(@types/node@22.13.9)
'@types/argparse': 1.0.38
argparse: 1.0.10
string-argv: 0.3.1
@@ -13810,107 +13815,99 @@ snapshots:
'@sec-ant/readable-stream@0.4.1': {}
- '@sentry/core@8.38.0':
- dependencies:
- '@sentry/types': 8.38.0
- '@sentry/utils': 8.38.0
+ '@sentry/core@8.55.0': {}
- '@sentry/node@8.38.0':
+ '@sentry/node@8.55.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.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/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-amqplib': 0.46.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-connect': 0.43.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-dataloader': 0.16.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-express': 0.47.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-fastify': 0.44.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-fs': 0.19.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-generic-pool': 0.43.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-graphql': 0.47.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-hapi': 0.45.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-http': 0.57.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-ioredis': 0.47.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-kafkajs': 0.7.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-knex': 0.44.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-koa': 0.47.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-lru-memoizer': 0.44.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-mongodb': 0.51.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-mongoose': 0.46.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-mysql': 0.45.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-mysql2': 0.45.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-nestjs-core': 0.44.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-pg': 0.50.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-redis-4': 0.46.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-tedious': 0.18.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation-undici': 0.10.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 1.30.1(@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
+ '@prisma/instrumentation': 5.22.0
+ '@sentry/core': 8.55.0
+ '@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0)
import-in-the-middle: 1.11.2
transitivePeerDependencies:
- supports-color
- '@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/opentelemetry@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@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.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
+ '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 1.30.1(@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/core': 8.55.0
- '@sentry/profiling-node@8.38.0':
+ '@sentry/profiling-node@8.55.0':
dependencies:
- '@sentry/core': 8.38.0
- '@sentry/node': 8.38.0
- '@sentry/types': 8.38.0
- '@sentry/utils': 8.38.0
+ '@sentry/core': 8.55.0
+ '@sentry/node': 8.55.0
detect-libc: 2.0.3
node-abi: 3.62.0
transitivePeerDependencies:
- supports-color
- '@sentry/types@8.38.0': {}
+ '@shikijs/core@3.2.1':
+ dependencies:
+ '@shikijs/types': 3.2.1
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
- '@sentry/utils@8.38.0':
+ '@shikijs/engine-javascript@3.2.1':
dependencies:
- '@sentry/types': 8.38.0
+ '@shikijs/types': 3.2.1
+ '@shikijs/vscode-textmate': 10.0.2
+ oniguruma-to-es: 4.1.0
- '@shikijs/core@1.22.2':
+ '@shikijs/engine-oniguruma@3.2.1':
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/types': 3.2.1
+ '@shikijs/vscode-textmate': 10.0.2
- '@shikijs/engine-javascript@1.22.2':
+ '@shikijs/langs@3.2.1':
dependencies:
- '@shikijs/types': 1.22.2
- '@shikijs/vscode-textmate': 9.3.0
- oniguruma-to-js: 0.4.3
+ '@shikijs/types': 3.2.1
- '@shikijs/engine-oniguruma@1.22.2':
+ '@shikijs/themes@3.2.1':
dependencies:
- '@shikijs/types': 1.22.2
- '@shikijs/vscode-textmate': 9.3.0
+ '@shikijs/types': 3.2.1
- '@shikijs/types@1.22.2':
+ '@shikijs/types@3.2.1':
dependencies:
- '@shikijs/vscode-textmate': 9.3.0
+ '@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
- '@shikijs/vscode-textmate@9.3.0': {}
+ '@shikijs/vscode-textmate@10.0.2': {}
'@sideway/address@4.1.4':
dependencies:
@@ -13924,7 +13921,7 @@ snapshots:
'@sideway/pinpoint@2.0.0': {}
- '@simplewebauthn/server@10.0.1(encoding@0.1.13)':
+ '@simplewebauthn/server@12.0.0(encoding@0.1.13)':
dependencies:
'@hexagon/base64': 1.1.27
'@levischuck/tiny-cbor': 0.2.2
@@ -13933,23 +13930,21 @@ snapshots:
'@peculiar/asn1-rsa': 2.3.8
'@peculiar/asn1-schema': 2.3.8
'@peculiar/asn1-x509': 2.3.8
- '@simplewebauthn/types': 10.0.0
+ '@simplewebauthn/types': 12.0.0
cross-fetch: 4.0.0(encoding@0.1.13)
transitivePeerDependencies:
- encoding
- '@simplewebauthn/types@10.0.0': {}
-
- '@simplewebauthn/types@11.0.0': {}
+ '@simplewebauthn/types@12.0.0': {}
'@sinclair/typebox@0.27.8': {}
- '@sindresorhus/is@4.6.0': {}
-
'@sindresorhus/is@5.3.0': {}
'@sindresorhus/is@7.0.1': {}
+ '@sindresorhus/merge-streams@4.0.0': {}
+
'@sinonjs/commons@2.0.0':
dependencies:
type-detect: 4.0.8
@@ -13958,13 +13953,25 @@ snapshots:
dependencies:
type-detect: 4.0.8
+ '@sinonjs/commons@3.0.1':
+ dependencies:
+ type-detect: 4.0.8
+
'@sinonjs/fake-timers@10.3.0':
dependencies:
'@sinonjs/commons': 3.0.0
'@sinonjs/fake-timers@11.2.2':
dependencies:
- '@sinonjs/commons': 3.0.0
+ '@sinonjs/commons': 3.0.1
+
+ '@sinonjs/fake-timers@11.3.1':
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+
+ '@sinonjs/fake-timers@13.0.5':
+ dependencies:
+ '@sinonjs/commons': 3.0.1
'@sinonjs/samsam@8.0.0':
dependencies:
@@ -13972,174 +13979,175 @@ snapshots:
lodash.get: 4.4.2
type-detect: 4.0.8
- '@sinonjs/text-encoding@0.7.2': {}
+ '@sinonjs/text-encoding@0.7.3': {}
'@smithy/abort-controller@2.2.0':
dependencies:
'@smithy/types': 2.12.0
tslib: 2.7.0
- '@smithy/abort-controller@3.1.1':
+ '@smithy/abort-controller@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/chunked-blob-reader-native@3.0.0':
+ '@smithy/chunked-blob-reader-native@4.0.0':
dependencies:
- '@smithy/util-base64': 3.0.0
+ '@smithy/util-base64': 4.0.0
tslib: 2.7.0
- '@smithy/chunked-blob-reader@3.0.0':
+ '@smithy/chunked-blob-reader@5.0.0':
dependencies:
tslib: 2.7.0
- '@smithy/config-resolver@3.0.5':
+ '@smithy/config-resolver@4.1.0':
dependencies:
- '@smithy/node-config-provider': 3.1.4
- '@smithy/types': 3.3.0
- '@smithy/util-config-provider': 3.0.0
- '@smithy/util-middleware': 3.0.3
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/types': 4.2.0
+ '@smithy/util-config-provider': 4.0.0
+ '@smithy/util-middleware': 4.0.2
tslib: 2.7.0
- '@smithy/core@2.3.1':
+ '@smithy/core@3.2.0':
dependencies:
- '@smithy/middleware-endpoint': 3.1.0
- '@smithy/middleware-retry': 3.0.13
- '@smithy/middleware-serde': 3.0.3
- '@smithy/protocol-http': 4.1.0
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- '@smithy/util-middleware': 3.0.3
+ '@smithy/middleware-serde': 4.0.3
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-body-length-browser': 4.0.0
+ '@smithy/util-middleware': 4.0.2
+ '@smithy/util-stream': 4.2.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
- '@smithy/credential-provider-imds@3.2.0':
+ '@smithy/credential-provider-imds@4.0.2':
dependencies:
- '@smithy/node-config-provider': 3.1.4
- '@smithy/property-provider': 3.1.3
- '@smithy/types': 3.3.0
- '@smithy/url-parser': 3.0.3
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/property-provider': 4.0.2
+ '@smithy/types': 4.2.0
+ '@smithy/url-parser': 4.0.2
tslib: 2.7.0
- '@smithy/eventstream-codec@3.1.2':
+ '@smithy/eventstream-codec@4.0.2':
dependencies:
'@aws-crypto/crc32': 5.2.0
- '@smithy/types': 3.3.0
- '@smithy/util-hex-encoding': 3.0.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-hex-encoding': 4.0.0
tslib: 2.7.0
- '@smithy/eventstream-serde-browser@3.0.5':
+ '@smithy/eventstream-serde-browser@4.0.2':
dependencies:
- '@smithy/eventstream-serde-universal': 3.0.4
- '@smithy/types': 3.3.0
+ '@smithy/eventstream-serde-universal': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/eventstream-serde-config-resolver@3.0.3':
+ '@smithy/eventstream-serde-config-resolver@4.1.0':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/eventstream-serde-node@3.0.4':
+ '@smithy/eventstream-serde-node@4.0.2':
dependencies:
- '@smithy/eventstream-serde-universal': 3.0.4
- '@smithy/types': 3.3.0
+ '@smithy/eventstream-serde-universal': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/eventstream-serde-universal@3.0.4':
+ '@smithy/eventstream-serde-universal@4.0.2':
dependencies:
- '@smithy/eventstream-codec': 3.1.2
- '@smithy/types': 3.3.0
+ '@smithy/eventstream-codec': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/fetch-http-handler@3.2.4':
+ '@smithy/fetch-http-handler@5.0.2':
dependencies:
- '@smithy/protocol-http': 4.1.0
- '@smithy/querystring-builder': 3.0.3
- '@smithy/types': 3.3.0
- '@smithy/util-base64': 3.0.0
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/querystring-builder': 4.0.2
+ '@smithy/types': 4.2.0
+ '@smithy/util-base64': 4.0.0
tslib: 2.7.0
- '@smithy/hash-blob-browser@3.1.2':
+ '@smithy/hash-blob-browser@4.0.2':
dependencies:
- '@smithy/chunked-blob-reader': 3.0.0
- '@smithy/chunked-blob-reader-native': 3.0.0
- '@smithy/types': 3.3.0
+ '@smithy/chunked-blob-reader': 5.0.0
+ '@smithy/chunked-blob-reader-native': 4.0.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/hash-node@3.0.3':
+ '@smithy/hash-node@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
- '@smithy/util-buffer-from': 3.0.0
- '@smithy/util-utf8': 3.0.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-buffer-from': 4.0.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
- '@smithy/hash-stream-node@3.1.2':
+ '@smithy/hash-stream-node@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
- '@smithy/util-utf8': 3.0.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
- '@smithy/invalid-dependency@3.0.3':
+ '@smithy/invalid-dependency@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
'@smithy/is-array-buffer@2.0.0':
dependencies:
tslib: 2.7.0
- '@smithy/is-array-buffer@3.0.0':
+ '@smithy/is-array-buffer@4.0.0':
dependencies:
tslib: 2.7.0
- '@smithy/md5-js@3.0.3':
+ '@smithy/md5-js@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
- '@smithy/util-utf8': 3.0.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
- '@smithy/middleware-content-length@3.0.5':
+ '@smithy/middleware-content-length@4.0.2':
dependencies:
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/middleware-endpoint@3.1.0':
+ '@smithy/middleware-endpoint@4.1.0':
dependencies:
- '@smithy/middleware-serde': 3.0.3
- '@smithy/node-config-provider': 3.1.4
- '@smithy/shared-ini-file-loader': 3.1.4
- '@smithy/types': 3.3.0
- '@smithy/url-parser': 3.0.3
- '@smithy/util-middleware': 3.0.3
+ '@smithy/core': 3.2.0
+ '@smithy/middleware-serde': 4.0.3
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/shared-ini-file-loader': 4.0.2
+ '@smithy/types': 4.2.0
+ '@smithy/url-parser': 4.0.2
+ '@smithy/util-middleware': 4.0.2
tslib: 2.7.0
- '@smithy/middleware-retry@3.0.13':
+ '@smithy/middleware-retry@4.1.0':
dependencies:
- '@smithy/node-config-provider': 3.1.4
- '@smithy/protocol-http': 4.1.0
- '@smithy/service-error-classification': 3.0.3
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
- '@smithy/util-middleware': 3.0.3
- '@smithy/util-retry': 3.0.3
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/service-error-classification': 4.0.2
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-middleware': 4.0.2
+ '@smithy/util-retry': 4.0.2
tslib: 2.7.0
uuid: 9.0.1
- '@smithy/middleware-serde@3.0.3':
+ '@smithy/middleware-serde@4.0.3':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/middleware-stack@3.0.3':
+ '@smithy/middleware-stack@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/node-config-provider@3.1.4':
+ '@smithy/node-config-provider@4.0.2':
dependencies:
- '@smithy/property-provider': 3.1.3
- '@smithy/shared-ini-file-loader': 3.1.4
- '@smithy/types': 3.3.0
+ '@smithy/property-provider': 4.0.2
+ '@smithy/shared-ini-file-loader': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
'@smithy/node-http-handler@2.5.0':
@@ -14150,17 +14158,17 @@ snapshots:
'@smithy/types': 2.12.0
tslib: 2.6.2
- '@smithy/node-http-handler@3.1.4':
+ '@smithy/node-http-handler@4.0.4':
dependencies:
- '@smithy/abort-controller': 3.1.1
- '@smithy/protocol-http': 4.1.0
- '@smithy/querystring-builder': 3.0.3
- '@smithy/types': 3.3.0
+ '@smithy/abort-controller': 4.0.2
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/querystring-builder': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/property-provider@3.1.3':
+ '@smithy/property-provider@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
'@smithy/protocol-http@3.3.0':
@@ -14168,9 +14176,9 @@ snapshots:
'@smithy/types': 2.12.0
tslib: 2.7.0
- '@smithy/protocol-http@4.1.0':
+ '@smithy/protocol-http@5.1.0':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
'@smithy/querystring-builder@2.2.0':
@@ -14179,71 +14187,72 @@ snapshots:
'@smithy/util-uri-escape': 2.2.0
tslib: 2.7.0
- '@smithy/querystring-builder@3.0.3':
+ '@smithy/querystring-builder@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
- '@smithy/util-uri-escape': 3.0.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-uri-escape': 4.0.0
tslib: 2.7.0
- '@smithy/querystring-parser@3.0.3':
+ '@smithy/querystring-parser@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/service-error-classification@3.0.3':
+ '@smithy/service-error-classification@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
- '@smithy/shared-ini-file-loader@3.1.4':
+ '@smithy/shared-ini-file-loader@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/signature-v4@4.1.0':
+ '@smithy/signature-v4@5.0.2':
dependencies:
- '@smithy/is-array-buffer': 3.0.0
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
- '@smithy/util-hex-encoding': 3.0.0
- '@smithy/util-middleware': 3.0.3
- '@smithy/util-uri-escape': 3.0.0
- '@smithy/util-utf8': 3.0.0
+ '@smithy/is-array-buffer': 4.0.0
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-hex-encoding': 4.0.0
+ '@smithy/util-middleware': 4.0.2
+ '@smithy/util-uri-escape': 4.0.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
- '@smithy/smithy-client@3.1.11':
+ '@smithy/smithy-client@4.2.0':
dependencies:
- '@smithy/middleware-endpoint': 3.1.0
- '@smithy/middleware-stack': 3.0.3
- '@smithy/protocol-http': 4.1.0
- '@smithy/types': 3.3.0
- '@smithy/util-stream': 3.1.3
+ '@smithy/core': 3.2.0
+ '@smithy/middleware-endpoint': 4.1.0
+ '@smithy/middleware-stack': 4.0.2
+ '@smithy/protocol-http': 5.1.0
+ '@smithy/types': 4.2.0
+ '@smithy/util-stream': 4.2.0
tslib: 2.7.0
'@smithy/types@2.12.0':
dependencies:
tslib: 2.7.0
- '@smithy/types@3.3.0':
+ '@smithy/types@4.2.0':
dependencies:
tslib: 2.7.0
- '@smithy/url-parser@3.0.3':
+ '@smithy/url-parser@4.0.2':
dependencies:
- '@smithy/querystring-parser': 3.0.3
- '@smithy/types': 3.3.0
+ '@smithy/querystring-parser': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/util-base64@3.0.0':
+ '@smithy/util-base64@4.0.0':
dependencies:
- '@smithy/util-buffer-from': 3.0.0
- '@smithy/util-utf8': 3.0.0
+ '@smithy/util-buffer-from': 4.0.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
- '@smithy/util-body-length-browser@3.0.0':
+ '@smithy/util-body-length-browser@4.0.0':
dependencies:
tslib: 2.7.0
- '@smithy/util-body-length-node@3.0.0':
+ '@smithy/util-body-length-node@4.0.0':
dependencies:
tslib: 2.7.0
@@ -14252,70 +14261,70 @@ snapshots:
'@smithy/is-array-buffer': 2.0.0
tslib: 2.7.0
- '@smithy/util-buffer-from@3.0.0':
+ '@smithy/util-buffer-from@4.0.0':
dependencies:
- '@smithy/is-array-buffer': 3.0.0
+ '@smithy/is-array-buffer': 4.0.0
tslib: 2.7.0
- '@smithy/util-config-provider@3.0.0':
+ '@smithy/util-config-provider@4.0.0':
dependencies:
tslib: 2.7.0
- '@smithy/util-defaults-mode-browser@3.0.13':
+ '@smithy/util-defaults-mode-browser@4.0.8':
dependencies:
- '@smithy/property-provider': 3.1.3
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
+ '@smithy/property-provider': 4.0.2
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
bowser: 2.11.0
tslib: 2.7.0
- '@smithy/util-defaults-mode-node@3.0.13':
+ '@smithy/util-defaults-mode-node@4.0.8':
dependencies:
- '@smithy/config-resolver': 3.0.5
- '@smithy/credential-provider-imds': 3.2.0
- '@smithy/node-config-provider': 3.1.4
- '@smithy/property-provider': 3.1.3
- '@smithy/smithy-client': 3.1.11
- '@smithy/types': 3.3.0
+ '@smithy/config-resolver': 4.1.0
+ '@smithy/credential-provider-imds': 4.0.2
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/property-provider': 4.0.2
+ '@smithy/smithy-client': 4.2.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/util-endpoints@2.0.5':
+ '@smithy/util-endpoints@3.0.2':
dependencies:
- '@smithy/node-config-provider': 3.1.4
- '@smithy/types': 3.3.0
+ '@smithy/node-config-provider': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/util-hex-encoding@3.0.0':
+ '@smithy/util-hex-encoding@4.0.0':
dependencies:
tslib: 2.7.0
- '@smithy/util-middleware@3.0.3':
+ '@smithy/util-middleware@4.0.2':
dependencies:
- '@smithy/types': 3.3.0
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/util-retry@3.0.3':
+ '@smithy/util-retry@4.0.2':
dependencies:
- '@smithy/service-error-classification': 3.0.3
- '@smithy/types': 3.3.0
+ '@smithy/service-error-classification': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
- '@smithy/util-stream@3.1.3':
+ '@smithy/util-stream@4.2.0':
dependencies:
- '@smithy/fetch-http-handler': 3.2.4
- '@smithy/node-http-handler': 3.1.4
- '@smithy/types': 3.3.0
- '@smithy/util-base64': 3.0.0
- '@smithy/util-buffer-from': 3.0.0
- '@smithy/util-hex-encoding': 3.0.0
- '@smithy/util-utf8': 3.0.0
+ '@smithy/fetch-http-handler': 5.0.2
+ '@smithy/node-http-handler': 4.0.4
+ '@smithy/types': 4.2.0
+ '@smithy/util-base64': 4.0.0
+ '@smithy/util-buffer-from': 4.0.0
+ '@smithy/util-hex-encoding': 4.0.0
+ '@smithy/util-utf8': 4.0.0
tslib: 2.7.0
'@smithy/util-uri-escape@2.2.0':
dependencies:
tslib: 2.7.0
- '@smithy/util-uri-escape@3.0.0':
+ '@smithy/util-uri-escape@4.0.0':
dependencies:
tslib: 2.7.0
@@ -14324,334 +14333,334 @@ snapshots:
'@smithy/util-buffer-from': 2.0.0
tslib: 2.7.0
- '@smithy/util-utf8@3.0.0':
+ '@smithy/util-utf8@4.0.0':
dependencies:
- '@smithy/util-buffer-from': 3.0.0
+ '@smithy/util-buffer-from': 4.0.0
tslib: 2.7.0
- '@smithy/util-waiter@3.1.2':
+ '@smithy/util-waiter@4.0.3':
dependencies:
- '@smithy/abort-controller': 3.1.1
- '@smithy/types': 3.3.0
+ '@smithy/abort-controller': 4.0.2
+ '@smithy/types': 4.2.0
tslib: 2.7.0
'@sqltools/formatter@1.2.5': {}
- '@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-actions@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
'@storybook/global': 5.0.0
'@types/uuid': 9.0.8
dequal: 2.0.3
polished: 4.2.2
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
uuid: 9.0.1
- '@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-backgrounds@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
'@storybook/global': 5.0.0
memoizerific: 1.11.3
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
- '@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-controls@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
'@storybook/global': 5.0.0
dequal: 2.0.3
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
- '@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-docs@8.6.7(@types/react@18.0.28)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- '@mdx-js/react': 3.0.1(@types/react@18.0.28)(react@18.3.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)
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ '@mdx-js/react': 3.0.1(@types/react@18.0.28)(react@19.0.0)
+ '@storybook/blocks': 8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/csf-plugin': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/react-dom-shim': 8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
transitivePeerDependencies:
- '@types/react'
- '@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))':
+ '@storybook/addon-essentials@8.6.7(@types/react@18.0.28)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- '@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)
+ '@storybook/addon-actions': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/addon-backgrounds': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/addon-controls': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/addon-docs': 8.6.7(@types/react@18.0.28)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/addon-highlight': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/addon-measure': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/addon-outline': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/addon-toolbars': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/addon-viewport': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
transitivePeerDependencies:
- '@types/react'
- '@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-highlight@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
'@storybook/global': 5.0.0
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@storybook/addon-interactions@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
+ '@storybook/addon-interactions@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
'@storybook/global': 5.0.0
- '@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))
+ '@storybook/instrumenter': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/test': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
polished: 4.2.2
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
- '@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))':
+ '@storybook/addon-links@8.6.7(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- '@storybook/csf': 0.1.11
'@storybook/global': 5.0.0
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
optionalDependencies:
- react: 18.3.1
+ react: 19.0.0
- '@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))':
+ '@storybook/addon-mdx-gfm@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
remark-gfm: 4.0.0
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
transitivePeerDependencies:
- supports-color
- '@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-measure@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
'@storybook/global': 5.0.0
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
tiny-invariant: 1.3.3
- '@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-outline@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
'@storybook/global': 5.0.0
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
- '@storybook/addon-storysource@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
+ '@storybook/addon-storysource@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- '@storybook/source-loader': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))
+ '@storybook/source-loader': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
estraverse: 5.3.0
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
tiny-invariant: 1.3.3
- '@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-toolbars@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@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/addon-viewport@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
memoizerific: 1.11.3
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@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/blocks@8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- '@storybook/csf': 0.1.11
- '@storybook/icons': 1.2.12(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/icons': 1.2.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
optionalDependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.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/builder-vite@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))':
dependencies:
- '@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/csf-plugin': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
browser-assert: 1.2.1
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
- vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0)
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
- '@storybook/components@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
+ '@storybook/components@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@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/core-events@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@storybook/core@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)':
+ '@storybook/core@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(utf-8-validate@6.0.5)':
dependencies:
- '@storybook/csf': 0.1.11
+ '@storybook/theming': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
better-opn: 3.0.2
browser-assert: 1.2.1
- esbuild: 0.24.0
- esbuild-register: 3.5.0(esbuild@0.24.0)
+ esbuild: 0.25.1
+ esbuild-register: 3.5.0(esbuild@0.25.1)
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)
+ ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)
optionalDependencies:
- prettier: 3.3.3
+ prettier: 3.5.3
transitivePeerDependencies:
- bufferutil
+ - storybook
- supports-color
- utf-8-validate
- '@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/csf-plugin@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
unplugin: 1.4.0
- '@storybook/csf@0.1.11':
- dependencies:
- type-fest: 2.19.0
-
'@storybook/global@5.0.0': {}
- '@storybook/icons@1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@storybook/icons@1.2.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
- '@storybook/instrumenter@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
+ '@storybook/instrumenter@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
'@storybook/global': 5.0.0
'@vitest/utils': 2.1.2
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@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/manager-api@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@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/preview-api@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@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/react-dom-shim@8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- react: 18.3.1
- react-dom: 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: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@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))':
+ '@storybook/react-vite@8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.36.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))':
dependencies:
- '@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)
+ '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.8.2)(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
+ '@rollup/pluginutils': 5.1.4(rollup@4.36.0)
+ '@storybook/builder-vite': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
+ '@storybook/react': 8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)
find-up: 5.0.0
- magic-string: 0.30.11
- react: 18.3.1
+ magic-string: 0.30.17
+ react: 19.0.0
react-docgen: 7.0.1
- react-dom: 18.3.1(react@18.3.1)
+ react-dom: 19.0.0(react@19.0.0)
resolve: 1.22.8
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
tsconfig-paths: 4.2.0
- vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0)
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
+ optionalDependencies:
+ '@storybook/test': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
transitivePeerDependencies:
- - '@storybook/test'
- rollup
- supports-color
- typescript
- '@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)':
+ '@storybook/react@8.6.7(@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(typescript@5.8.2)':
dependencies:
- '@storybook/components': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))
+ '@storybook/components': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/global': 5.0.0
- '@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)
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ '@storybook/manager-api': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/preview-api': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/react-dom-shim': 8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/theming': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
optionalDependencies:
- '@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/test': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ typescript: 5.8.2
- '@storybook/source-loader@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
+ '@storybook/source-loader@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- '@storybook/csf': 0.1.11
es-toolkit: 1.27.0
estraverse: 5.3.0
- prettier: 3.3.3
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ prettier: 3.5.3
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@storybook/test@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
+ '@storybook/test@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- '@storybook/csf': 0.1.11
'@storybook/global': 5.0.0
- '@storybook/instrumenter': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))
+ '@storybook/instrumenter': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@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.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@storybook/theming@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
+ '@storybook/theming@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@storybook/types@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
+ '@storybook/types@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))':
dependencies:
- storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
- '@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))':
+ '@storybook/vue3-vite@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))':
dependencies:
- '@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))
+ '@storybook/builder-vite': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
+ '@storybook/vue3': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vue@3.5.13(typescript@5.8.2))
find-package-json: 1.2.0
- 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))
+ magic-string: 0.30.17
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
+ typescript: 5.8.2
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
+ vue-component-meta: 2.0.16(typescript@5.8.2)
+ vue-docgen-api: 4.75.1(vue@3.5.13(typescript@5.8.2))
transitivePeerDependencies:
- vue
- '@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))':
+ '@storybook/vue3@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(vue@3.5.13(typescript@5.8.2))':
dependencies:
- '@storybook/components': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))
+ '@storybook/components': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
'@storybook/global': 5.0.0
- '@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)
+ '@storybook/manager-api': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/preview-api': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/theming': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@vue/compiler-core': 3.5.12
+ storybook: 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)
ts-dedent: 2.2.0
type-fest: 2.19.0
- vue: 3.5.12(typescript@5.6.3)
+ vue: 3.5.13(typescript@5.8.2)
vue-component-type-helpers: 2.2.8
- '@swc/cli@0.3.12(@swc/core@1.9.2)(chokidar@3.5.3)':
+ '@stylistic/eslint-plugin@4.2.0(eslint@9.22.0)(typescript@5.8.2)':
+ dependencies:
+ '@typescript-eslint/utils': 8.28.0(eslint@9.22.0)(typescript@5.8.2)
+ eslint: 9.22.0
+ eslint-visitor-keys: 4.2.0
+ espree: 10.3.0
+ estraverse: 5.3.0
+ picomatch: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ '@swc/cli@0.6.0(@swc/core@1.11.11)(chokidar@4.0.3)':
dependencies:
- '@mole-inc/bin-wrapper': 8.0.1
- '@swc/core': 1.9.2
+ '@swc/core': 1.11.11
'@swc/counter': 0.1.3
+ '@xhmikosr/bin-wrapper': 13.0.5
commander: 8.3.0
- fast-glob: 3.3.2
- minimatch: 9.0.3
+ fast-glob: 3.3.3
+ minimatch: 9.0.4
piscina: 4.4.0
- semver: 7.6.0
+ semver: 7.6.3
slash: 3.0.0
source-map: 0.7.4
optionalDependencies:
- chokidar: 3.5.3
+ chokidar: 4.0.3
'@swc/core-android-arm64@1.3.11':
dependencies:
'@swc/wasm': 1.2.130
optional: true
- '@swc/core-darwin-arm64@1.3.56':
- optional: true
-
- '@swc/core-darwin-arm64@1.9.2':
- optional: true
-
- '@swc/core-darwin-x64@1.3.56':
+ '@swc/core-darwin-arm64@1.11.11':
optional: true
- '@swc/core-darwin-x64@1.9.2':
+ '@swc/core-darwin-x64@1.11.11':
optional: true
'@swc/core-freebsd-x64@1.3.11':
@@ -14659,80 +14668,56 @@ snapshots:
'@swc/wasm': 1.2.130
optional: true
- '@swc/core-linux-arm-gnueabihf@1.3.56':
- optional: true
-
- '@swc/core-linux-arm-gnueabihf@1.9.2':
- optional: true
-
- '@swc/core-linux-arm64-gnu@1.3.56':
+ '@swc/core-linux-arm-gnueabihf@1.11.11':
optional: true
- '@swc/core-linux-arm64-gnu@1.9.2':
+ '@swc/core-linux-arm64-gnu@1.11.11':
optional: true
- '@swc/core-linux-arm64-musl@1.3.56':
+ '@swc/core-linux-arm64-musl@1.11.11':
optional: true
- '@swc/core-linux-arm64-musl@1.9.2':
+ '@swc/core-linux-x64-gnu@1.11.11':
optional: true
- '@swc/core-linux-x64-gnu@1.3.56':
+ '@swc/core-linux-x64-musl@1.11.11':
optional: true
- '@swc/core-linux-x64-gnu@1.9.2':
+ '@swc/core-win32-arm64-msvc@1.11.11':
optional: true
- '@swc/core-linux-x64-musl@1.3.56':
+ '@swc/core-win32-ia32-msvc@1.11.11':
optional: true
- '@swc/core-linux-x64-musl@1.9.2':
+ '@swc/core-win32-x64-msvc@1.11.11':
optional: true
- '@swc/core-win32-arm64-msvc@1.3.56':
- optional: true
-
- '@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.9.2':
- optional: true
-
- '@swc/core-win32-x64-msvc@1.3.56':
- optional: true
-
- '@swc/core-win32-x64-msvc@1.9.2':
- optional: true
-
- '@swc/core@1.9.2':
+ '@swc/core@1.11.11':
dependencies:
'@swc/counter': 0.1.3
- '@swc/types': 0.1.17
+ '@swc/types': 0.1.19
optionalDependencies:
- '@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/core-darwin-arm64': 1.11.11
+ '@swc/core-darwin-x64': 1.11.11
+ '@swc/core-linux-arm-gnueabihf': 1.11.11
+ '@swc/core-linux-arm64-gnu': 1.11.11
+ '@swc/core-linux-arm64-musl': 1.11.11
+ '@swc/core-linux-x64-gnu': 1.11.11
+ '@swc/core-linux-x64-musl': 1.11.11
+ '@swc/core-win32-arm64-msvc': 1.11.11
+ '@swc/core-win32-ia32-msvc': 1.11.11
+ '@swc/core-win32-x64-msvc': 1.11.11
'@swc/counter@0.1.3': {}
- '@swc/jest@0.2.37(@swc/core@1.9.2)':
+ '@swc/jest@0.2.37(@swc/core@1.11.11)':
dependencies:
'@jest/create-cache-key-function': 29.7.0
- '@swc/core': 1.9.2
+ '@swc/core': 1.11.11
'@swc/counter': 0.1.3
jsonc-parser: 3.2.0
- '@swc/types@0.1.17':
+ '@swc/types@0.1.19':
dependencies:
'@swc/counter': 0.1.3
@@ -14745,10 +14730,6 @@ snapshots:
stringz: 2.1.0
uuid: 9.0.1
- '@szmarczak/http-timer@4.0.6':
- dependencies:
- defer-to-connect: 2.0.1
-
'@szmarczak/http-timer@5.0.1':
dependencies:
defer-to-connect: 2.0.1
@@ -14789,14 +14770,14 @@ snapshots:
dependencies:
'@testing-library/dom': 10.4.0
- '@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))':
+ '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.5.13)(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))':
dependencies:
'@babel/runtime': 7.23.4
'@testing-library/dom': 9.3.4
- '@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)
+ '@vue/test-utils': 2.4.1(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))
+ vue: 3.5.13(typescript@5.8.2)
optionalDependencies:
- '@vue/compiler-sfc': 3.5.12
+ '@vue/compiler-sfc': 3.5.13
transitivePeerDependencies:
- '@vue/server-renderer'
@@ -14822,7 +14803,7 @@ snapshots:
'@types/accepts@1.3.7':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/archiver@6.0.3':
dependencies:
@@ -14858,18 +14839,11 @@ snapshots:
'@types/body-parser@1.19.5':
dependencies:
'@types/connect': 3.4.35
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/braces@3.0.1': {}
- '@types/cacheable-request@6.0.3':
- dependencies:
- '@types/http-cache-semantics': 4.0.4
- '@types/keyv': 3.1.4
- '@types/node': 22.9.0
- '@types/responselike': 1.0.0
-
- '@types/canvas-confetti@1.6.4': {}
+ '@types/canvas-confetti@1.9.0': {}
'@types/color-convert@2.0.4':
dependencies:
@@ -14879,11 +14853,11 @@ snapshots:
'@types/connect@3.4.35':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/connect@3.4.36':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/content-disposition@0.5.8': {}
@@ -14897,6 +14871,8 @@ snapshots:
'@types/disposable-email-domains@1.0.2': {}
+ '@types/dlv@1.1.5': {}
+
'@types/doctrine@0.0.9': {}
'@types/eslint@7.29.0':
@@ -14908,7 +14884,7 @@ snapshots:
'@types/express-serve-static-core@4.17.33':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
@@ -14921,20 +14897,17 @@ snapshots:
'@types/fluent-ffmpeg@2.1.27':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/form-data@2.5.0':
dependencies:
- form-data: 4.0.1
-
- '@types/glob@7.2.0':
- dependencies:
- '@types/minimatch': 5.1.2
- '@types/node': 22.9.0
+ form-data: 4.0.2
'@types/graceful-fs@4.1.6':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
+
+ '@types/hammerjs@2.0.46': {}
'@types/hast@3.0.4':
dependencies:
@@ -14946,7 +14919,7 @@ snapshots:
'@types/http-link-header@1.0.7':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/istanbul-lib-coverage@2.0.4': {}
@@ -14972,27 +14945,21 @@ snapshots:
'@types/jsdom@21.1.7':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/tough-cookie': 4.0.2
parse5: 7.2.1
- '@types/json-schema@7.0.12': {}
-
'@types/json-schema@7.0.15': {}
'@types/json5@0.0.29': {}
'@types/jsonld@1.5.15': {}
- '@types/jsrsasign@10.5.14': {}
+ '@types/jsrsasign@10.5.15': {}
'@types/katex@0.16.7': {}
- '@types/keyv@3.1.4':
- dependencies:
- '@types/node': 22.9.0
-
- '@types/matter-js@0.19.7': {}
+ '@types/matter-js@0.19.8': {}
'@types/mdast@4.0.3':
dependencies:
@@ -15008,23 +14975,29 @@ snapshots:
'@types/mime@3.0.1': {}
- '@types/minimatch@5.1.2': {}
-
'@types/minimist@1.2.2': {}
'@types/ms@0.7.34': {}
'@types/mysql@2.15.26':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
+
+ '@types/node@22.13.10':
+ dependencies:
+ undici-types: 6.20.0
+
+ '@types/node@22.13.11':
+ dependencies:
+ undici-types: 6.20.0
- '@types/node@22.9.0':
+ '@types/node@22.13.9':
dependencies:
- undici-types: 6.19.8
+ undici-types: 6.20.0
- '@types/nodemailer@6.4.16':
+ '@types/nodemailer@6.4.17':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/normalize-package-data@2.4.1': {}
@@ -15035,15 +15008,15 @@ snapshots:
'@types/oauth2orize@1.11.5':
dependencies:
'@types/express': 4.17.17
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/oauth@0.9.5':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/oauth@0.9.6':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/object-assign-deep@0.4.3': {}
@@ -15051,17 +15024,17 @@ snapshots:
'@types/pg-pool@2.0.6':
dependencies:
- '@types/pg': 8.11.10
+ '@types/pg': 8.11.11
- '@types/pg@8.11.10':
+ '@types/pg@8.11.11':
dependencies:
- '@types/node': 22.9.0
- pg-protocol: 1.6.1
+ '@types/node': 22.13.11
+ pg-protocol: 1.7.0
pg-types: 4.0.1
'@types/pg@8.6.1':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
pg-protocol: 1.7.0
pg-types: 2.2.0
@@ -15069,7 +15042,7 @@ snapshots:
'@types/proxy-addr@2.0.3':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/psl@1.1.3': {}
@@ -15079,7 +15052,7 @@ snapshots:
'@types/qrcode@1.5.5':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/qs@6.9.7': {}
@@ -15097,16 +15070,12 @@ snapshots:
'@types/readdir-glob@1.1.1':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/rename@1.0.7': {}
'@types/resolve@1.20.3': {}
- '@types/responselike@1.0.0':
- dependencies:
- '@types/node': 22.9.0
-
'@types/sanitize-html@2.13.0':
dependencies:
htmlparser2: 8.0.1
@@ -15120,17 +15089,15 @@ snapshots:
'@types/serve-static@1.15.1':
dependencies:
'@types/mime': 3.0.1
- '@types/node': 22.9.0
-
- '@types/serviceworker@0.0.67': {}
+ '@types/node': 22.13.11
- '@types/shimmer@1.0.5': {}
+ '@types/serviceworker@0.0.74': {}
'@types/shimmer@1.2.0': {}
'@types/simple-oauth2@5.0.7': {}
- '@types/sinon@10.0.13':
+ '@types/sinon@17.0.4':
dependencies:
'@types/sinonjs__fake-timers': 8.1.5
@@ -15148,7 +15115,7 @@ snapshots:
'@types/tedious@4.0.14':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/throttle-debounce@5.0.2': {}
@@ -15162,25 +15129,23 @@ snapshots:
'@types/unist@3.0.2': {}
- '@types/uuid@10.0.0': {}
-
'@types/uuid@9.0.8': {}
'@types/vary@1.1.3':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/web-push@3.6.4':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
- '@types/ws@8.5.11':
+ '@types/ws@8.18.0':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
- '@types/ws@8.5.13':
+ '@types/ws@8.5.11':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
'@types/yargs-parser@21.0.0': {}
@@ -15190,7 +15155,7 @@ snapshots:
'@types/yauzl@2.10.0':
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
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)':
@@ -15213,41 +15178,54 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@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)':
+ '@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.14.0)(typescript@5.8.2))(eslint@9.14.0)(typescript@5.8.2)':
dependencies:
- '@eslint-community/regexpp': 4.6.2
- '@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.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
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 8.26.0(eslint@9.14.0)(typescript@5.8.2)
+ '@typescript-eslint/scope-manager': 8.26.0
+ '@typescript-eslint/type-utils': 8.26.0(eslint@9.14.0)(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.26.0(eslint@9.14.0)(typescript@5.8.2)
+ '@typescript-eslint/visitor-keys': 8.26.0
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.6.3)
- optionalDependencies:
- typescript: 5.6.3
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
- '@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/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)':
dependencies:
- '@eslint-community/regexpp': 4.11.0
- '@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.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.14.0
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 8.26.0(eslint@9.22.0)(typescript@5.8.2)
+ '@typescript-eslint/scope-manager': 8.26.0
+ '@typescript-eslint/type-utils': 8.26.0(eslint@9.22.0)(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.26.0(eslint@9.22.0)(typescript@5.8.2)
+ '@typescript-eslint/visitor-keys': 8.26.0
+ eslint: 9.22.0
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
- ts-api-utils: 1.3.0(typescript@5.6.3)
- optionalDependencies:
- typescript: 5.6.3
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/eslint-plugin@8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0)(typescript@5.8.2)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 8.27.0(eslint@9.22.0)(typescript@5.8.2)
+ '@typescript-eslint/scope-manager': 8.27.0
+ '@typescript-eslint/type-utils': 8.27.0(eslint@9.22.0)(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.27.0(eslint@9.22.0)(typescript@5.8.2)
+ '@typescript-eslint/visitor-keys': 8.27.0
+ eslint: 9.22.0
+ graphemer: 1.4.0
+ ignore: 5.3.1
+ natural-compare: 1.4.0
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
@@ -15264,29 +15242,39 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@7.1.0(eslint@9.14.0)(typescript@5.6.3)':
+ '@typescript-eslint/parser@8.26.0(eslint@9.14.0)(typescript@5.8.2)':
dependencies:
- '@typescript-eslint/scope-manager': 7.1.0
- '@typescript-eslint/types': 7.1.0
- '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.6.3)
- '@typescript-eslint/visitor-keys': 7.1.0
- debug: 4.3.4
+ '@typescript-eslint/scope-manager': 8.26.0
+ '@typescript-eslint/types': 8.26.0
+ '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
+ '@typescript-eslint/visitor-keys': 8.26.0
+ debug: 4.3.7(supports-color@8.1.1)
eslint: 9.14.0
- optionalDependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3)':
+ '@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.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.3)
- '@typescript-eslint/visitor-keys': 7.17.0
- debug: 4.3.5(supports-color@5.5.0)
- eslint: 9.14.0
- optionalDependencies:
- typescript: 5.6.3
+ '@typescript-eslint/scope-manager': 8.26.0
+ '@typescript-eslint/types': 8.26.0
+ '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
+ '@typescript-eslint/visitor-keys': 8.26.0
+ debug: 4.3.7(supports-color@8.1.1)
+ eslint: 9.22.0
+ typescript: 5.8.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.27.0
+ '@typescript-eslint/types': 8.27.0
+ '@typescript-eslint/typescript-estree': 8.27.0(typescript@5.8.2)
+ '@typescript-eslint/visitor-keys': 8.27.0
+ debug: 4.3.7(supports-color@8.1.1)
+ eslint: 9.22.0
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
@@ -15295,15 +15283,20 @@ snapshots:
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0
- '@typescript-eslint/scope-manager@7.1.0':
+ '@typescript-eslint/scope-manager@8.26.0':
dependencies:
- '@typescript-eslint/types': 7.1.0
- '@typescript-eslint/visitor-keys': 7.1.0
+ '@typescript-eslint/types': 8.26.0
+ '@typescript-eslint/visitor-keys': 8.26.0
- '@typescript-eslint/scope-manager@7.17.0':
+ '@typescript-eslint/scope-manager@8.27.0':
dependencies:
- '@typescript-eslint/types': 7.17.0
- '@typescript-eslint/visitor-keys': 7.17.0
+ '@typescript-eslint/types': 8.27.0
+ '@typescript-eslint/visitor-keys': 8.27.0
+
+ '@typescript-eslint/scope-manager@8.28.0':
+ dependencies:
+ '@typescript-eslint/types': 8.28.0
+ '@typescript-eslint/visitor-keys': 8.28.0
'@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.1.6)':
dependencies:
@@ -15317,35 +15310,46 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/type-utils@7.1.0(eslint@9.14.0)(typescript@5.6.3)':
+ '@typescript-eslint/type-utils@8.26.0(eslint@9.14.0)(typescript@5.8.2)':
dependencies:
- '@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
+ '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.26.0(eslint@9.14.0)(typescript@5.8.2)
+ debug: 4.3.7(supports-color@8.1.1)
eslint: 9.14.0
- ts-api-utils: 1.3.0(typescript@5.6.3)
- optionalDependencies:
- typescript: 5.6.3
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/type-utils@7.17.0(eslint@9.14.0)(typescript@5.6.3)':
+ '@typescript-eslint/type-utils@8.26.0(eslint@9.22.0)(typescript@5.8.2)':
dependencies:
- '@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.3
+ '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.26.0(eslint@9.22.0)(typescript@5.8.2)
+ debug: 4.3.7(supports-color@8.1.1)
+ eslint: 9.22.0
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/type-utils@8.27.0(eslint@9.22.0)(typescript@5.8.2)':
+ dependencies:
+ '@typescript-eslint/typescript-estree': 8.27.0(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.27.0(eslint@9.22.0)(typescript@5.8.2)
+ debug: 4.3.7(supports-color@8.1.1)
+ eslint: 9.22.0
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@6.21.0': {}
- '@typescript-eslint/types@7.1.0': {}
+ '@typescript-eslint/types@8.26.0': {}
+
+ '@typescript-eslint/types@8.27.0': {}
- '@typescript-eslint/types@7.17.0': {}
+ '@typescript-eslint/types@8.28.0': {}
'@typescript-eslint/typescript-estree@6.21.0(typescript@5.1.6)':
dependencies:
@@ -15362,33 +15366,45 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/typescript-estree@7.1.0(typescript@5.6.3)':
+ '@typescript-eslint/typescript-estree@8.26.0(typescript@5.8.2)':
dependencies:
- '@typescript-eslint/types': 7.1.0
- '@typescript-eslint/visitor-keys': 7.1.0
- debug: 4.3.4
- globby: 11.1.0
+ '@typescript-eslint/types': 8.26.0
+ '@typescript-eslint/visitor-keys': 8.26.0
+ debug: 4.3.7(supports-color@8.1.1)
+ fast-glob: 3.3.3
is-glob: 4.0.3
- minimatch: 9.0.3
- semver: 7.6.0
- ts-api-utils: 1.3.0(typescript@5.6.3)
- optionalDependencies:
- typescript: 5.6.3
+ minimatch: 9.0.4
+ semver: 7.6.3
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/typescript-estree@7.17.0(typescript@5.6.3)':
+ '@typescript-eslint/typescript-estree@8.27.0(typescript@5.8.2)':
dependencies:
- '@typescript-eslint/types': 7.17.0
- '@typescript-eslint/visitor-keys': 7.17.0
- debug: 4.3.5(supports-color@5.5.0)
- globby: 11.1.0
+ '@typescript-eslint/types': 8.27.0
+ '@typescript-eslint/visitor-keys': 8.27.0
+ debug: 4.3.7(supports-color@8.1.1)
+ fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.4
- semver: 7.6.0
- ts-api-utils: 1.3.0(typescript@5.6.3)
- optionalDependencies:
- typescript: 5.6.3
+ semver: 7.6.3
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/typescript-estree@8.28.0(typescript@5.8.2)':
+ dependencies:
+ '@typescript-eslint/types': 8.28.0
+ '@typescript-eslint/visitor-keys': 8.28.0
+ debug: 4.4.0
+ fast-glob: 3.3.3
+ is-glob: 4.0.3
+ minimatch: 9.0.4
+ semver: 7.6.3
+ ts-api-utils: 2.1.0(typescript@5.8.2)
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
@@ -15406,102 +15422,113 @@ snapshots:
- supports-color
- typescript
- '@typescript-eslint/utils@7.1.0(eslint@9.14.0)(typescript@5.6.3)':
+ '@typescript-eslint/utils@8.26.0(eslint@9.14.0)(typescript@5.8.2)':
dependencies:
'@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.6.3)
+ '@typescript-eslint/scope-manager': 8.26.0
+ '@typescript-eslint/types': 8.26.0
+ '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
eslint: 9.14.0
- semver: 7.6.0
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
- - typescript
- '@typescript-eslint/utils@7.17.0(eslint@9.14.0)(typescript@5.6.3)':
+ '@typescript-eslint/utils@8.26.0(eslint@9.22.0)(typescript@5.8.2)':
dependencies:
- '@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.3)
- eslint: 9.14.0
+ '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0)
+ '@typescript-eslint/scope-manager': 8.26.0
+ '@typescript-eslint/types': 8.26.0
+ '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.8.2)
+ eslint: 9.22.0
+ typescript: 5.8.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.27.0(eslint@9.22.0)(typescript@5.8.2)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0)
+ '@typescript-eslint/scope-manager': 8.27.0
+ '@typescript-eslint/types': 8.27.0
+ '@typescript-eslint/typescript-estree': 8.27.0(typescript@5.8.2)
+ eslint: 9.22.0
+ typescript: 5.8.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.28.0(eslint@9.22.0)(typescript@5.8.2)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0)
+ '@typescript-eslint/scope-manager': 8.28.0
+ '@typescript-eslint/types': 8.28.0
+ '@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2)
+ eslint: 9.22.0
+ typescript: 5.8.2
transitivePeerDependencies:
- supports-color
- - typescript
'@typescript-eslint/visitor-keys@6.21.0':
dependencies:
'@typescript-eslint/types': 6.21.0
eslint-visitor-keys: 3.4.3
- '@typescript-eslint/visitor-keys@7.1.0':
+ '@typescript-eslint/visitor-keys@8.26.0':
dependencies:
- '@typescript-eslint/types': 7.1.0
- eslint-visitor-keys: 3.4.3
+ '@typescript-eslint/types': 8.26.0
+ eslint-visitor-keys: 4.2.0
- '@typescript-eslint/visitor-keys@7.17.0':
+ '@typescript-eslint/visitor-keys@8.27.0':
dependencies:
- '@typescript-eslint/types': 7.17.0
- eslint-visitor-keys: 3.4.3
-
- '@ungap/structured-clone@1.2.0': {}
+ '@typescript-eslint/types': 8.27.0
+ eslint-visitor-keys: 4.2.0
- '@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))':
+ '@typescript-eslint/visitor-keys@8.28.0':
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)
+ '@typescript-eslint/types': 8.28.0
+ eslint-visitor-keys: 4.2.0
+
+ '@ungap/structured-clone@1.2.0': {}
- '@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))':
+ '@vitejs/plugin-vue@5.2.3(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@5.8.2))':
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)
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
+ vue: 3.5.13(typescript@5.8.2)
- '@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))':
+ '@vitest/coverage-v8@3.0.9(vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))':
dependencies:
- '@ampproject/remapping': 2.2.1
- '@bcoe/v8-coverage': 0.2.3
- debug: 4.3.5(supports-color@5.5.0)
+ '@ampproject/remapping': 2.3.0
+ '@bcoe/v8-coverage': 1.0.2
+ debug: 4.4.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)
+ istanbul-lib-source-maps: 5.0.6
+ istanbul-reports: 3.1.7
+ magic-string: 0.30.17
+ magicast: 0.3.5
+ std-env: 3.8.1
+ test-exclude: 7.0.1
+ tinyrainbow: 2.0.0
+ vitest: 3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
transitivePeerDependencies:
- supports-color
- '@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))':
+ '@vitest/coverage-v8@3.0.9(vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))':
dependencies:
- '@ampproject/remapping': 2.2.1
- '@bcoe/v8-coverage': 0.2.3
- debug: 4.3.5(supports-color@5.5.0)
+ '@ampproject/remapping': 2.3.0
+ '@bcoe/v8-coverage': 1.0.2
+ debug: 4.4.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)(sass@1.79.4)(terser@5.36.0)
+ istanbul-lib-source-maps: 5.0.6
+ istanbul-reports: 3.1.7
+ magic-string: 0.30.17
+ magicast: 0.3.5
+ std-env: 3.8.1
+ test-exclude: 7.0.1
+ tinyrainbow: 2.0.0
+ vitest: 3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
transitivePeerDependencies:
- supports-color
- '@vitest/expect@1.6.0':
- dependencies:
- '@vitest/spy': 1.6.0
- '@vitest/utils': 1.6.0
- chai: 4.3.10
-
'@vitest/expect@2.0.5':
dependencies:
'@vitest/spy': 2.0.5
@@ -15509,6 +15536,22 @@ snapshots:
chai: 5.1.1
tinyrainbow: 1.2.0
+ '@vitest/expect@3.0.9':
+ dependencies:
+ '@vitest/spy': 3.0.9
+ '@vitest/utils': 3.0.9
+ chai: 5.2.0
+ tinyrainbow: 2.0.0
+
+ '@vitest/mocker@3.0.9(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))':
+ dependencies:
+ '@vitest/spy': 3.0.9
+ estree-walker: 3.0.3
+ magic-string: 0.30.17
+ optionalDependencies:
+ msw: 2.7.3(@types/node@22.13.11)(typescript@5.8.2)
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
+
'@vitest/pretty-format@2.0.5':
dependencies:
tinyrainbow: 1.2.0
@@ -15517,32 +15560,28 @@ snapshots:
dependencies:
tinyrainbow: 1.2.0
- '@vitest/runner@1.6.0':
+ '@vitest/pretty-format@3.0.9':
dependencies:
- '@vitest/utils': 1.6.0
- p-limit: 5.0.0
- pathe: 1.1.2
+ tinyrainbow: 2.0.0
- '@vitest/snapshot@1.6.0':
+ '@vitest/runner@3.0.9':
dependencies:
- magic-string: 0.30.11
- pathe: 1.1.2
- pretty-format: 29.7.0
+ '@vitest/utils': 3.0.9
+ pathe: 2.0.3
- '@vitest/spy@1.6.0':
+ '@vitest/snapshot@3.0.9':
dependencies:
- tinyspy: 2.2.0
+ '@vitest/pretty-format': 3.0.9
+ magic-string: 0.30.17
+ pathe: 2.0.3
'@vitest/spy@2.0.5':
dependencies:
tinyspy: 3.0.2
- '@vitest/utils@1.6.0':
+ '@vitest/spy@3.0.9':
dependencies:
- diff-sequences: 29.6.3
- estree-walker: 3.0.3
- loupe: 2.3.7
- pretty-format: 29.7.0
+ tinyspy: 3.0.2
'@vitest/utils@2.0.5':
dependencies:
@@ -15557,137 +15596,211 @@ snapshots:
loupe: 3.1.2
tinyrainbow: 1.2.0
+ '@vitest/utils@3.0.9':
+ dependencies:
+ '@vitest/pretty-format': 3.0.9
+ loupe: 3.1.3
+ tinyrainbow: 2.0.0
+
'@volar/language-core@2.2.0':
dependencies:
'@volar/source-map': 2.2.0
- '@volar/language-core@2.4.10':
+ '@volar/language-core@2.4.12':
dependencies:
- '@volar/source-map': 2.4.10
+ '@volar/source-map': 2.4.12
'@volar/source-map@2.2.0':
dependencies:
muggle-string: 0.4.1
- '@volar/source-map@2.4.10': {}
+ '@volar/source-map@2.4.12': {}
'@volar/typescript@2.2.0':
dependencies:
'@volar/language-core': 2.2.0
path-browserify: 1.0.1
- '@volar/typescript@2.4.10':
+ '@volar/typescript@2.4.12':
dependencies:
- '@volar/language-core': 2.4.10
+ '@volar/language-core': 2.4.12
path-browserify: 1.0.1
vscode-uri: 3.0.8
- '@vue/compiler-core@3.5.11':
+ '@vue/compiler-core@3.5.12':
dependencies:
'@babel/parser': 7.25.7
- '@vue/shared': 3.5.11
+ '@vue/shared': 3.5.12
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.1
- '@vue/compiler-core@3.5.12':
+ '@vue/compiler-core@3.5.13':
dependencies:
'@babel/parser': 7.25.7
- '@vue/shared': 3.5.12
+ '@vue/shared': 3.5.13
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.1
- '@vue/compiler-dom@3.5.11':
- dependencies:
- '@vue/compiler-core': 3.5.11
- '@vue/shared': 3.5.11
-
'@vue/compiler-dom@3.5.12':
dependencies:
'@vue/compiler-core': 3.5.12
'@vue/shared': 3.5.12
- '@vue/compiler-sfc@3.5.12':
+ '@vue/compiler-dom@3.5.13':
+ dependencies:
+ '@vue/compiler-core': 3.5.13
+ '@vue/shared': 3.5.13
+
+ '@vue/compiler-sfc@3.5.13':
dependencies:
'@babel/parser': 7.25.7
- '@vue/compiler-core': 3.5.12
- '@vue/compiler-dom': 3.5.12
- '@vue/compiler-ssr': 3.5.12
- '@vue/shared': 3.5.12
+ '@vue/compiler-core': 3.5.13
+ '@vue/compiler-dom': 3.5.13
+ '@vue/compiler-ssr': 3.5.13
+ '@vue/shared': 3.5.13
estree-walker: 2.0.2
- magic-string: 0.30.11
- postcss: 8.4.49
+ magic-string: 0.30.17
+ postcss: 8.5.3
source-map-js: 1.2.1
- '@vue/compiler-ssr@3.5.12':
+ '@vue/compiler-ssr@3.5.13':
dependencies:
- '@vue/compiler-dom': 3.5.12
- '@vue/shared': 3.5.12
+ '@vue/compiler-dom': 3.5.13
+ '@vue/shared': 3.5.13
'@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.3)':
+ '@vue/language-core@2.0.16(typescript@5.8.2)':
dependencies:
'@volar/language-core': 2.2.0
- '@vue/compiler-dom': 3.5.11
- '@vue/shared': 3.5.11
+ '@vue/compiler-dom': 3.5.12
+ '@vue/shared': 3.5.12
computeds: 0.0.1
minimatch: 9.0.4
path-browserify: 1.0.1
vue-template-compiler: 2.7.14
optionalDependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
- '@vue/language-core@2.1.10(typescript@5.6.3)':
+ '@vue/language-core@2.2.8(typescript@5.8.2)':
dependencies:
- '@volar/language-core': 2.4.10
- '@vue/compiler-dom': 3.5.11
+ '@volar/language-core': 2.4.12
+ '@vue/compiler-dom': 3.5.12
'@vue/compiler-vue2': 2.7.16
- '@vue/shared': 3.5.11
- alien-signals: 0.2.2
+ '@vue/shared': 3.5.12
+ alien-signals: 1.0.7
minimatch: 9.0.4
muggle-string: 0.4.1
path-browserify: 1.0.1
optionalDependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
- '@vue/reactivity@3.5.12':
+ '@vue/reactivity@3.5.13':
dependencies:
- '@vue/shared': 3.5.12
+ '@vue/shared': 3.5.13
- '@vue/runtime-core@3.5.12':
+ '@vue/runtime-core@3.5.13':
dependencies:
- '@vue/reactivity': 3.5.12
- '@vue/shared': 3.5.12
+ '@vue/reactivity': 3.5.13
+ '@vue/shared': 3.5.13
- '@vue/runtime-dom@3.5.12':
+ '@vue/runtime-dom@3.5.13':
dependencies:
- '@vue/reactivity': 3.5.12
- '@vue/runtime-core': 3.5.12
- '@vue/shared': 3.5.12
+ '@vue/reactivity': 3.5.13
+ '@vue/runtime-core': 3.5.13
+ '@vue/shared': 3.5.13
csstype: 3.1.3
- '@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3))':
+ '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2))':
dependencies:
- '@vue/compiler-ssr': 3.5.12
- '@vue/shared': 3.5.12
- vue: 3.5.12(typescript@5.6.3)
-
- '@vue/shared@3.5.11': {}
+ '@vue/compiler-ssr': 3.5.13
+ '@vue/shared': 3.5.13
+ vue: 3.5.13(typescript@5.8.2)
'@vue/shared@3.5.12': {}
- '@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/shared@3.5.13': {}
+
+ '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))':
dependencies:
js-beautify: 1.14.9
- vue: 3.5.12(typescript@5.6.3)
+ vue: 3.5.13(typescript@5.8.2)
vue-component-type-helpers: 1.8.4
optionalDependencies:
- '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.6.3))
+ '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.8.2))
+
+ '@xhmikosr/archive-type@7.0.0':
+ dependencies:
+ file-type: 19.6.0
+
+ '@xhmikosr/bin-check@7.0.3':
+ dependencies:
+ execa: 5.1.1
+ isexe: 2.0.0
+
+ '@xhmikosr/bin-wrapper@13.0.5':
+ dependencies:
+ '@xhmikosr/bin-check': 7.0.3
+ '@xhmikosr/downloader': 15.0.1
+ '@xhmikosr/os-filter-obj': 3.0.0
+ bin-version-check: 5.1.0
+
+ '@xhmikosr/decompress-tar@8.0.1':
+ dependencies:
+ file-type: 19.6.0
+ is-stream: 2.0.1
+ tar-stream: 3.1.7
+
+ '@xhmikosr/decompress-tarbz2@8.0.2':
+ dependencies:
+ '@xhmikosr/decompress-tar': 8.0.1
+ file-type: 19.6.0
+ is-stream: 2.0.1
+ seek-bzip: 2.0.0
+ unbzip2-stream: 1.4.3
+
+ '@xhmikosr/decompress-targz@8.0.1':
+ dependencies:
+ '@xhmikosr/decompress-tar': 8.0.1
+ file-type: 19.6.0
+ is-stream: 2.0.1
+
+ '@xhmikosr/decompress-unzip@7.0.0':
+ dependencies:
+ file-type: 19.6.0
+ get-stream: 6.0.1
+ yauzl: 3.2.0
+
+ '@xhmikosr/decompress@10.0.1':
+ dependencies:
+ '@xhmikosr/decompress-tar': 8.0.1
+ '@xhmikosr/decompress-tarbz2': 8.0.2
+ '@xhmikosr/decompress-targz': 8.0.1
+ '@xhmikosr/decompress-unzip': 7.0.0
+ graceful-fs: 4.2.11
+ make-dir: 4.0.0
+ strip-dirs: 3.0.0
+
+ '@xhmikosr/downloader@15.0.1':
+ dependencies:
+ '@xhmikosr/archive-type': 7.0.0
+ '@xhmikosr/decompress': 10.0.1
+ content-disposition: 0.5.4
+ defaults: 3.0.0
+ ext-name: 5.0.0
+ file-type: 19.6.0
+ filenamify: 6.0.0
+ get-stream: 6.0.1
+ got: 13.0.0
+
+ '@xhmikosr/os-filter-obj@3.0.0':
+ dependencies:
+ arch: 3.0.0
abbrev@1.1.1: {}
@@ -15704,19 +15817,17 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
- acorn-import-attributes@1.9.5(acorn@8.14.0):
+ acorn-import-attributes@1.9.5(acorn@8.14.1):
dependencies:
- acorn: 8.14.0
+ acorn: 8.14.1
- acorn-jsx@5.3.2(acorn@8.14.0):
+ acorn-jsx@5.3.2(acorn@8.14.1):
dependencies:
- acorn: 8.14.0
-
- acorn-walk@8.3.2: {}
+ acorn: 8.14.1
acorn@7.4.1: {}
- acorn@8.14.0: {}
+ acorn@8.14.1: {}
agent-base@7.1.0:
dependencies:
@@ -15724,6 +15835,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ agent-base@7.1.3: {}
+
aggregate-error@3.1.0:
dependencies:
clean-stack: 2.2.0
@@ -15783,7 +15896,20 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
- alien-signals@0.2.2: {}
+ alien-signals@1.0.7: {}
+
+ analytics-utils@1.0.14(@types/dlv@1.1.5):
+ dependencies:
+ '@analytics/type-utils': 0.6.2
+ '@types/dlv': 1.1.5
+ dlv: 1.1.3
+
+ analytics@0.8.16(@types/dlv@1.1.5):
+ dependencies:
+ '@analytics/core': 0.12.17(@types/dlv@1.1.5)
+ '@analytics/storage-utils': 0.4.2
+ transitivePeerDependencies:
+ - '@types/dlv'
ansi-colors@4.1.3: {}
@@ -15809,6 +15935,8 @@ snapshots:
ansi-styles@6.2.1: {}
+ ansis@3.17.0: {}
+
any-promise@1.3.0: {}
anymatch@3.1.3:
@@ -15820,7 +15948,10 @@ snapshots:
append-field@1.0.0: {}
- arch@2.2.0: {}
+ arch@2.2.0:
+ optional: true
+
+ arch@3.0.0: {}
archiver-utils@5.0.2:
dependencies:
@@ -15864,11 +15995,6 @@ snapshots:
dependencies:
dequal: 2.0.3
- array-buffer-byte-length@1.0.0:
- dependencies:
- call-bind: 1.0.2
- is-array-buffer: 3.0.2
-
array-buffer-byte-length@1.0.1:
dependencies:
call-bind: 1.0.7
@@ -15898,26 +16024,17 @@ snapshots:
array.prototype.flat@1.3.2:
dependencies:
- call-bind: 1.0.2
- define-properties: 1.2.0
- es-abstract: 1.22.1
- es-shim-unscopables: 1.0.0
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.23.3
+ es-shim-unscopables: 1.0.2
array.prototype.flatmap@1.3.2:
dependencies:
- call-bind: 1.0.2
- define-properties: 1.2.0
- es-abstract: 1.22.1
- es-shim-unscopables: 1.0.0
-
- arraybuffer.prototype.slice@1.0.1:
- dependencies:
- array-buffer-byte-length: 1.0.0
- call-bind: 1.0.2
- define-properties: 1.2.0
- get-intrinsic: 1.2.1
- is-array-buffer: 3.0.2
- is-shared-array-buffer: 1.0.2
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.23.3
+ es-shim-unscopables: 1.0.2
arraybuffer.prototype.slice@1.0.3:
dependencies:
@@ -15955,8 +16072,6 @@ snapshots:
assert-plus@1.0.0: {}
- assertion-error@1.1.0: {}
-
assertion-error@2.0.1: {}
ast-types@0.16.1:
@@ -15994,11 +16109,11 @@ snapshots:
'@fastify/error': 4.0.0
fastq: 1.17.1
- aws-sdk-client-mock@4.0.1:
+ aws-sdk-client-mock@4.1.0:
dependencies:
- '@types/sinon': 10.0.13
- sinon: 16.1.3
- tslib: 2.6.2
+ '@types/sinon': 17.0.4
+ sinon: 18.0.1
+ tslib: 2.7.0
aws-sign2@0.7.0:
optional: true
@@ -16014,16 +16129,16 @@ snapshots:
axios@1.7.4:
dependencies:
- follow-redirects: 1.15.9(debug@4.3.7)
- form-data: 4.0.1
+ follow-redirects: 1.15.9(debug@4.4.0)
+ form-data: 4.0.2
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
- axios@1.7.7(debug@4.3.7):
+ axios@1.8.4(debug@4.4.0):
dependencies:
- follow-redirects: 1.15.9(debug@4.3.7)
- form-data: 4.0.1
+ follow-redirects: 1.15.9(debug@4.4.0)
+ form-data: 4.0.2
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
@@ -16140,24 +16255,17 @@ snapshots:
dependencies:
open: 8.4.2
- bin-check@4.1.0:
- dependencies:
- execa: 0.7.0
- executable: 4.1.1
-
- bin-version-check@5.0.0:
+ bin-version-check@5.1.0:
dependencies:
bin-version: 6.0.0
semver: 7.6.3
- semver-truncate: 2.0.0
+ semver-truncate: 3.0.0
bin-version@6.0.0:
dependencies:
execa: 5.1.1
find-versions: 5.1.0
- binary-extensions@2.2.0: {}
-
bl@4.1.0:
dependencies:
buffer: 5.7.1
@@ -16203,10 +16311,6 @@ snapshots:
dependencies:
balanced-match: 1.0.2
- braces@3.0.2:
- dependencies:
- fill-range: 7.0.1
-
braces@3.0.3:
dependencies:
fill-range: 7.1.1
@@ -16220,13 +16324,6 @@ 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
@@ -16234,6 +16331,13 @@ snapshots:
node-releases: 2.0.14
update-browserslist-db: 1.0.13(browserslist@4.23.0)
+ browserslist@4.24.4:
+ dependencies:
+ caniuse-lite: 1.0.30001707
+ electron-to-chromium: 1.5.123
+ node-releases: 2.0.19
+ update-browserslist-db: 1.1.3(browserslist@4.24.4)
+
bs-logger@0.2.6:
dependencies:
fast-json-stable-stringify: 2.1.0
@@ -16242,8 +16346,7 @@ snapshots:
dependencies:
node-int64: 0.4.0
- buffer-crc32@0.2.13:
- optional: true
+ buffer-crc32@0.2.13: {}
buffer-crc32@1.0.0: {}
@@ -16266,20 +16369,15 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
- bufferutil@4.0.7:
- dependencies:
- node-gyp-build: 4.6.0
- optional: true
-
- bufferutil@4.0.8:
+ bufferutil@4.0.9:
dependencies:
node-gyp-build: 4.8.1
optional: true
- bullmq@5.26.1:
+ bullmq@5.44.1:
dependencies:
- cron-parser: 4.8.1
- ioredis: 5.4.1
+ cron-parser: 4.9.0
+ ioredis: 5.6.0
msgpackr: 1.11.2
node-abort-controller: 3.1.1
semver: 7.6.3
@@ -16313,8 +16411,6 @@ snapshots:
tar: 6.2.1
unique-filename: 3.0.0
- cacheable-lookup@5.0.4: {}
-
cacheable-lookup@7.0.0: {}
cacheable-request@10.2.14:
@@ -16337,19 +16433,14 @@ snapshots:
normalize-url: 8.0.1
responselike: 3.0.0
- cacheable-request@7.0.2:
- dependencies:
- clone-response: 1.0.3
- get-stream: 5.2.0
- http-cache-semantics: 4.1.1
- keyv: 4.5.4
- lowercase-keys: 2.0.0
- normalize-url: 6.1.0
- responselike: 2.0.1
-
cachedir@2.3.0:
optional: true
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
call-bind@1.0.2:
dependencies:
function-bind: 1.1.2
@@ -16379,15 +16470,15 @@ snapshots:
caniuse-api@3.0.0:
dependencies:
- browserslist: 4.23.0
+ browserslist: 4.24.4
caniuse-lite: 1.0.30001591
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
- caniuse-lite@1.0.30001566: {}
-
caniuse-lite@1.0.30001591: {}
+ caniuse-lite@1.0.30001707: {}
+
canonicalize@1.0.8: {}
canvas-confetti@1.9.3: {}
@@ -16406,17 +16497,15 @@ snapshots:
ccount@2.0.1: {}
- chai@4.3.10:
+ chai@5.1.1:
dependencies:
- assertion-error: 1.1.0
- check-error: 1.0.3
- deep-eql: 4.1.3
- get-func-name: 2.0.2
- loupe: 2.3.7
- pathval: 1.1.1
- type-detect: 4.0.8
+ assertion-error: 2.0.1
+ check-error: 2.1.1
+ deep-eql: 5.0.2
+ loupe: 3.1.2
+ pathval: 2.0.0
- chai@5.1.1:
+ chai@5.2.0:
dependencies:
assertion-error: 2.0.1
check-error: 2.1.1
@@ -16426,7 +16515,7 @@ snapshots:
chalk-template@1.1.0:
dependencies:
- chalk: 5.3.0
+ chalk: 5.4.1
chalk@2.4.2:
dependencies:
@@ -16444,7 +16533,7 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
- chalk@5.3.0: {}
+ chalk@5.4.1: {}
char-regex@1.0.2: {}
@@ -16458,32 +16547,29 @@ snapshots:
dependencies:
is-regex: 1.1.4
- chart.js@4.4.6:
+ chart.js@4.4.8:
dependencies:
'@kurkle/color': 0.3.2
- chartjs-adapter-date-fns@3.0.0(chart.js@4.4.6)(date-fns@2.30.0):
+ chartjs-adapter-date-fns@3.0.0(chart.js@4.4.8)(date-fns@4.1.0):
dependencies:
- chart.js: 4.4.6
- date-fns: 2.30.0
+ chart.js: 4.4.8
+ date-fns: 4.1.0
- chartjs-chart-matrix@2.0.1(chart.js@4.4.6):
+ chartjs-chart-matrix@2.1.1(chart.js@4.4.8):
dependencies:
- chart.js: 4.4.6
+ chart.js: 4.4.8
- chartjs-plugin-gradient@0.6.1(chart.js@4.4.6):
+ chartjs-plugin-gradient@0.6.1(chart.js@4.4.8):
dependencies:
- chart.js: 4.4.6
+ chart.js: 4.4.8
- chartjs-plugin-zoom@2.0.1(chart.js@4.4.6):
+ chartjs-plugin-zoom@2.2.0(chart.js@4.4.8):
dependencies:
- chart.js: 4.4.6
+ '@types/hammerjs': 2.0.46
+ chart.js: 4.4.8
hammerjs: 2.0.8
- check-error@1.0.3:
- dependencies:
- get-func-name: 2.0.2
-
check-error@2.1.1: {}
check-more-types@2.24.0: {}
@@ -16495,7 +16581,7 @@ snapshots:
css-what: 6.1.0
domelementtype: 2.3.0
domhandler: 5.0.3
- domutils: 3.0.1
+ domutils: 3.1.0
cheerio@1.0.0:
dependencies:
@@ -16511,33 +16597,17 @@ snapshots:
undici: 6.20.0
whatwg-mimetype: 4.0.0
- cheerio@1.0.0-rc.12:
+ chokidar@4.0.3:
dependencies:
- cheerio-select: 2.1.0
- dom-serializer: 2.0.0
- domhandler: 5.0.3
- domutils: 3.0.1
- htmlparser2: 8.0.1
- parse5: 7.2.1
- parse5-htmlparser2-tree-adapter: 7.0.0
-
- chokidar@3.5.3:
- dependencies:
- anymatch: 3.1.3
- braces: 3.0.2
- glob-parent: 5.1.2
- is-binary-path: 2.1.0
- is-glob: 4.0.3
- normalize-path: 3.0.0
- readdirp: 3.6.0
- optionalDependencies:
- fsevents: 2.3.3
+ readdirp: 4.1.2
chownr@1.1.4: {}
chownr@2.0.0: {}
- chromatic@11.18.1: {}
+ chownr@3.0.0: {}
+
+ chromatic@11.27.0: {}
ci-info@3.7.1: {}
@@ -16599,16 +16669,10 @@ snapshots:
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
- clone-response@1.0.3:
- dependencies:
- mimic-response: 1.0.1
-
cluster-key-slot@1.1.2: {}
co@4.6.0: {}
- code-error-fragment@0.0.230: {}
-
collect-v8-coverage@1.0.1: {}
color-convert@1.9.3:
@@ -16650,8 +16714,7 @@ snapshots:
commander@2.20.3: {}
- commander@6.2.1:
- optional: true
+ commander@6.2.1: {}
commander@7.2.0: {}
@@ -16695,7 +16758,7 @@ snapshots:
ini: 1.3.8
proto-list: 1.2.4
- consola@2.15.3: {}
+ consola@3.4.2: {}
constantinople@4.0.1:
dependencies:
@@ -16712,8 +16775,6 @@ snapshots:
cookie-signature@1.0.6: {}
- cookie@0.6.0: {}
-
cookie@0.7.1: {}
cookie@0.7.2: {}
@@ -16736,13 +16797,28 @@ snapshots:
crc-32: 1.2.2
readable-stream: 4.3.0
- create-jest@29.7.0(@types/node@22.9.0):
+ create-jest@29.7.0(@types/node@22.13.10):
+ 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@22.13.10)
+ jest-util: 29.7.0
+ prompts: 2.4.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ create-jest@29.7.0(@types/node@22.13.9):
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@22.9.0)
+ jest-config: 29.7.0(@types/node@22.13.9)
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@@ -16751,14 +16827,14 @@ snapshots:
- supports-color
- ts-node
- cron-parser@4.8.1:
+ cron-parser@4.9.0:
dependencies:
luxon: 3.3.0
- cropperjs@2.0.0-rc.2:
+ cropperjs@2.0.0:
dependencies:
- '@cropper/elements': 2.0.0-rc.2
- '@cropper/utils': 2.0.0-rc.2
+ '@cropper/elements': 2.0.0
+ '@cropper/utils': 2.0.0
cross-env@7.0.3:
dependencies:
@@ -16776,39 +16852,39 @@ snapshots:
transitivePeerDependencies:
- encoding
- cross-spawn@5.1.0:
+ cross-spawn@7.0.3:
dependencies:
- lru-cache: 4.1.5
- shebang-command: 1.2.0
- which: 1.3.1
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
- cross-spawn@7.0.3:
+ cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
- css-declaration-sorter@7.2.0(postcss@8.4.49):
+ css-declaration-sorter@7.2.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
css-select@5.1.0:
dependencies:
boolbase: 1.0.0
css-what: 6.1.0
domhandler: 5.0.3
- domutils: 3.0.1
+ domutils: 3.1.0
nth-check: 2.1.1
css-tree@2.2.1:
dependencies:
mdn-data: 2.0.28
- source-map-js: 1.2.0
+ source-map-js: 1.2.1
css-tree@2.3.1:
dependencies:
mdn-data: 2.0.30
- source-map-js: 1.2.0
+ source-map-js: 1.2.1
css-what@6.1.0: {}
@@ -16816,57 +16892,58 @@ snapshots:
cssesc@3.0.0: {}
- cssnano-preset-default@6.1.2(postcss@8.4.49):
+ cssnano-preset-default@7.0.6(postcss@8.5.3):
dependencies:
- browserslist: 4.23.0
- 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)
+ browserslist: 4.24.4
+ css-declaration-sorter: 7.2.0(postcss@8.5.3)
+ cssnano-utils: 5.0.0(postcss@8.5.3)
+ postcss: 8.5.3
+ postcss-calc: 10.1.1(postcss@8.5.3)
+ postcss-colormin: 7.0.2(postcss@8.5.3)
+ postcss-convert-values: 7.0.4(postcss@8.5.3)
+ postcss-discard-comments: 7.0.3(postcss@8.5.3)
+ postcss-discard-duplicates: 7.0.1(postcss@8.5.3)
+ postcss-discard-empty: 7.0.0(postcss@8.5.3)
+ postcss-discard-overridden: 7.0.0(postcss@8.5.3)
+ postcss-merge-longhand: 7.0.4(postcss@8.5.3)
+ postcss-merge-rules: 7.0.4(postcss@8.5.3)
+ postcss-minify-font-values: 7.0.0(postcss@8.5.3)
+ postcss-minify-gradients: 7.0.0(postcss@8.5.3)
+ postcss-minify-params: 7.0.2(postcss@8.5.3)
+ postcss-minify-selectors: 7.0.4(postcss@8.5.3)
+ postcss-normalize-charset: 7.0.0(postcss@8.5.3)
+ postcss-normalize-display-values: 7.0.0(postcss@8.5.3)
+ postcss-normalize-positions: 7.0.0(postcss@8.5.3)
+ postcss-normalize-repeat-style: 7.0.0(postcss@8.5.3)
+ postcss-normalize-string: 7.0.0(postcss@8.5.3)
+ postcss-normalize-timing-functions: 7.0.0(postcss@8.5.3)
+ postcss-normalize-unicode: 7.0.2(postcss@8.5.3)
+ postcss-normalize-url: 7.0.0(postcss@8.5.3)
+ postcss-normalize-whitespace: 7.0.0(postcss@8.5.3)
+ postcss-ordered-values: 7.0.1(postcss@8.5.3)
+ postcss-reduce-initial: 7.0.2(postcss@8.5.3)
+ postcss-reduce-transforms: 7.0.0(postcss@8.5.3)
+ postcss-svgo: 7.0.1(postcss@8.5.3)
+ postcss-unique-selectors: 7.0.3(postcss@8.5.3)
- cssnano-utils@4.0.2(postcss@8.4.49):
+ cssnano-utils@5.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
- cssnano@6.1.2(postcss@8.4.49):
+ cssnano@7.0.6(postcss@8.5.3):
dependencies:
- cssnano-preset-default: 6.1.2(postcss@8.4.49)
- lilconfig: 3.1.1
- postcss: 8.4.49
+ cssnano-preset-default: 7.0.6(postcss@8.5.3)
+ lilconfig: 3.1.3
+ postcss: 8.5.3
csso@5.0.5:
dependencies:
css-tree: 2.2.1
- cssstyle@4.0.1:
+ cssstyle@4.3.0:
dependencies:
- rrweb-cssom: 0.6.0
+ '@asamuzakjp/css-color': 3.1.1
+ rrweb-cssom: 0.8.0
csstype@3.1.3: {}
@@ -16926,7 +17003,7 @@ snapshots:
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
- whatwg-url: 14.0.0
+ whatwg-url: 14.2.0
data-view-buffer@1.0.1:
dependencies:
@@ -16950,6 +17027,8 @@ snapshots:
dependencies:
'@babel/runtime': 7.23.4
+ date-fns@4.1.0: {}
+
dayjs@1.11.10: {}
de-indent@1.0.2: {}
@@ -16974,12 +17053,22 @@ snapshots:
optionalDependencies:
supports-color: 5.5.0
+ debug@4.3.7(supports-color@5.5.0):
+ dependencies:
+ ms: 2.1.3
+ optionalDependencies:
+ supports-color: 5.5.0
+
debug@4.3.7(supports-color@8.1.1):
dependencies:
ms: 2.1.3
optionalDependencies:
supports-color: 8.1.1
+ debug@4.4.0:
+ dependencies:
+ ms: 2.1.3
+
decamelize-keys@1.1.1:
dependencies:
decamelize: 1.2.0
@@ -17019,10 +17108,6 @@ snapshots:
transitivePeerDependencies:
- debug
- deep-eql@4.1.3:
- dependencies:
- type-detect: 4.0.8
-
deep-eql@5.0.2: {}
deep-equal@2.2.0:
@@ -17051,6 +17136,8 @@ snapshots:
deepmerge@4.2.2: {}
+ defaults@3.0.0: {}
+
defer-to-connect@2.0.1: {}
define-data-property@1.1.4:
@@ -17069,7 +17156,7 @@ snapshots:
define-properties@1.2.1:
dependencies:
define-data-property: 1.1.4
- has-property-descriptors: 1.0.0
+ has-property-descriptors: 1.0.2
object-keys: 1.1.1
delayed-stream@1.0.0: {}
@@ -17082,6 +17169,9 @@ snapshots:
destroy@1.2.0: {}
+ detect-libc@1.0.3:
+ optional: true
+
detect-libc@2.0.3: {}
detect-newline@3.1.0: {}
@@ -17094,8 +17184,6 @@ snapshots:
diff-sequences@29.6.3: {}
- diff@5.1.0: {}
-
diff@5.2.0: {}
dijkstrajs@1.0.2: {}
@@ -17106,6 +17194,8 @@ snapshots:
disposable-email-domains@1.0.62: {}
+ dlv@1.1.3: {}
+
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
@@ -17166,6 +17256,12 @@ snapshots:
dotenv@16.0.3: {}
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
duplexer@0.1.2: {}
eastasianwidth@0.2.0: {}
@@ -17188,16 +17284,14 @@ snapshots:
ee-first@1.1.1: {}
- ejs@3.1.10:
- dependencies:
- jake: 10.8.5
-
- electron-to-chromium@1.4.601: {}
-
electron-to-chromium@1.4.686: {}
+ electron-to-chromium@1.5.123: {}
+
emittery@0.13.1: {}
+ emoji-regex-xs@1.0.0: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -17237,48 +17331,6 @@ snapshots:
dependencies:
is-arrayish: 0.2.1
- es-abstract@1.22.1:
- dependencies:
- array-buffer-byte-length: 1.0.0
- arraybuffer.prototype.slice: 1.0.1
- available-typed-arrays: 1.0.5
- call-bind: 1.0.2
- es-set-tostringtag: 2.0.1
- es-to-primitive: 1.2.1
- function.prototype.name: 1.1.5
- get-intrinsic: 1.2.1
- get-symbol-description: 1.0.0
- globalthis: 1.0.3
- gopd: 1.0.1
- has: 1.0.3
- has-property-descriptors: 1.0.0
- has-proto: 1.0.1
- has-symbols: 1.0.3
- internal-slot: 1.0.5
- is-array-buffer: 3.0.2
- is-callable: 1.2.7
- is-negative-zero: 2.0.2
- is-regex: 1.1.4
- is-shared-array-buffer: 1.0.2
- is-string: 1.0.7
- is-typed-array: 1.1.10
- is-weakref: 1.0.2
- object-inspect: 1.12.3
- object-keys: 1.1.1
- object.assign: 4.1.4
- regexp.prototype.flags: 1.5.0
- safe-array-concat: 1.0.0
- safe-regex-test: 1.0.0
- string.prototype.trim: 1.2.7
- string.prototype.trimend: 1.0.6
- string.prototype.trimstart: 1.0.6
- typed-array-buffer: 1.0.0
- typed-array-byte-length: 1.0.0
- typed-array-byte-offset: 1.0.0
- typed-array-length: 1.0.4
- unbox-primitive: 1.0.2
- which-typed-array: 1.1.11
-
es-abstract@1.23.3:
dependencies:
array-buffer-byte-length: 1.0.1
@@ -17332,6 +17384,8 @@ snapshots:
dependencies:
get-intrinsic: 1.2.4
+ es-define-property@1.0.1: {}
+
es-errors@1.3.0: {}
es-get-iterator@1.1.3:
@@ -17346,15 +17400,15 @@ snapshots:
isarray: 2.0.5
stop-iteration-iterator: 1.0.0
+ es-module-lexer@1.6.0: {}
+
es-object-atoms@1.0.0:
dependencies:
es-errors: 1.3.0
- es-set-tostringtag@2.0.1:
+ es-object-atoms@1.1.1:
dependencies:
- get-intrinsic: 1.2.1
- has: 1.0.3
- has-tostringtag: 1.0.0
+ es-errors: 1.3.0
es-set-tostringtag@2.0.3:
dependencies:
@@ -17362,9 +17416,12 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.2
- es-shim-unscopables@1.0.0:
+ es-set-tostringtag@2.1.0:
dependencies:
- has: 1.0.3
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
es-shim-unscopables@1.0.2:
dependencies:
@@ -17378,93 +17435,73 @@ snapshots:
es-toolkit@1.27.0: {}
- esbuild-register@3.5.0(esbuild@0.24.0):
+ esbuild-register@3.5.0(esbuild@0.25.1):
dependencies:
debug: 4.3.7(supports-color@8.1.1)
- esbuild: 0.24.0
+ esbuild: 0.25.1
transitivePeerDependencies:
- supports-color
- esbuild@0.18.20:
- optionalDependencies:
- '@esbuild/android-arm': 0.18.20
- '@esbuild/android-arm64': 0.18.20
- '@esbuild/android-x64': 0.18.20
- '@esbuild/darwin-arm64': 0.18.20
- '@esbuild/darwin-x64': 0.18.20
- '@esbuild/freebsd-arm64': 0.18.20
- '@esbuild/freebsd-x64': 0.18.20
- '@esbuild/linux-arm': 0.18.20
- '@esbuild/linux-arm64': 0.18.20
- '@esbuild/linux-ia32': 0.18.20
- '@esbuild/linux-loong64': 0.18.20
- '@esbuild/linux-mips64el': 0.18.20
- '@esbuild/linux-ppc64': 0.18.20
- '@esbuild/linux-riscv64': 0.18.20
- '@esbuild/linux-s390x': 0.18.20
- '@esbuild/linux-x64': 0.18.20
- '@esbuild/netbsd-x64': 0.18.20
- '@esbuild/openbsd-x64': 0.18.20
- '@esbuild/sunos-x64': 0.18.20
- '@esbuild/win32-arm64': 0.18.20
- '@esbuild/win32-ia32': 0.18.20
- '@esbuild/win32-x64': 0.18.20
-
- esbuild@0.21.5:
+ esbuild@0.25.0:
optionalDependencies:
- '@esbuild/aix-ppc64': 0.21.5
- '@esbuild/android-arm': 0.21.5
- '@esbuild/android-arm64': 0.21.5
- '@esbuild/android-x64': 0.21.5
- '@esbuild/darwin-arm64': 0.21.5
- '@esbuild/darwin-x64': 0.21.5
- '@esbuild/freebsd-arm64': 0.21.5
- '@esbuild/freebsd-x64': 0.21.5
- '@esbuild/linux-arm': 0.21.5
- '@esbuild/linux-arm64': 0.21.5
- '@esbuild/linux-ia32': 0.21.5
- '@esbuild/linux-loong64': 0.21.5
- '@esbuild/linux-mips64el': 0.21.5
- '@esbuild/linux-ppc64': 0.21.5
- '@esbuild/linux-riscv64': 0.21.5
- '@esbuild/linux-s390x': 0.21.5
- '@esbuild/linux-x64': 0.21.5
- '@esbuild/netbsd-x64': 0.21.5
- '@esbuild/openbsd-x64': 0.21.5
- '@esbuild/sunos-x64': 0.21.5
- '@esbuild/win32-arm64': 0.21.5
- '@esbuild/win32-ia32': 0.21.5
- '@esbuild/win32-x64': 0.21.5
+ '@esbuild/aix-ppc64': 0.25.0
+ '@esbuild/android-arm': 0.25.0
+ '@esbuild/android-arm64': 0.25.0
+ '@esbuild/android-x64': 0.25.0
+ '@esbuild/darwin-arm64': 0.25.0
+ '@esbuild/darwin-x64': 0.25.0
+ '@esbuild/freebsd-arm64': 0.25.0
+ '@esbuild/freebsd-x64': 0.25.0
+ '@esbuild/linux-arm': 0.25.0
+ '@esbuild/linux-arm64': 0.25.0
+ '@esbuild/linux-ia32': 0.25.0
+ '@esbuild/linux-loong64': 0.25.0
+ '@esbuild/linux-mips64el': 0.25.0
+ '@esbuild/linux-ppc64': 0.25.0
+ '@esbuild/linux-riscv64': 0.25.0
+ '@esbuild/linux-s390x': 0.25.0
+ '@esbuild/linux-x64': 0.25.0
+ '@esbuild/netbsd-arm64': 0.25.0
+ '@esbuild/netbsd-x64': 0.25.0
+ '@esbuild/openbsd-arm64': 0.25.0
+ '@esbuild/openbsd-x64': 0.25.0
+ '@esbuild/sunos-x64': 0.25.0
+ '@esbuild/win32-arm64': 0.25.0
+ '@esbuild/win32-ia32': 0.25.0
+ '@esbuild/win32-x64': 0.25.0
- esbuild@0.24.0:
+ esbuild@0.25.1:
optionalDependencies:
- '@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
+ '@esbuild/aix-ppc64': 0.25.1
+ '@esbuild/android-arm': 0.25.1
+ '@esbuild/android-arm64': 0.25.1
+ '@esbuild/android-x64': 0.25.1
+ '@esbuild/darwin-arm64': 0.25.1
+ '@esbuild/darwin-x64': 0.25.1
+ '@esbuild/freebsd-arm64': 0.25.1
+ '@esbuild/freebsd-x64': 0.25.1
+ '@esbuild/linux-arm': 0.25.1
+ '@esbuild/linux-arm64': 0.25.1
+ '@esbuild/linux-ia32': 0.25.1
+ '@esbuild/linux-loong64': 0.25.1
+ '@esbuild/linux-mips64el': 0.25.1
+ '@esbuild/linux-ppc64': 0.25.1
+ '@esbuild/linux-riscv64': 0.25.1
+ '@esbuild/linux-s390x': 0.25.1
+ '@esbuild/linux-x64': 0.25.1
+ '@esbuild/netbsd-arm64': 0.25.1
+ '@esbuild/netbsd-x64': 0.25.1
+ '@esbuild/openbsd-arm64': 0.25.1
+ '@esbuild/openbsd-x64': 0.25.1
+ '@esbuild/sunos-x64': 0.25.1
+ '@esbuild/win32-arm64': 0.25.1
+ '@esbuild/win32-ia32': 0.25.1
+ '@esbuild/win32-x64': 0.25.1
escalade@3.1.1: {}
+ escalade@3.2.0: {}
+
escape-goat@3.0.0: {}
escape-html@1.0.3: {}
@@ -17502,17 +17539,27 @@ snapshots:
transitivePeerDependencies:
- supports-color
- 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):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0):
dependencies:
debug: 3.2.7(supports-color@8.1.1)
optionalDependencies:
- '@typescript-eslint/parser': 7.17.0(eslint@9.14.0)(typescript@5.6.3)
- eslint: 9.14.0
+ '@typescript-eslint/parser': 8.26.0(eslint@9.22.0)(typescript@5.8.2)
+ eslint: 9.22.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.14.0)(typescript@5.6.3))(eslint@9.14.0):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0):
+ dependencies:
+ debug: 3.2.7(supports-color@8.1.1)
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.27.0(eslint@9.22.0)(typescript@5.8.2)
+ eslint: 9.22.0
+ eslint-import-resolver-node: 0.3.9
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -17521,9 +17568,9 @@ snapshots:
array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
- eslint: 9.14.0
+ eslint: 9.22.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)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.26.0(eslint@9.22.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -17532,15 +17579,16 @@ snapshots:
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)
+ '@typescript-eslint/parser': 8.26.0(eslint@9.22.0)(typescript@5.8.2)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- 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-plugin-import@2.31.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint@9.22.0):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -17549,9 +17597,9 @@ snapshots:
array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
- eslint: 9.14.0
+ eslint: 9.22.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)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.22.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -17563,25 +17611,22 @@ snapshots:
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)
+ '@typescript-eslint/parser': 8.27.0(eslint@9.22.0)(typescript@5.8.2)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-vue@9.31.0(eslint@9.14.0):
+ eslint-plugin-vue@10.0.0(eslint@9.22.0)(vue-eslint-parser@10.1.1(eslint@9.22.0)):
dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@9.14.0)
- eslint: 9.14.0
- globals: 13.24.0
+ '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0)
+ eslint: 9.22.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.14.0)
+ vue-eslint-parser: 10.1.1(eslint@9.22.0)
xml-name-validator: 4.0.0
- transitivePeerDependencies:
- - supports-color
eslint-rule-docs@1.1.235: {}
@@ -17595,6 +17640,11 @@ snapshots:
esrecurse: 4.3.0
estraverse: 5.3.0
+ eslint-scope@8.3.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
eslint-visitor-keys@3.4.3: {}
eslint-visitor-keys@4.2.0: {}
@@ -17682,24 +17732,60 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ eslint@9.22.0:
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0)
+ '@eslint-community/regexpp': 4.12.1
+ '@eslint/config-array': 0.19.2
+ '@eslint/config-helpers': 0.1.0
+ '@eslint/core': 0.12.0
+ '@eslint/eslintrc': 3.3.1
+ '@eslint/js': 9.22.0
+ '@eslint/plugin-kit': 0.2.7
+ '@humanfs/node': 0.16.6
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.2
+ '@types/estree': 1.0.6
+ '@types/json-schema': 7.0.15
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.3.7(supports-color@8.1.1)
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.3.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
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.1
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ transitivePeerDependencies:
+ - supports-color
+
espree@10.3.0:
dependencies:
- acorn: 8.14.0
- acorn-jsx: 5.3.2(acorn@8.14.0)
+ acorn: 8.14.1
+ acorn-jsx: 5.3.2(acorn@8.14.1)
eslint-visitor-keys: 4.2.0
espree@9.6.1:
dependencies:
- acorn: 8.14.0
- acorn-jsx: 5.3.2(acorn@8.14.0)
+ acorn: 8.14.1
+ acorn-jsx: 5.3.2(acorn@8.14.1)
eslint-visitor-keys: 3.4.3
esprima@4.0.1: {}
- esquery@1.4.2:
- dependencies:
- estraverse: 5.3.0
-
esquery@1.6.0:
dependencies:
estraverse: 5.3.0
@@ -17741,16 +17827,6 @@ snapshots:
events@3.3.0: {}
- execa@0.7.0:
- dependencies:
- cross-spawn: 5.1.0
- get-stream: 3.0.0
- is-stream: 1.1.0
- npm-run-path: 2.0.2
- p-finally: 1.0.0
- signal-exit: 3.0.7
- strip-eof: 1.0.0
-
execa@4.1.0:
dependencies:
cross-spawn: 7.0.3
@@ -17800,14 +17876,32 @@ snapshots:
signal-exit: 4.1.0
strip-final-newline: 3.0.0
+ execa@9.5.2:
+ 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.2.0
+ signal-exit: 4.1.0
+ strip-final-newline: 4.0.0
+ yoctocolors: 2.1.1
+
executable@4.1.1:
dependencies:
pify: 2.3.0
+ optional: true
exit@0.1.2: {}
expand-template@2.0.3: {}
+ expect-type@1.2.0: {}
+
expect@29.7.0:
dependencies:
'@jest/expect-utils': 29.7.0
@@ -17818,14 +17912,14 @@ snapshots:
exponential-backoff@3.1.1: {}
- 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
@@ -17854,7 +17948,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- express@4.21.1:
+ express@4.21.2:
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
@@ -17875,7 +17969,7 @@ snapshots:
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
- path-to-regexp: 0.1.10
+ path-to-regexp: 0.1.12
proxy-addr: 2.0.7
qs: 6.13.0
range-parser: 1.2.1
@@ -17922,7 +18016,7 @@ snapshots:
fast-fifo@1.3.0: {}
- fast-glob@3.3.2:
+ fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
@@ -17956,10 +18050,6 @@ snapshots:
fast-uri@3.0.1: {}
- fast-xml-parser@4.2.5:
- dependencies:
- strnum: 1.0.5
-
fast-xml-parser@4.4.1:
dependencies:
strnum: 1.0.5
@@ -17989,11 +18079,12 @@ snapshots:
raw-body: 3.0.0
secure-json-parse: 2.7.0
- fastify@5.0.0:
+ fastify@5.2.1:
dependencies:
'@fastify/ajv-compiler': 4.0.1
'@fastify/error': 4.0.0
'@fastify/fast-json-stringify-compiler': 5.0.1
+ '@fastify/proxy-addr': 5.0.0
abstract-logging: 2.0.1
avvio: 9.0.0
fast-json-stringify: 6.0.0
@@ -18001,10 +18092,9 @@ snapshots:
light-my-request: 6.1.0
pino: 9.2.0
process-warning: 4.0.0
- proxy-addr: 2.0.7
rfdc: 1.4.1
- secure-json-parse: 2.7.0
- semver: 7.6.0
+ secure-json-parse: 3.0.2
+ semver: 7.6.3
toad-cache: 3.7.0
fastq@1.17.1:
@@ -18034,6 +18124,10 @@ snapshots:
escape-string-regexp: 1.0.5
optional: true
+ figures@6.1.0:
+ dependencies:
+ is-unicode-supported: 2.1.0
+
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.2.0
@@ -18042,12 +18136,6 @@ snapshots:
dependencies:
flat-cache: 4.0.1
- file-type@17.1.6:
- dependencies:
- readable-web-to-node-stream: 3.0.2
- strtok3: 7.0.0
- token-types: 5.0.1
-
file-type@19.6.0:
dependencies:
get-stream: 9.0.1
@@ -18055,21 +18143,11 @@ snapshots:
token-types: 6.0.0
uint8array-extras: 1.4.0
- filelist@1.0.4:
- dependencies:
- minimatch: 5.1.2
-
filename-reserved-regex@3.0.0: {}
- filenamify@5.1.1:
+ filenamify@6.0.0:
dependencies:
filename-reserved-regex: 3.0.0
- strip-outer: 2.0.0
- trim-repeated: 2.0.0
-
- fill-range@7.0.1:
- dependencies:
- to-regex-range: 5.0.1
fill-range@7.1.1:
dependencies:
@@ -18113,7 +18191,7 @@ snapshots:
dependencies:
aggregate-error: 5.0.0
execa: 8.0.1
- pid-port: 1.0.0
+ pid-port: 1.0.2
process-exists: 5.0.0
ps-list: 8.1.1
taskkill: 5.0.0
@@ -18138,9 +18216,9 @@ snapshots:
follow-redirects@1.15.2: {}
- follow-redirects@1.15.9(debug@4.3.7):
+ follow-redirects@1.15.9(debug@4.4.0):
optionalDependencies:
- debug: 4.3.7(supports-color@8.1.1)
+ debug: 4.4.0
for-each@0.3.3:
dependencies:
@@ -18164,16 +18242,19 @@ snapshots:
combined-stream: 1.0.8
mime-types: 2.1.35
- form-data@4.0.1:
+ form-data@4.0.2:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
mime-types: 2.1.35
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
+ forwarded-parse@2.1.2: {}
+
forwarded@0.2.0: {}
fresh@0.5.2: {}
@@ -18182,11 +18263,11 @@ snapshots:
fs-constants@1.0.0: {}
- fs-extra@7.0.1:
+ fs-extra@11.3.0:
dependencies:
graceful-fs: 4.2.11
- jsonfile: 4.0.0
- universalify: 0.1.2
+ jsonfile: 6.1.0
+ universalify: 2.0.0
fs-extra@8.1.0:
dependencies:
@@ -18217,13 +18298,6 @@ snapshots:
function-bind@1.1.2: {}
- function.prototype.name@1.1.5:
- dependencies:
- call-bind: 1.0.2
- define-properties: 1.2.0
- es-abstract: 1.22.1
- functions-have-names: 1.2.3
-
function.prototype.name@1.1.6:
dependencies:
call-bind: 1.0.7
@@ -18237,8 +18311,6 @@ snapshots:
get-caller-file@2.0.5: {}
- get-func-name@2.0.2: {}
-
get-intrinsic@1.2.1:
dependencies:
function-bind: 1.1.2
@@ -18254,13 +18326,30 @@ snapshots:
has-symbols: 1.0.3
hasown: 2.0.2
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
get-package-type@0.1.0: {}
- get-stream@3.0.0: {}
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
get-stream@5.2.0:
dependencies:
pump: 3.0.0
+ optional: true
get-stream@6.0.1: {}
@@ -18271,18 +18360,13 @@ snapshots:
'@sec-ant/readable-stream': 0.4.1
is-stream: 4.0.1
- get-symbol-description@1.0.0:
- dependencies:
- call-bind: 1.0.2
- get-intrinsic: 1.2.1
-
get-symbol-description@1.0.2:
dependencies:
call-bind: 1.0.7
es-errors: 1.3.0
get-intrinsic: 1.2.4
- get-tsconfig@4.7.2:
+ get-tsconfig@4.10.0:
dependencies:
resolve-pkg-maps: 1.0.0
@@ -18305,19 +18389,23 @@ snapshots:
dependencies:
is-glob: 4.0.3
- glob-promise@4.2.2(glob@7.2.3):
- dependencies:
- '@types/glob': 7.2.0
- glob: 7.2.3
-
glob@10.3.10:
dependencies:
foreground-child: 3.1.1
jackspeak: 2.3.6
- minimatch: 9.0.3
- minipass: 7.0.4
+ minimatch: 9.0.4
+ minipass: 7.1.2
path-scurry: 1.10.1
+ glob@10.4.5:
+ dependencies:
+ foreground-child: 3.1.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.4
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.0
+ path-scurry: 1.11.1
+
glob@11.0.0:
dependencies:
foreground-child: 3.1.1
@@ -18327,6 +18415,15 @@ snapshots:
package-json-from-dist: 1.0.0
path-scurry: 2.0.0
+ glob@11.0.1:
+ dependencies:
+ foreground-child: 3.1.1
+ jackspeak: 4.0.1
+ minimatch: 10.0.1
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.0
+ path-scurry: 2.0.0
+
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
@@ -18357,7 +18454,7 @@ snapshots:
globals@14.0.0: {}
- globals@15.12.0: {}
+ globals@16.0.0: {}
globalthis@1.0.3:
dependencies:
@@ -18367,7 +18464,7 @@ snapshots:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
- fast-glob: 3.3.2
+ fast-glob: 3.3.3
ignore: 5.3.1
merge2: 1.4.1
slash: 3.0.0
@@ -18376,21 +18473,9 @@ snapshots:
dependencies:
get-intrinsic: 1.2.4
- got@11.8.5:
- dependencies:
- '@sindresorhus/is': 4.6.0
- '@szmarczak/http-timer': 4.0.6
- '@types/cacheable-request': 6.0.3
- '@types/responselike': 1.0.0
- cacheable-lookup: 5.0.4
- cacheable-request: 7.0.2
- decompress-response: 6.0.0
- http2-wrapper: 1.0.3
- lowercase-keys: 2.0.0
- p-cancelable: 2.1.1
- responselike: 2.0.1
+ gopd@1.2.0: {}
- got@12.6.1:
+ got@13.0.0:
dependencies:
'@sindresorhus/is': 5.3.0
'@szmarczak/http-timer': 5.0.1
@@ -18404,7 +18489,7 @@ snapshots:
p-cancelable: 3.0.0
responselike: 3.0.0
- got@14.4.4:
+ got@14.4.6:
dependencies:
'@sindresorhus/is': 7.0.1
'@szmarczak/http-timer': 5.0.1
@@ -18420,26 +18505,19 @@ snapshots:
graceful-fs@4.2.11: {}
- grapheme-splitter@1.0.4: {}
-
graphemer@1.4.0: {}
graphql@16.8.1: {}
hammerjs@2.0.8: {}
- happy-dom@10.0.3:
+ happy-dom@16.8.1:
dependencies:
- css.escape: 1.5.1
- entities: 4.5.0
- iconv-lite: 0.6.3
webidl-conversions: 7.0.0
- whatwg-encoding: 2.0.0
whatwg-mimetype: 3.0.0
- happy-dom@15.11.4:
+ happy-dom@17.4.4:
dependencies:
- entities: 4.5.0
webidl-conversions: 7.0.0
whatwg-mimetype: 3.0.0
@@ -18465,6 +18543,8 @@ snapshots:
has-symbols@1.0.3: {}
+ has-symbols@1.1.0: {}
+
has-tostringtag@1.0.0:
dependencies:
has-symbols: 1.0.3
@@ -18487,7 +18567,7 @@ snapshots:
dependencies:
function-bind: 1.1.2
- hast-util-to-html@9.0.3:
+ hast-util-to-html@9.0.5:
dependencies:
'@types/hast': 3.0.4
'@types/unist': 3.0.2
@@ -18496,7 +18576,7 @@ snapshots:
hast-util-whitespace: 3.0.0
html-void-elements: 3.0.0
mdast-util-to-hast: 13.2.0
- property-information: 6.5.0
+ property-information: 7.0.0
space-separated-tokens: 2.0.2
stringify-entities: 4.0.4
zwitch: 2.0.4
@@ -18525,7 +18605,7 @@ snapshots:
dependencies:
whatwg-encoding: 3.1.1
- html-entities@2.3.2: {}
+ html-entities@2.5.2: {}
html-escaper@2.0.2: {}
@@ -18569,7 +18649,7 @@ snapshots:
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.0
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -18580,11 +18660,6 @@ snapshots:
sshpk: 1.18.0
optional: true
- http2-wrapper@1.0.3:
- dependencies:
- quick-lru: 5.1.1
- resolve-alpn: 1.2.1
-
http2-wrapper@2.2.1:
dependencies:
quick-lru: 5.1.1
@@ -18602,7 +18677,14 @@ snapshots:
https-proxy-agent@7.0.5:
dependencies:
agent-base: 7.1.0
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.7(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.3
+ debug: 4.3.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@@ -18615,6 +18697,8 @@ snapshots:
human-signals@5.0.0: {}
+ human-signals@8.0.0: {}
+
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@@ -18629,13 +18713,13 @@ snapshots:
ignore-by-default@1.0.1: {}
- ignore-walk@6.0.5:
+ ignore-walk@7.0.0:
dependencies:
minimatch: 9.0.4
ignore@5.3.1: {}
- immutable@4.2.2: {}
+ immutable@5.0.3: {}
import-fresh@3.3.0:
dependencies:
@@ -18644,8 +18728,8 @@ snapshots:
import-in-the-middle@1.11.2:
dependencies:
- acorn: 8.14.0
- acorn-import-attributes: 1.9.5(acorn@8.14.0)
+ acorn: 8.14.1
+ acorn-import-attributes: 1.9.5(acorn@8.14.1)
cjs-module-lexer: 1.2.2
module-details-from-path: 1.0.3
@@ -18676,6 +18760,10 @@ snapshots:
insert-text-at-cursor@0.3.0: {}
+ inspect-with-kind@1.0.5:
+ dependencies:
+ kind-of: 6.0.3
+
install-artifact-from-github@1.3.5: {}
internal-slot@1.0.5:
@@ -18688,15 +18776,15 @@ snapshots:
dependencies:
es-errors: 1.3.0
hasown: 2.0.2
- side-channel: 1.0.4
+ side-channel: 1.0.6
intersection-observer@0.12.2: {}
- ioredis@5.4.1:
+ ioredis@5.6.0:
dependencies:
'@ioredis/commands': 1.2.0
cluster-key-slot: 1.1.2
- debug: 4.3.4
+ debug: 4.3.7(supports-color@8.1.1)
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
@@ -18715,7 +18803,7 @@ snapshots:
dependencies:
ip-address: 9.0.5
- ip-regex@4.3.0: {}
+ ip-regex@5.0.0: {}
ip@2.0.1: {}
@@ -18749,10 +18837,6 @@ snapshots:
dependencies:
has-bigints: 1.0.2
- is-binary-path@2.1.0:
- dependencies:
- binary-extensions: 2.2.0
-
is-boolean-object@1.1.2:
dependencies:
call-bind: 1.0.2
@@ -18805,16 +18889,10 @@ snapshots:
is-path-inside: 3.0.3
optional: true
- is-ip@3.1.0:
- dependencies:
- ip-regex: 4.3.0
-
is-lambda@1.0.1: {}
is-map@2.0.2: {}
- is-negative-zero@2.0.2: {}
-
is-negative-zero@2.0.3: {}
is-node-process@1.2.0: {}
@@ -18852,8 +18930,6 @@ snapshots:
dependencies:
call-bind: 1.0.7
- is-stream@1.1.0: {}
-
is-stream@2.0.1: {}
is-stream@3.0.0: {}
@@ -18889,6 +18965,8 @@ snapshots:
is-unicode-supported@0.1.0: {}
+ is-unicode-supported@2.1.0: {}
+
is-weakmap@2.0.1: {}
is-weakref@1.0.2:
@@ -18904,8 +18982,6 @@ snapshots:
dependencies:
is-docker: 2.2.1
- isarray@0.0.1: {}
-
isarray@1.0.0: {}
isarray@2.0.5: {}
@@ -18953,10 +19029,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- istanbul-lib-source-maps@5.0.4:
+ istanbul-lib-source-maps@5.0.6:
dependencies:
'@jridgewell/trace-mapping': 0.3.25
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.4.0
istanbul-lib-coverage: 3.2.2
transitivePeerDependencies:
- supports-color
@@ -18966,6 +19042,11 @@ snapshots:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
+ istanbul-reports@3.1.7:
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+
iterare@1.2.1: {}
jackspeak@2.3.6:
@@ -18974,18 +19055,17 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
- jackspeak@4.0.1:
+ jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
- jake@10.8.5:
+ jackspeak@4.0.1:
dependencies:
- async: 3.2.4
- chalk: 4.1.2
- filelist: 1.0.4
- minimatch: 3.1.2
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
jest-changed-files@29.7.0:
dependencies:
@@ -18999,7 +19079,7 @@ snapshots:
'@jest/expect': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
chalk: 4.1.2
co: 4.6.0
dedent: 1.3.0
@@ -19019,16 +19099,16 @@ snapshots:
- babel-plugin-macros
- supports-color
- jest-cli@29.7.0(@types/node@22.9.0):
+ jest-cli@29.7.0(@types/node@22.13.10):
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@22.9.0)
+ create-jest: 29.7.0(@types/node@22.13.10)
exit: 0.1.2
import-local: 3.1.0
- jest-config: 29.7.0(@types/node@22.9.0)
+ jest-config: 29.7.0(@types/node@22.13.10)
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@@ -19038,7 +19118,56 @@ snapshots:
- supports-color
- ts-node
- jest-config@29.7.0(@types/node@22.9.0):
+ jest-cli@29.7.0(@types/node@22.13.9):
+ 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@22.13.9)
+ exit: 0.1.2
+ import-local: 3.1.0
+ jest-config: 29.7.0(@types/node@22.13.9)
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ jest-config@29.7.0(@types/node@22.13.10):
+ dependencies:
+ '@babel/core': 7.23.5
+ '@jest/test-sequencer': 29.7.0
+ '@jest/types': 29.6.3
+ babel-jest: 29.7.0(@babel/core@7.23.5)
+ chalk: 4.1.2
+ ci-info: 3.7.1
+ deepmerge: 4.2.2
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-get-type: 29.6.3
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-runner: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ '@types/node': 22.13.10
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-config@29.7.0(@types/node@22.13.11):
dependencies:
'@babel/core': 7.23.5
'@jest/test-sequencer': 29.7.0
@@ -19063,7 +19192,37 @@ snapshots:
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-config@29.7.0(@types/node@22.13.9):
+ dependencies:
+ '@babel/core': 7.23.5
+ '@jest/test-sequencer': 29.7.0
+ '@jest/types': 29.6.3
+ babel-jest: 29.7.0(@babel/core@7.23.5)
+ chalk: 4.1.2
+ ci-info: 3.7.1
+ deepmerge: 4.2.2
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-get-type: 29.6.3
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-runner: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ '@types/node': 22.13.9
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@@ -19092,7 +19251,7 @@ snapshots:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -19109,7 +19268,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.6
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@@ -19148,7 +19307,7 @@ snapshots:
jest-mock@29.7.0:
dependencies:
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
jest-util: 29.7.0
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@@ -19183,7 +19342,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.11
@@ -19211,7 +19370,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
chalk: 4.1.2
cjs-module-lexer: 1.2.2
collect-v8-coverage: 1.0.1
@@ -19257,7 +19416,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
chalk: 4.1.2
ci-info: 3.7.1
graceful-fs: 4.2.11
@@ -19276,7 +19435,7 @@ snapshots:
dependencies:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@@ -19290,17 +19449,29 @@ snapshots:
jest-worker@29.7.0:
dependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
- jest@29.7.0(@types/node@22.9.0):
+ jest@29.7.0(@types/node@22.13.10):
+ dependencies:
+ '@jest/core': 29.7.0
+ '@jest/types': 29.6.3
+ import-local: 3.1.0
+ jest-cli: 29.7.0(@types/node@22.13.10)
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ jest@29.7.0(@types/node@22.13.9):
dependencies:
'@jest/core': 29.7.0
'@jest/types': 29.6.3
import-local: 3.1.0
- jest-cli: 29.7.0(@types/node@22.9.0)
+ jest-cli: 29.7.0(@types/node@22.13.9)
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@@ -19336,8 +19507,6 @@ snapshots:
js-tokens@4.0.0: {}
- js-tokens@9.0.0: {}
-
js-yaml@3.14.1:
dependencies:
argparse: 1.0.10
@@ -19351,32 +19520,32 @@ snapshots:
jsbn@1.1.0: {}
- jschardet@3.0.0: {}
+ jschardet@3.1.4: {}
jsdoc-type-pratt-parser@4.1.0: {}
- jsdom@24.1.1:
+ jsdom@26.0.0:
dependencies:
- cssstyle: 4.0.1
+ cssstyle: 4.3.0
data-urls: 5.0.0
decimal.js: 10.4.3
- form-data: 4.0.1
+ form-data: 4.0.2
html-encoding-sniffer: 4.0.0
http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.5
+ https-proxy-agent: 7.0.6
is-potential-custom-element-name: 1.0.1
- nwsapi: 2.2.12
+ nwsapi: 2.2.19
parse5: 7.2.1
- rrweb-cssom: 0.7.1
+ rrweb-cssom: 0.8.0
saxes: 6.0.0
symbol-tree: 3.2.4
- tough-cookie: 4.1.4
+ tough-cookie: 5.0.0
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.8)(utf-8-validate@6.0.4)
+ whatwg-url: 14.2.0
+ ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)
xml-name-validator: 5.0.0
transitivePeerDependencies:
- bufferutil
@@ -19384,28 +19553,28 @@ snapshots:
- utf-8-validate
optional: true
- jsdom@24.1.1(bufferutil@4.0.7)(canvas@3.1.0)(utf-8-validate@6.0.3):
+ jsdom@26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5):
dependencies:
- cssstyle: 4.0.1
+ cssstyle: 4.3.0
data-urls: 5.0.0
decimal.js: 10.4.3
- form-data: 4.0.1
+ form-data: 4.0.2
html-encoding-sniffer: 4.0.0
http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.5
+ https-proxy-agent: 7.0.6
is-potential-custom-element-name: 1.0.1
- nwsapi: 2.2.12
+ nwsapi: 2.2.19
parse5: 7.2.1
- rrweb-cssom: 0.7.1
+ rrweb-cssom: 0.8.0
saxes: 6.0.0
symbol-tree: 3.2.4
- tough-cookie: 4.1.4
+ tough-cookie: 5.0.0
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)
+ whatwg-url: 14.2.0
+ ws: 8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)
xml-name-validator: 5.0.0
optionalDependencies:
canvas: 3.1.0
@@ -19414,35 +19583,6 @@ snapshots:
- supports-color
- utf-8-validate
- jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4):
- 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.8)(utf-8-validate@6.0.4)
- xml-name-validator: 5.0.0
- transitivePeerDependencies:
- - bufferutil
- - supports-color
- - utf-8-validate
- optional: true
-
jsesc@2.5.2: {}
json-buffer@3.0.1: {}
@@ -19463,11 +19603,6 @@ snapshots:
json-stringify-safe@5.0.1: {}
- json-to-ast@2.1.0:
- dependencies:
- code-error-fragment: 0.0.230
- grapheme-splitter: 1.0.4
-
json5@1.0.2:
dependencies:
minimist: 1.2.8
@@ -19491,9 +19626,8 @@ snapshots:
universalify: 2.0.0
optionalDependencies:
graceful-fs: 4.2.11
- optional: true
- jsonld@8.3.2(web-streams-polyfill@4.0.0):
+ jsonld@8.3.3(web-streams-polyfill@4.0.0):
dependencies:
'@digitalbazaar/http-client': 3.4.1(web-streams-polyfill@4.0.0)
canonicalize: 1.0.8
@@ -19526,15 +19660,16 @@ snapshots:
is-promise: 2.2.2
promise: 7.3.1
- juice@11.0.0:
+ juice@11.0.1:
dependencies:
cheerio: 1.0.0
commander: 12.1.0
+ entities: 4.5.0
mensch: 0.3.4
slick: 1.12.2
web-resource-inliner: 7.0.0
- just-extend@4.2.1: {}
+ just-extend@6.2.0: {}
jwa@2.0.0:
dependencies:
@@ -19588,7 +19723,7 @@ snapshots:
process-warning: 4.0.0
set-cookie-parser: 2.6.0
- lilconfig@3.1.1: {}
+ lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
@@ -19599,18 +19734,13 @@ snapshots:
log-update: 4.0.0
p-map: 4.0.0
rfdc: 1.4.1
- rxjs: 7.8.1
+ rxjs: 7.8.2
through: 2.3.8
wrap-ansi: 7.0.0
optionalDependencies:
enquirer: 2.3.6
optional: true
- local-pkg@0.5.0:
- dependencies:
- mlly: 1.5.0
- pkg-types: 1.0.3
-
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
@@ -19651,28 +19781,17 @@ snapshots:
longest-streak@3.1.0: {}
- loose-envify@1.4.0:
- dependencies:
- js-tokens: 4.0.0
-
- loupe@2.3.7:
- dependencies:
- get-func-name: 2.0.2
-
loupe@3.1.2: {}
- lowercase-keys@2.0.0: {}
+ loupe@3.1.3: {}
lowercase-keys@3.0.0: {}
lru-cache@10.2.2: {}
- lru-cache@11.0.0: {}
+ lru-cache@10.4.3: {}
- lru-cache@4.1.5:
- dependencies:
- pseudomap: 1.0.2
- yallist: 2.1.2
+ lru-cache@11.0.0: {}
lru-cache@5.1.1:
dependencies:
@@ -19694,25 +19813,21 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
- magic-string@0.30.10:
- dependencies:
- '@jridgewell/sourcemap-codec': 1.4.15
-
- magic-string@0.30.11:
+ magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
- magicast@0.3.4:
+ magicast@0.3.5:
dependencies:
- '@babel/parser': 7.24.7
- '@babel/types': 7.24.7
- source-map-js: 1.2.0
+ '@babel/parser': 7.25.7
+ '@babel/types': 7.25.7
+ source-map-js: 1.2.1
mailcheck@1.1.1: {}
make-dir@4.0.0:
dependencies:
- semver: 7.6.0
+ semver: 7.6.3
make-error@1.3.6: {}
@@ -19748,7 +19863,7 @@ snapshots:
marked@4.3.0: {}
- matter-js@0.19.0: {}
+ math-intrinsics@1.1.0: {}
matter-js@0.20.0: {}
@@ -19871,7 +19986,7 @@ snapshots:
media-typer@0.3.0: {}
- meilisearch@0.45.0: {}
+ meilisearch@0.49.0: {}
memoizerific@1.11.3:
dependencies:
@@ -20118,8 +20233,6 @@ snapshots:
mimic-fn@4.0.0: {}
- mimic-response@1.0.1: {}
-
mimic-response@3.1.0: {}
mimic-response@4.0.0: {}
@@ -20194,8 +20307,6 @@ snapshots:
minipass@5.0.0: {}
- minipass@7.0.4: {}
-
minipass@7.1.2: {}
minizlib@2.1.2:
@@ -20203,6 +20314,11 @@ snapshots:
minipass: 3.3.6
yallist: 4.0.0
+ minizlib@3.0.1:
+ dependencies:
+ minipass: 7.1.2
+ rimraf: 5.0.10
+
mkdirp-classic@0.5.3: {}
mkdirp@0.5.6:
@@ -20211,16 +20327,9 @@ snapshots:
mkdirp@1.0.4: {}
- mkdirp@2.1.6: {}
+ mkdirp@3.0.1: {}
- mlly@1.5.0:
- dependencies:
- acorn: 8.14.0
- pathe: 1.1.2
- pkg-types: 1.0.3
- ufo: 1.3.2
-
- mnemonist@0.39.8:
+ mnemonist@0.40.0:
dependencies:
obliterator: 2.0.4
@@ -20254,33 +20363,33 @@ snapshots:
optionalDependencies:
msgpackr-extract: 3.0.2
- msw-storybook-addon@2.0.4(msw@2.6.4(@types/node@22.9.0)(typescript@5.6.3)):
+ msw-storybook-addon@2.0.4(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2)):
dependencies:
is-node-process: 1.2.0
- msw: 2.6.4(@types/node@22.9.0)(typescript@5.6.3)
+ msw: 2.7.3(@types/node@22.13.11)(typescript@5.8.2)
- msw@2.6.4(@types/node@22.9.0)(typescript@5.6.3):
+ msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2):
dependencies:
'@bundled-es-modules/cookie': 2.0.1
'@bundled-es-modules/statuses': 1.0.1
'@bundled-es-modules/tough-cookie': 0.1.6
- '@inquirer/confirm': 5.0.2(@types/node@22.9.0)
- '@mswjs/interceptors': 0.36.10
+ '@inquirer/confirm': 5.0.2(@types/node@22.13.11)
+ '@mswjs/interceptors': 0.37.6
'@open-draft/deferred-promise': 2.2.0
'@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.3
path-to-regexp: 6.3.0
+ picocolors: 1.1.1
strict-event-emitter: 0.5.1
type-fest: 4.27.0
yargs: 17.7.2
optionalDependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
transitivePeerDependencies:
- '@types/node'
@@ -20308,9 +20417,9 @@ snapshots:
nan@2.20.0: {}
- nanoid@3.3.7: {}
+ nanoid@3.3.11: {}
- nanoid@5.0.8: {}
+ nanoid@5.1.5: {}
napi-build-utils@2.0.0: {}
@@ -20335,16 +20444,16 @@ snapshots:
nice-napi@1.0.2:
dependencies:
node-addon-api: 3.2.1
- node-gyp-build: 4.6.0
+ node-gyp-build: 4.8.1
optional: true
- nise@5.1.4:
+ nise@6.1.1:
dependencies:
- '@sinonjs/commons': 2.0.0
- '@sinonjs/fake-timers': 10.3.0
- '@sinonjs/text-encoding': 0.7.2
- just-extend: 4.2.1
- path-to-regexp: 1.8.0
+ '@sinonjs/commons': 3.0.1
+ '@sinonjs/fake-timers': 13.0.5
+ '@sinonjs/text-encoding': 0.7.3
+ just-extend: 6.2.0
+ path-to-regexp: 8.2.0
node-abi@3.62.0:
dependencies:
@@ -20374,9 +20483,6 @@ snapshots:
node-gyp-build-optional-packages@5.0.7:
optional: true
- node-gyp-build@4.6.0:
- optional: true
-
node-gyp-build@4.8.1: {}
node-gyp@10.2.0:
@@ -20398,11 +20504,13 @@ snapshots:
node-releases@2.0.14: {}
- nodemailer@6.9.16: {}
+ node-releases@2.0.19: {}
+
+ nodemailer@6.10.0: {}
nodemon@3.1.7:
dependencies:
- chokidar: 3.5.3
+ chokidar: 4.0.3
debug: 4.3.5(supports-color@5.5.0)
ignore-by-default: 1.0.1
minimatch: 3.1.2
@@ -20413,6 +20521,19 @@ snapshots:
touch: 3.1.0
undefsafe: 2.0.5
+ nodemon@3.1.9:
+ dependencies:
+ chokidar: 4.0.3
+ debug: 4.3.7(supports-color@5.5.0)
+ ignore-by-default: 1.0.1
+ minimatch: 3.1.2
+ pstree.remy: 1.1.8
+ semver: 7.6.3
+ simple-update-notifier: 2.0.0
+ supports-color: 5.5.0
+ touch: 3.1.0
+ undefsafe: 2.0.5
+
nofilter@3.1.0: {}
nopt@1.0.10:
@@ -20443,14 +20564,8 @@ snapshots:
normalize-path@3.0.0: {}
- normalize-url@6.1.0: {}
-
normalize-url@8.0.1: {}
- npm-run-path@2.0.2:
- dependencies:
- path-key: 2.0.1
-
npm-run-path@4.0.1:
dependencies:
path-key: 3.1.1
@@ -20463,11 +20578,16 @@ 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
- nwsapi@2.2.12: {}
+ nwsapi@2.2.19: {}
oauth2orize-pkce@0.1.2: {}
@@ -20481,6 +20601,8 @@ snapshots:
oauth@0.10.0: {}
+ oauth@0.10.2: {}
+
object-assign-deep@0.4.0: {}
object-assign@4.1.1: {}
@@ -20553,9 +20675,14 @@ snapshots:
dependencies:
mimic-fn: 4.0.0
- oniguruma-to-js@0.4.3:
+ oniguruma-parser@0.5.4: {}
+
+ oniguruma-to-es@4.1.0:
dependencies:
- regex: 4.4.0
+ emoji-regex-xs: 1.0.0
+ oniguruma-parser: 0.5.4
+ regex: 6.0.1
+ regex-recursion: 6.0.2
open@8.4.2:
dependencies:
@@ -20565,13 +20692,13 @@ snapshots:
openapi-types@12.1.3: {}
- openapi-typescript@6.7.3:
+ openapi-typescript@6.7.6:
dependencies:
ansi-colors: 4.1.3
- fast-glob: 3.3.2
+ fast-glob: 3.3.3
js-yaml: 4.1.0
supports-color: 9.4.0
- undici: 5.28.2
+ undici: 5.29.0
yargs-parser: 21.1.1
optionator@0.9.4:
@@ -20583,23 +20710,17 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
- os-filter-obj@2.0.0:
- dependencies:
- arch: 2.2.0
-
os-utils@0.0.14: {}
ospath@1.2.2:
optional: true
- otpauth@9.3.4:
+ otpauth@9.3.6:
dependencies:
- '@noble/hashes': 1.5.0
+ '@noble/hashes': 1.6.1
outvariant@1.4.3: {}
- p-cancelable@2.1.1: {}
-
p-cancelable@3.0.0: {}
p-cancelable@4.0.1: {}
@@ -20614,10 +20735,6 @@ snapshots:
dependencies:
yocto-queue: 0.1.0
- p-limit@5.0.0:
- dependencies:
- yocto-queue: 1.0.0
-
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
@@ -20658,6 +20775,8 @@ snapshots:
dependencies:
xtend: 4.0.2
+ parse-ms@4.0.0: {}
+
parse-srcset@1.0.2: {}
parse5-htmlparser2-tree-adapter@6.0.1:
@@ -20689,8 +20808,6 @@ snapshots:
path-is-absolute@1.0.1: {}
- path-key@2.0.1: {}
-
path-key@3.1.1: {}
path-key@4.0.0: {}
@@ -20700,7 +20817,12 @@ snapshots:
path-scurry@1.10.1:
dependencies:
lru-cache: 10.2.2
- minipass: 7.0.4
+ minipass: 7.1.2
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.2.2
+ minipass: 7.1.2
path-scurry@2.0.0:
dependencies:
@@ -20709,19 +20831,15 @@ snapshots:
path-to-regexp@0.1.10: {}
- path-to-regexp@1.8.0:
- dependencies:
- isarray: 0.0.1
-
- path-to-regexp@3.3.0: {}
+ path-to-regexp@0.1.12: {}
path-to-regexp@6.3.0: {}
- path-type@4.0.0: {}
+ path-to-regexp@8.2.0: {}
- pathe@1.1.2: {}
+ path-type@4.0.0: {}
- pathval@1.1.1: {}
+ pathe@2.0.3: {}
pathval@2.0.0: {}
@@ -20729,12 +20847,9 @@ snapshots:
dependencies:
through: 2.3.8
- peek-readable@5.2.0: {}
-
peek-readable@5.3.1: {}
- pend@1.2.0:
- optional: true
+ pend@1.2.0: {}
performance-now@2.1.0:
optional: true
@@ -20748,14 +20863,14 @@ snapshots:
pg-numeric@1.0.2: {}
- pg-pool@3.7.0(pg@8.13.1):
+ pg-pool@3.8.0(pg@8.14.1):
dependencies:
- pg: 8.13.1
-
- pg-protocol@1.6.1: {}
+ pg: 8.14.1
pg-protocol@1.7.0: {}
+ pg-protocol@1.8.0: {}
+
pg-types@2.2.0:
dependencies:
pg-int8: 1.0.1
@@ -20774,11 +20889,11 @@ snapshots:
postgres-interval: 3.0.0
postgres-range: 1.1.3
- pg@8.13.1:
+ pg@8.14.1:
dependencies:
pg-connection-string: 2.7.0
- pg-pool: 3.7.0(pg@8.13.1)
- pg-protocol: 1.7.0
+ pg-pool: 3.8.0(pg@8.14.1)
+ pg-protocol: 1.8.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
@@ -20790,23 +20905,18 @@ snapshots:
photoswipe@5.4.4: {}
- picocolors@1.0.0: {}
-
- picocolors@1.0.1: {}
-
- picocolors@1.1.0: {}
-
picocolors@1.1.1: {}
picomatch@2.3.1: {}
picomatch@4.0.2: {}
- pid-port@1.0.0:
+ pid-port@1.0.2:
dependencies:
execa: 8.0.1
- pify@2.3.0: {}
+ pify@2.3.0:
+ optional: true
pino-abstract-transport@1.2.0:
dependencies:
@@ -20841,12 +20951,6 @@ snapshots:
dependencies:
find-up: 4.1.0
- pkg-types@1.0.3:
- dependencies:
- jsonc-parser: 3.2.0
- mlly: 1.5.0
- pathe: 1.1.2
-
plimit-lit@1.5.0:
dependencies:
queue-lit: 1.5.0
@@ -20857,180 +20961,183 @@ snapshots:
pngjs@5.0.0: {}
+ pnpm@10.6.1: {}
+
polished@4.2.2:
dependencies:
'@babel/runtime': 7.23.4
possible-typed-array-names@1.0.0: {}
- postcss-calc@9.0.1(postcss@8.4.49):
+ postcss-calc@10.1.1(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
- postcss-selector-parser: 6.0.15
+ postcss: 8.5.3
+ postcss-selector-parser: 7.1.0
postcss-value-parser: 4.2.0
- postcss-colormin@6.1.0(postcss@8.4.49):
+ postcss-colormin@7.0.2(postcss@8.5.3):
dependencies:
- browserslist: 4.23.0
+ browserslist: 4.24.4
caniuse-api: 3.0.0
colord: 2.9.3
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-convert-values@6.1.0(postcss@8.4.49):
+ postcss-convert-values@7.0.4(postcss@8.5.3):
dependencies:
- browserslist: 4.23.0
- postcss: 8.4.49
+ browserslist: 4.24.4
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-discard-comments@6.0.2(postcss@8.4.49):
+ postcss-discard-comments@7.0.3(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
+ postcss-selector-parser: 6.1.2
- postcss-discard-duplicates@6.0.3(postcss@8.4.49):
+ postcss-discard-duplicates@7.0.1(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
- postcss-discard-empty@6.0.3(postcss@8.4.49):
+ postcss-discard-empty@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
- postcss-discard-overridden@6.0.2(postcss@8.4.49):
+ postcss-discard-overridden@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
- postcss-merge-longhand@6.0.5(postcss@8.4.49):
+ postcss-merge-longhand@7.0.4(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- stylehacks: 6.1.1(postcss@8.4.49)
+ stylehacks: 7.0.4(postcss@8.5.3)
- postcss-merge-rules@6.1.1(postcss@8.4.49):
+ postcss-merge-rules@7.0.4(postcss@8.5.3):
dependencies:
- browserslist: 4.23.0
+ browserslist: 4.24.4
caniuse-api: 3.0.0
- cssnano-utils: 4.0.2(postcss@8.4.49)
- postcss: 8.4.49
- postcss-selector-parser: 6.0.16
+ cssnano-utils: 5.0.0(postcss@8.5.3)
+ postcss: 8.5.3
+ postcss-selector-parser: 6.1.2
- postcss-minify-font-values@6.1.0(postcss@8.4.49):
+ postcss-minify-font-values@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-minify-gradients@6.0.3(postcss@8.4.49):
+ postcss-minify-gradients@7.0.0(postcss@8.5.3):
dependencies:
colord: 2.9.3
- cssnano-utils: 4.0.2(postcss@8.4.49)
- postcss: 8.4.49
+ cssnano-utils: 5.0.0(postcss@8.5.3)
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-minify-params@6.1.0(postcss@8.4.49):
+ postcss-minify-params@7.0.2(postcss@8.5.3):
dependencies:
- browserslist: 4.23.0
- cssnano-utils: 4.0.2(postcss@8.4.49)
- postcss: 8.4.49
+ browserslist: 4.24.4
+ cssnano-utils: 5.0.0(postcss@8.5.3)
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-minify-selectors@6.0.4(postcss@8.4.49):
+ postcss-minify-selectors@7.0.4(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
- postcss-selector-parser: 6.0.16
+ cssesc: 3.0.0
+ postcss: 8.5.3
+ postcss-selector-parser: 6.1.2
- postcss-normalize-charset@6.0.2(postcss@8.4.49):
+ postcss-normalize-charset@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
- postcss-normalize-display-values@6.0.2(postcss@8.4.49):
+ postcss-normalize-display-values@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-normalize-positions@6.0.2(postcss@8.4.49):
+ postcss-normalize-positions@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-normalize-repeat-style@6.0.2(postcss@8.4.49):
+ postcss-normalize-repeat-style@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-normalize-string@6.0.2(postcss@8.4.49):
+ postcss-normalize-string@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-normalize-timing-functions@6.0.2(postcss@8.4.49):
+ postcss-normalize-timing-functions@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-normalize-unicode@6.1.0(postcss@8.4.49):
+ postcss-normalize-unicode@7.0.2(postcss@8.5.3):
dependencies:
- browserslist: 4.23.0
- postcss: 8.4.49
+ browserslist: 4.24.4
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-normalize-url@6.0.2(postcss@8.4.49):
+ postcss-normalize-url@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-normalize-whitespace@6.0.2(postcss@8.4.49):
+ postcss-normalize-whitespace@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-ordered-values@6.0.2(postcss@8.4.49):
+ postcss-ordered-values@7.0.1(postcss@8.5.3):
dependencies:
- cssnano-utils: 4.0.2(postcss@8.4.49)
- postcss: 8.4.49
+ cssnano-utils: 5.0.0(postcss@8.5.3)
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-reduce-initial@6.1.0(postcss@8.4.49):
+ postcss-reduce-initial@7.0.2(postcss@8.5.3):
dependencies:
- browserslist: 4.23.0
+ browserslist: 4.24.4
caniuse-api: 3.0.0
- postcss: 8.4.49
+ postcss: 8.5.3
- postcss-reduce-transforms@6.0.2(postcss@8.4.49):
+ postcss-reduce-transforms@7.0.0(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- postcss-selector-parser@6.0.15:
+ postcss-selector-parser@6.0.16:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
- postcss-selector-parser@6.0.16:
+ postcss-selector-parser@6.1.2:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
- postcss-svgo@6.0.3(postcss@8.4.49):
+ postcss-selector-parser@7.1.0:
dependencies:
- postcss: 8.4.49
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-svgo@7.0.1(postcss@8.5.3):
+ dependencies:
+ postcss: 8.5.3
postcss-value-parser: 4.2.0
- svgo: 3.2.0
+ svgo: 3.3.2
- postcss-unique-selectors@6.0.4(postcss@8.4.49):
+ postcss-unique-selectors@7.0.3(postcss@8.5.3):
dependencies:
- postcss: 8.4.49
- postcss-selector-parser: 6.0.16
+ postcss: 8.5.3
+ postcss-selector-parser: 6.1.2
postcss-value-parser@4.2.0: {}
- postcss@8.4.47:
+ postcss@8.5.3:
dependencies:
- nanoid: 3.3.7
- picocolors: 1.1.0
- source-map-js: 1.2.1
-
- postcss@8.4.49:
- dependencies:
- nanoid: 3.3.7
+ nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
@@ -21075,6 +21182,8 @@ snapshots:
prettier@3.3.3: {}
+ prettier@3.5.3: {}
+
pretty-bytes@5.6.0:
optional: true
@@ -21090,11 +21199,15 @@ snapshots:
ansi-styles: 5.2.0
react-is: 18.2.0
- private-ip@2.3.3:
+ pretty-ms@9.2.0:
dependencies:
- ip-regex: 4.3.0
+ parse-ms: 4.0.0
+
+ private-ip@3.0.2:
+ dependencies:
+ '@chainsafe/is-ip': 2.1.0
+ ip-regex: 5.0.0
ipaddr.js: 2.2.0
- is-ip: 3.1.0
netmask: 2.0.2
probe-image-size@7.2.3:
@@ -21137,7 +21250,7 @@ snapshots:
kleur: 3.0.3
sisteransi: 1.0.5
- property-information@6.5.0: {}
+ property-information@7.0.0: {}
proto-list@1.2.4: {}
@@ -21157,8 +21270,6 @@ snapshots:
dependencies:
event-stream: 3.3.4
- pseudomap@1.0.2: {}
-
psl@1.13.0:
dependencies:
punycode: 2.3.1
@@ -21316,9 +21427,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
- react-docgen-typescript@2.2.2(typescript@5.6.3):
+ react-docgen-typescript@2.2.2(typescript@5.8.2):
dependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
react-docgen@7.0.1:
dependencies:
@@ -21335,19 +21446,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
- react-dom@18.3.1(react@18.3.1):
+ react-dom@19.0.0(react@19.0.0):
dependencies:
- loose-envify: 1.4.0
- react: 18.3.1
- scheduler: 0.23.2
+ react: 19.0.0
+ scheduler: 0.25.0
react-is@17.0.2: {}
react-is@18.2.0: {}
- react@18.3.1:
- dependencies:
- loose-envify: 1.4.0
+ react@19.0.0: {}
read-pkg-up@7.0.1:
dependencies:
@@ -21385,17 +21493,11 @@ snapshots:
events: 3.3.0
process: 0.11.10
- readable-web-to-node-stream@3.0.2:
- dependencies:
- readable-stream: 3.6.0
-
readdir-glob@1.1.2:
dependencies:
minimatch: 5.1.2
- readdirp@3.6.0:
- dependencies:
- picomatch: 2.3.1
+ readdirp@4.1.2: {}
real-require@0.2.0: {}
@@ -21416,10 +21518,6 @@ snapshots:
redis-errors@1.2.0: {}
- redis-info@3.1.0:
- dependencies:
- lodash: 4.17.21
-
redis-lock@0.1.4: {}
redis-parser@3.0.0:
@@ -21430,7 +21528,15 @@ snapshots:
regenerator-runtime@0.14.0: {}
- regex@4.4.0: {}
+ regex-recursion@6.0.2:
+ dependencies:
+ regex-utilities: 2.3.0
+
+ regex-utilities@2.3.0: {}
+
+ regex@6.0.1:
+ dependencies:
+ regex-utilities: 2.3.0
regexp.prototype.flags@1.5.0:
dependencies:
@@ -21518,10 +21624,6 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
- responselike@2.0.1:
- dependencies:
- lowercase-keys: 2.0.0
-
responselike@3.0.0:
dependencies:
lowercase-keys: 3.0.0
@@ -21544,33 +21646,36 @@ snapshots:
dependencies:
glob: 7.2.3
- rollup@4.26.0:
+ rimraf@5.0.10:
+ dependencies:
+ glob: 10.3.10
+
+ rollup@4.36.0:
dependencies:
'@types/estree': 1.0.6
optionalDependencies:
- '@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
+ '@rollup/rollup-android-arm-eabi': 4.36.0
+ '@rollup/rollup-android-arm64': 4.36.0
+ '@rollup/rollup-darwin-arm64': 4.36.0
+ '@rollup/rollup-darwin-x64': 4.36.0
+ '@rollup/rollup-freebsd-arm64': 4.36.0
+ '@rollup/rollup-freebsd-x64': 4.36.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.36.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.36.0
+ '@rollup/rollup-linux-arm64-gnu': 4.36.0
+ '@rollup/rollup-linux-arm64-musl': 4.36.0
+ '@rollup/rollup-linux-loongarch64-gnu': 4.36.0
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.36.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.36.0
+ '@rollup/rollup-linux-s390x-gnu': 4.36.0
+ '@rollup/rollup-linux-x64-gnu': 4.36.0
+ '@rollup/rollup-linux-x64-musl': 4.36.0
+ '@rollup/rollup-win32-arm64-msvc': 4.36.0
+ '@rollup/rollup-win32-ia32-msvc': 4.36.0
+ '@rollup/rollup-win32-x64-msvc': 4.36.0
fsevents: 2.3.3
- rrweb-cssom@0.6.0: {}
-
- rrweb-cssom@0.7.1: {}
+ rrweb-cssom@0.8.0: {}
rss-parser@3.13.0:
dependencies:
@@ -21581,16 +21686,9 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
- rxjs@7.8.1:
+ rxjs@7.8.2:
dependencies:
- tslib: 2.6.2
-
- safe-array-concat@1.0.0:
- dependencies:
- call-bind: 1.0.2
- get-intrinsic: 1.2.1
- has-symbols: 1.0.3
- isarray: 2.0.5
+ tslib: 2.7.0
safe-array-concat@1.1.2:
dependencies:
@@ -21603,12 +21701,6 @@ snapshots:
safe-buffer@5.2.1: {}
- safe-regex-test@1.0.0:
- dependencies:
- call-bind: 1.0.2
- get-intrinsic: 1.2.1
- is-regex: 1.1.4
-
safe-regex-test@1.0.3:
dependencies:
call-bind: 1.0.7
@@ -21623,26 +21715,22 @@ snapshots:
safer-buffer@2.1.2: {}
- sanitize-html@2.13.1:
+ sanitize-html@2.15.0:
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.47
+ postcss: 8.5.3
- sass@1.79.3:
+ sass@1.86.0:
dependencies:
- chokidar: 3.5.3
- immutable: 4.2.2
- source-map-js: 1.2.0
-
- sass@1.79.4:
- dependencies:
- chokidar: 3.5.3
- immutable: 4.2.2
+ chokidar: 4.0.3
+ immutable: 5.0.3
source-map-js: 1.2.1
+ optionalDependencies:
+ '@parcel/watcher': 2.5.1
sax@1.2.4: {}
@@ -21650,21 +21738,23 @@ snapshots:
dependencies:
xmlchars: 2.2.0
- scheduler@0.23.2:
- dependencies:
- loose-envify: 1.4.0
+ scheduler@0.25.0: {}
secure-json-parse@2.7.0: {}
- secure-json-parse@3.0.0: {}
+ secure-json-parse@3.0.2: {}
seedrandom@3.0.5: {}
+ seek-bzip@2.0.0:
+ dependencies:
+ commander: 6.2.1
+
semver-regex@4.0.5: {}
- semver-truncate@2.0.0:
+ semver-truncate@3.0.0:
dependencies:
- semver: 6.3.1
+ semver: 7.6.3
semver@5.7.1: {}
@@ -21762,16 +21852,10 @@ snapshots:
'@img/sharp-win32-ia32': 0.33.5
'@img/sharp-win32-x64': 0.33.5
- shebang-command@1.2.0:
- dependencies:
- shebang-regex: 1.0.0
-
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
- shebang-regex@1.0.0: {}
-
shebang-regex@3.0.0: {}
shiki@0.14.7:
@@ -21781,13 +21865,15 @@ snapshots:
vscode-oniguruma: 1.7.0
vscode-textmate: 8.0.0
- shiki@1.22.2:
+ shiki@3.2.1:
dependencies:
- '@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
+ '@shikijs/core': 3.2.1
+ '@shikijs/engine-javascript': 3.2.1
+ '@shikijs/engine-oniguruma': 3.2.1
+ '@shikijs/langs': 3.2.1
+ '@shikijs/themes': 3.2.1
+ '@shikijs/types': 3.2.1
+ '@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
shimmer@1.2.1: {}
@@ -21834,15 +21920,15 @@ snapshots:
simple-update-notifier@2.0.0:
dependencies:
- semver: 7.5.4
+ semver: 7.6.3
- sinon@16.1.3:
+ sinon@18.0.1:
dependencies:
- '@sinonjs/commons': 3.0.0
- '@sinonjs/fake-timers': 10.3.0
+ '@sinonjs/commons': 3.0.1
+ '@sinonjs/fake-timers': 11.2.2
'@sinonjs/samsam': 8.0.0
- diff: 5.1.0
- nise: 5.1.4
+ diff: 5.2.0
+ nise: 6.1.1
supports-color: 7.2.0
sisteransi@1.0.5: {}
@@ -21949,8 +22035,6 @@ snapshots:
sortablejs@1.14.0: {}
- source-map-js@1.2.0: {}
-
source-map-js@1.2.1: {}
source-map-support@0.5.13:
@@ -21993,6 +22077,8 @@ snapshots:
sprintf-js@1.1.3: {}
+ sql-highlight@6.0.0: {}
+
sshpk@1.17.0:
dependencies:
asn1: 0.2.6
@@ -22030,45 +22116,58 @@ snapshots:
standard-as-callback@2.1.0: {}
- start-server-and-test@2.0.8:
+ start-server-and-test@2.0.10:
dependencies:
arg: 5.0.2
bluebird: 3.7.2
check-more-types: 2.24.0
- debug: 4.3.7(supports-color@8.1.1)
+ debug: 4.4.0
+ execa: 5.1.1
+ lazy-ass: 1.6.0
+ ps-tree: 1.2.0
+ wait-on: 8.0.2(debug@4.4.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ start-server-and-test@2.0.11:
+ dependencies:
+ arg: 5.0.2
+ bluebird: 3.7.2
+ check-more-types: 2.24.0
+ debug: 4.4.0
execa: 5.1.1
lazy-ass: 1.6.0
ps-tree: 1.2.0
- wait-on: 8.0.1(debug@4.3.7)
+ wait-on: 8.0.3(debug@4.4.0)
transitivePeerDependencies:
- supports-color
statuses@2.0.1: {}
- std-env@3.7.0: {}
+ std-env@3.8.1: {}
stop-iteration-iterator@1.0.0:
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.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):
+ storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/components@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/core-events@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/manager-api@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/preview-api@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/theming@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(@storybook/types@8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
- '@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))
+ '@storybook/blocks': 8.6.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/components': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/core-events': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/manager-api': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/preview-api': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/theming': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
+ '@storybook/types': 8.6.7(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))
optionalDependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
- storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4):
+ storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5):
dependencies:
- '@storybook/core': 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
+ '@storybook/core': 8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(storybook@8.6.7(bufferutil@4.0.9)(prettier@3.5.3)(utf-8-validate@6.0.5))(utf-8-validate@6.0.5)
optionalDependencies:
- prettier: 3.3.3
+ prettier: 3.5.3
transitivePeerDependencies:
- bufferutil
- supports-color
@@ -22119,12 +22218,6 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
- string.prototype.trim@1.2.7:
- dependencies:
- call-bind: 1.0.2
- define-properties: 1.2.0
- es-abstract: 1.22.1
-
string.prototype.trim@1.2.9:
dependencies:
call-bind: 1.0.7
@@ -22132,24 +22225,12 @@ snapshots:
es-abstract: 1.23.3
es-object-atoms: 1.0.0
- string.prototype.trimend@1.0.6:
- dependencies:
- call-bind: 1.0.2
- define-properties: 1.2.0
- es-abstract: 1.22.1
-
string.prototype.trimend@1.0.8:
dependencies:
call-bind: 1.0.7
define-properties: 1.2.1
es-object-atoms: 1.0.0
- string.prototype.trimstart@1.0.6:
- dependencies:
- call-bind: 1.0.2
- define-properties: 1.2.0
- es-abstract: 1.22.1
-
string.prototype.trimstart@1.0.8:
dependencies:
call-bind: 1.0.7
@@ -22185,12 +22266,17 @@ snapshots:
strip-bom@4.0.0: {}
- strip-eof@1.0.0: {}
+ strip-dirs@3.0.0:
+ dependencies:
+ inspect-with-kind: 1.0.5
+ is-plain-obj: 1.1.0
strip-final-newline@2.0.0: {}
strip-final-newline@3.0.0: {}
+ strip-final-newline@4.0.0: {}
+
strip-indent@3.0.0:
dependencies:
min-indent: 1.0.1
@@ -22203,29 +22289,18 @@ snapshots:
strip-json-comments@3.1.1: {}
- strip-literal@2.1.0:
- dependencies:
- js-tokens: 9.0.0
-
- strip-outer@2.0.0: {}
-
strnum@1.0.5: {}
- strtok3@7.0.0:
- dependencies:
- '@tokenizer/token': 0.3.0
- peek-readable: 5.2.0
-
strtok3@9.0.1:
dependencies:
'@tokenizer/token': 0.3.0
peek-readable: 5.3.1
- stylehacks@6.1.1(postcss@8.4.49):
+ stylehacks@7.0.4(postcss@8.5.3):
dependencies:
- browserslist: 4.23.0
- postcss: 8.4.49
- postcss-selector-parser: 6.0.16
+ browserslist: 4.24.4
+ postcss: 8.5.3
+ postcss-selector-parser: 6.1.2
supports-color@5.5.0:
dependencies:
@@ -22248,7 +22323,7 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
- svgo@3.2.0:
+ svgo@3.3.2:
dependencies:
'@trysound/sax': 0.2.0
commander: 7.2.0
@@ -22256,11 +22331,11 @@ snapshots:
css-tree: 2.3.1
css-what: 6.1.0
csso: 5.0.5
- picocolors: 1.0.0
+ picocolors: 1.1.1
symbol-tree@3.2.4: {}
- systeminformation@5.23.5: {}
+ systeminformation@5.25.11: {}
tar-fs@2.1.2:
dependencies:
@@ -22283,6 +22358,12 @@ snapshots:
fast-fifo: 1.3.0
streamx: 2.15.0
+ tar-stream@3.1.7:
+ dependencies:
+ b4a: 1.6.4
+ fast-fifo: 1.3.0
+ streamx: 2.15.0
+
tar@6.2.1:
dependencies:
chownr: 2.0.0
@@ -22292,14 +22373,23 @@ snapshots:
mkdirp: 1.0.4
yallist: 4.0.0
+ tar@7.4.3:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.2
+ minizlib: 3.0.1
+ mkdirp: 3.0.1
+ yallist: 5.0.0
+
taskkill@5.0.0:
dependencies:
execa: 6.1.0
- terser@5.36.0:
+ terser@5.39.0:
dependencies:
'@jridgewell/source-map': 0.3.6
- acorn: 8.14.0
+ acorn: 8.14.1
commander: 2.20.3
source-map-support: 0.5.21
@@ -22309,6 +22399,12 @@ snapshots:
glob: 7.2.3
minimatch: 3.1.2
+ test-exclude@7.0.1:
+ dependencies:
+ '@istanbuljs/schema': 0.1.3
+ glob: 10.4.5
+ minimatch: 9.0.4
+
text-decoding@1.0.0: {}
text-table@0.2.0: {}
@@ -22327,7 +22423,7 @@ snapshots:
dependencies:
real-require: 0.2.0
- three@0.169.0: {}
+ three@0.174.0: {}
throttle-debounce@5.0.2: {}
@@ -22338,25 +22434,25 @@ snapshots:
tiny-invariant@1.3.3: {}
- tinybench@2.6.0: {}
+ tinybench@2.9.0: {}
tinycolor2@1.6.0: {}
- tinypool@0.8.4: {}
+ tinyexec@0.3.2: {}
+
+ tinypool@1.0.2: {}
tinyrainbow@1.2.0: {}
- tinyspy@2.2.0: {}
+ tinyrainbow@2.0.0: {}
tinyspy@3.0.2: {}
- tldts-core@6.1.63:
- optional: true
+ tldts-core@6.1.63: {}
tldts@6.1.63:
dependencies:
tldts-core: 6.1.63
- optional: true
tmp@0.2.3: {}
@@ -22376,11 +22472,6 @@ snapshots:
token-stream@1.0.0: {}
- token-types@5.0.1:
- dependencies:
- '@tokenizer/token': 0.3.0
- ieee754: 1.2.1
-
token-types@6.0.0:
dependencies:
'@tokenizer/token': 0.3.0
@@ -22400,16 +22491,13 @@ snapshots:
tough-cookie@5.0.0:
dependencies:
tldts: 6.1.63
- optional: true
tr46@0.0.3: {}
- tr46@5.0.0:
+ tr46@5.1.0:
dependencies:
punycode: 2.3.1
- trace-redirect@1.0.6: {}
-
tree-kill@1.2.2:
optional: true
@@ -22417,29 +22505,25 @@ snapshots:
trim-newlines@3.0.1: {}
- trim-repeated@2.0.0:
- dependencies:
- escape-string-regexp: 5.0.0
-
trough@2.2.0: {}
ts-api-utils@1.3.0(typescript@5.1.6):
dependencies:
typescript: 5.1.6
- ts-api-utils@1.3.0(typescript@5.6.3):
+ ts-api-utils@2.1.0(typescript@5.8.2):
dependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
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.24.0)(jest@29.7.0(@types/node@22.9.0))(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.25.0)(jest@29.7.0(@types/node@22.13.10))(typescript@5.1.6):
dependencies:
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
- jest: 29.7.0(@types/node@22.9.0)
+ jest: 29.7.0(@types/node@22.13.10)
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
@@ -22451,13 +22535,13 @@ snapshots:
'@babel/core': 7.24.7
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.24.7)
- esbuild: 0.24.0
+ esbuild: 0.25.0
ts-map@1.0.3: {}
- tsc-alias@1.8.10:
+ tsc-alias@1.8.11:
dependencies:
- chokidar: 3.5.3
+ chokidar: 4.0.3
commander: 9.5.0
globby: 11.1.0
mylas: 2.1.13
@@ -22489,14 +22573,14 @@ snapshots:
tslib@2.6.2: {}
- tslib@2.6.3: {}
-
tslib@2.7.0: {}
- tsx@4.4.0:
+ tslib@2.8.1: {}
+
+ tsx@4.19.3:
dependencies:
- esbuild: 0.18.20
- get-tsconfig: 4.7.2
+ esbuild: 0.25.1
+ get-tsconfig: 4.10.0
optionalDependencies:
fsevents: 2.3.3
@@ -22531,25 +22615,12 @@ snapshots:
media-typer: 0.3.0
mime-types: 2.1.35
- typed-array-buffer@1.0.0:
- dependencies:
- call-bind: 1.0.2
- get-intrinsic: 1.2.1
- is-typed-array: 1.1.10
-
typed-array-buffer@1.0.2:
dependencies:
call-bind: 1.0.7
es-errors: 1.3.0
is-typed-array: 1.1.13
- typed-array-byte-length@1.0.0:
- dependencies:
- call-bind: 1.0.2
- for-each: 0.3.3
- has-proto: 1.0.1
- is-typed-array: 1.1.10
-
typed-array-byte-length@1.0.1:
dependencies:
call-bind: 1.0.7
@@ -22558,14 +22629,6 @@ snapshots:
has-proto: 1.0.3
is-typed-array: 1.1.13
- typed-array-byte-offset@1.0.0:
- dependencies:
- available-typed-arrays: 1.0.5
- call-bind: 1.0.2
- for-each: 0.3.3
- has-proto: 1.0.1
- is-typed-array: 1.1.10
-
typed-array-byte-offset@1.0.2:
dependencies:
available-typed-arrays: 1.0.7
@@ -22575,12 +22638,6 @@ snapshots:
has-proto: 1.0.3
is-typed-array: 1.1.13
- typed-array-length@1.0.4:
- dependencies:
- call-bind: 1.0.2
- for-each: 0.3.3
- is-typed-array: 1.1.10
-
typed-array-length@1.0.6:
dependencies:
call-bind: 1.0.7
@@ -22600,36 +22657,33 @@ snapshots:
shiki: 0.14.7
typescript: 5.1.6
- typeorm@0.3.20(ioredis@5.4.1)(pg@8.13.1):
+ typeorm@0.3.21(ioredis@5.6.0)(pg@8.14.1)(reflect-metadata@0.2.2):
dependencies:
'@sqltools/formatter': 1.2.5
+ ansis: 3.17.0
app-root-path: 3.1.0
buffer: 6.0.3
- chalk: 4.1.2
- cli-highlight: 2.1.11
dayjs: 1.11.10
- debug: 4.3.4
+ debug: 4.3.7(supports-color@8.1.1)
dotenv: 16.0.3
- glob: 10.3.10
- mkdirp: 2.1.6
+ glob: 10.4.5
reflect-metadata: 0.2.2
sha.js: 2.4.11
- tslib: 2.6.2
- uuid: 9.0.1
+ sql-highlight: 6.0.0
+ tslib: 2.7.0
+ uuid: 11.1.0
yargs: 17.7.2
optionalDependencies:
- ioredis: 5.4.1
- pg: 8.13.1
+ ioredis: 5.6.0
+ pg: 8.14.1
transitivePeerDependencies:
- supports-color
typescript@5.1.6: {}
- typescript@5.4.2: {}
+ typescript@5.7.3: {}
- typescript@5.6.3: {}
-
- ufo@1.3.2: {}
+ typescript@5.8.2: {}
uid2@0.0.4: {}
@@ -22639,7 +22693,7 @@ snapshots:
uint8array-extras@1.4.0: {}
- ulid@2.3.0: {}
+ ulid@2.4.0: {}
unbox-primitive@1.0.2:
dependencies:
@@ -22648,16 +22702,27 @@ snapshots:
has-symbols: 1.0.3
which-boxed-primitive: 1.0.2
+ unbzip2-stream@1.4.3:
+ dependencies:
+ buffer: 5.7.1
+ through: 2.3.8
+
undefsafe@2.0.5: {}
- undici-types@6.19.8: {}
+ undici-types@6.20.0: {}
undici@5.28.2:
dependencies:
'@fastify/busboy': 2.1.0
+ undici@5.29.0:
+ dependencies:
+ '@fastify/busboy': 2.1.0
+
undici@6.20.0: {}
+ unicorn-magic@0.3.0: {}
+
unified@11.0.4:
dependencies:
'@types/unist': 3.0.2
@@ -22703,8 +22768,7 @@ snapshots:
universalify@0.2.0: {}
- universalify@2.0.0:
- optional: true
+ universalify@2.0.0: {}
unload@2.4.1: {}
@@ -22712,25 +22776,25 @@ snapshots:
unplugin@1.4.0:
dependencies:
- acorn: 8.14.0
- chokidar: 3.5.3
+ acorn: 8.14.1
+ chokidar: 4.0.3
webpack-sources: 3.2.3
webpack-virtual-modules: 0.5.0
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
escalade: 3.1.1
- picocolors: 1.0.0
+ picocolors: 1.1.1
+
+ update-browserslist-db@1.1.3(browserslist@4.24.4):
+ dependencies:
+ browserslist: 4.24.4
+ escalade: 3.2.0
+ picocolors: 1.1.1
uri-js@4.4.1:
dependencies:
@@ -22741,12 +22805,7 @@ snapshots:
querystringify: 2.2.0
requires-port: 1.0.0
- utf-8-validate@6.0.3:
- dependencies:
- node-gyp-build: 4.6.0
- optional: true
-
- utf-8-validate@6.0.4:
+ utf-8-validate@6.0.5:
dependencies:
node-gyp-build: 4.8.1
optional: true
@@ -22763,20 +22822,20 @@ snapshots:
utils-merge@1.0.1: {}
- uuid@10.0.0: {}
+ uuid@11.1.0: {}
uuid@8.3.2:
optional: true
uuid@9.0.1: {}
- v-code-diff@1.13.1(vue@3.5.12(typescript@5.6.3)):
+ v-code-diff@1.13.1(vue@3.5.13(typescript@5.8.2)):
dependencies:
diff: 5.2.0
diff-match-patch: 1.0.5
highlight.js: 11.10.0
- vue: 3.5.12(typescript@5.6.3)
- vue-demi: 0.14.7(vue@3.5.12(typescript@5.6.3))
+ vue: 3.5.13(typescript@5.8.2)
+ vue-demi: 0.14.7(vue@3.5.13(typescript@5.8.2))
v8-to-istanbul@9.2.0:
dependencies:
@@ -22810,33 +22869,16 @@ snapshots:
unist-util-stringify-position: 4.0.0
vfile-message: 4.0.2
- vite-node@1.6.0(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0):
+ vite-node@3.0.9(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3):
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.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)
+ debug: 4.4.0
+ es-module-lexer: 1.6.0
+ pathe: 2.0.3
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
transitivePeerDependencies:
- '@types/node'
+ - jiti
- less
- lightningcss
- sass
@@ -22845,109 +22887,108 @@ snapshots:
- sugarss
- supports-color
- terser
+ - tsx
+ - yaml
vite-plugin-turbosnap@1.0.3: {}
- vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0):
+ vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3):
dependencies:
- esbuild: 0.21.5
- postcss: 8.4.49
- rollup: 4.26.0
+ esbuild: 0.25.1
+ postcss: 8.5.3
+ rollup: 4.36.0
optionalDependencies:
- '@types/node': 22.9.0
+ '@types/node': 22.13.11
fsevents: 2.3.3
- sass: 1.79.3
- terser: 5.36.0
+ sass: 1.86.0
+ terser: 5.39.0
+ tsx: 4.19.3
- vite@5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0):
+ vitest-fetch-mock@0.4.5(vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)):
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: 3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
- 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)):
+ vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3):
dependencies:
- cross-fetch: 3.1.6(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)
- transitivePeerDependencies:
- - encoding
-
- 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
- '@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.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
+ '@vitest/expect': 3.0.9
+ '@vitest/mocker': 3.0.9(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
+ '@vitest/pretty-format': 3.0.9
+ '@vitest/runner': 3.0.9
+ '@vitest/snapshot': 3.0.9
+ '@vitest/spy': 3.0.9
+ '@vitest/utils': 3.0.9
+ chai: 5.2.0
+ debug: 4.4.0
+ expect-type: 1.2.0
+ magic-string: 0.30.17
+ pathe: 2.0.3
+ std-env: 3.8.1
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinypool: 1.0.2
+ tinyrainbow: 2.0.0
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
+ vite-node: 3.0.9(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
+ why-is-node-running: 2.3.0
optionalDependencies:
- '@types/node': 22.9.0
- happy-dom: 10.0.3
- jsdom: 24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+ '@types/debug': 4.1.12
+ '@types/node': 22.13.11
+ happy-dom: 17.4.4
+ jsdom: 26.0.0(bufferutil@4.0.9)(canvas@3.1.0)(utf-8-validate@6.0.5)
transitivePeerDependencies:
+ - jiti
- less
- lightningcss
+ - msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
+ - tsx
+ - yaml
- 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):
+ vitest@3.0.9(@types/debug@4.1.12)(@types/node@22.13.11)(happy-dom@17.4.4)(jsdom@26.0.0)(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3):
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
+ '@vitest/expect': 3.0.9
+ '@vitest/mocker': 3.0.9(msw@2.7.3(@types/node@22.13.11)(typescript@5.8.2))(vite@6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3))
+ '@vitest/pretty-format': 3.0.9
+ '@vitest/runner': 3.0.9
+ '@vitest/snapshot': 3.0.9
+ '@vitest/spy': 3.0.9
+ '@vitest/utils': 3.0.9
+ chai: 5.2.0
+ debug: 4.4.0
+ expect-type: 1.2.0
+ magic-string: 0.30.17
+ pathe: 2.0.3
+ std-env: 3.8.1
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinypool: 1.0.2
+ tinyrainbow: 2.0.0
+ vite: 6.2.2(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
+ vite-node: 3.0.9(@types/node@22.13.11)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)
+ why-is-node-running: 2.3.0
optionalDependencies:
- '@types/node': 22.9.0
- happy-dom: 10.0.3
- jsdom: 24.1.1
+ '@types/debug': 4.1.12
+ '@types/node': 22.13.11
+ happy-dom: 17.4.4
+ jsdom: 26.0.0
transitivePeerDependencies:
+ - jiti
- less
- lightningcss
+ - msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
+ - tsx
+ - yaml
void-elements@3.1.0: {}
@@ -22978,97 +23019,104 @@ snapshots:
vscode-uri@3.0.8: {}
- vue-component-meta@2.0.16(typescript@5.6.3):
+ vue-component-meta@2.0.16(typescript@5.8.2):
dependencies:
'@volar/typescript': 2.2.0
- '@vue/language-core': 2.0.16(typescript@5.6.3)
+ '@vue/language-core': 2.0.16(typescript@5.8.2)
path-browserify: 1.0.1
vue-component-type-helpers: 2.0.16
optionalDependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
vue-component-type-helpers@1.8.4: {}
vue-component-type-helpers@2.0.16: {}
- vue-component-type-helpers@2.1.10: {}
-
vue-component-type-helpers@2.2.8: {}
- vue-demi@0.14.7(vue@3.5.12(typescript@5.6.3)):
+ vue-demi@0.14.7(vue@3.5.13(typescript@5.8.2)):
dependencies:
- vue: 3.5.12(typescript@5.6.3)
+ vue: 3.5.13(typescript@5.8.2)
- vue-docgen-api@4.75.1(vue@3.5.12(typescript@5.6.3)):
+ vue-docgen-api@4.75.1(vue@3.5.13(typescript@5.8.2)):
dependencies:
'@babel/parser': 7.25.7
'@babel/types': 7.25.7
- '@vue/compiler-dom': 3.5.11
- '@vue/compiler-sfc': 3.5.12
+ '@vue/compiler-dom': 3.5.12
+ '@vue/compiler-sfc': 3.5.13
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.12(typescript@5.6.3)
- vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.12(typescript@5.6.3))
+ vue: 3.5.13(typescript@5.8.2)
+ vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.13(typescript@5.8.2))
- vue-eslint-parser@9.4.3(eslint@9.14.0):
+ vue-eslint-parser@10.1.1(eslint@9.22.0):
dependencies:
- debug: 4.3.4
- eslint: 9.14.0
- eslint-scope: 7.2.2
- eslint-visitor-keys: 3.4.3
- espree: 9.6.1
- esquery: 1.4.2
+ debug: 4.4.0
+ eslint: 9.22.0
+ eslint-scope: 8.2.0
+ eslint-visitor-keys: 4.2.0
+ espree: 10.3.0
+ esquery: 1.6.0
lodash: 4.17.21
- semver: 7.6.0
+ semver: 7.6.3
transitivePeerDependencies:
- supports-color
- vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.12(typescript@5.6.3)):
+ vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.13(typescript@5.8.2)):
dependencies:
- vue: 3.5.12(typescript@5.6.3)
+ vue: 3.5.13(typescript@5.8.2)
vue-template-compiler@2.7.14:
dependencies:
de-indent: 1.0.2
he: 1.2.0
- vue-tsc@2.1.10(typescript@5.6.3):
+ vue-tsc@2.2.8(typescript@5.8.2):
dependencies:
- '@volar/typescript': 2.4.10
- '@vue/language-core': 2.1.10(typescript@5.6.3)
- semver: 7.6.3
- typescript: 5.6.3
+ '@volar/typescript': 2.4.12
+ '@vue/language-core': 2.2.8(typescript@5.8.2)
+ typescript: 5.8.2
- vue@3.5.12(typescript@5.6.3):
+ vue@3.5.13(typescript@5.8.2):
dependencies:
- '@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
+ '@vue/compiler-dom': 3.5.13
+ '@vue/compiler-sfc': 3.5.13
+ '@vue/runtime-dom': 3.5.13
+ '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.8.2))
+ '@vue/shared': 3.5.13
optionalDependencies:
- typescript: 5.6.3
+ typescript: 5.8.2
- vuedraggable@4.1.0(vue@3.5.12(typescript@5.6.3)):
+ vuedraggable@4.1.0(vue@3.5.13(typescript@5.8.2)):
dependencies:
sortablejs: 1.14.0
- vue: 3.5.12(typescript@5.6.3)
+ vue: 3.5.13(typescript@5.8.2)
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
- wait-on@8.0.1(debug@4.3.7):
+ wait-on@8.0.2(debug@4.4.0):
+ dependencies:
+ axios: 1.8.4(debug@4.4.0)
+ joi: 17.13.3
+ lodash: 4.17.21
+ minimist: 1.2.8
+ rxjs: 7.8.2
+ transitivePeerDependencies:
+ - debug
+
+ wait-on@8.0.3(debug@4.4.0):
dependencies:
- axios: 1.7.7(debug@4.3.7)
+ axios: 1.8.4(debug@4.4.0)
joi: 17.13.3
lodash: 4.17.21
minimist: 1.2.8
- rxjs: 7.8.1
+ rxjs: 7.8.2
transitivePeerDependencies:
- debug
@@ -23076,6 +23124,8 @@ snapshots:
dependencies:
makeerror: 1.0.12
+ wanakana@5.3.1: {}
+
web-push@3.6.7:
dependencies:
asn1.js: 5.4.1
@@ -23107,10 +23157,6 @@ snapshots:
webpack-virtual-modules@0.5.0: {}
- whatwg-encoding@2.0.0:
- dependencies:
- iconv-lite: 0.6.3
-
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
@@ -23119,9 +23165,9 @@ snapshots:
whatwg-mimetype@4.0.0: {}
- whatwg-url@14.0.0:
+ whatwg-url@14.2.0:
dependencies:
- tr46: 5.0.0
+ tr46: 5.1.0
webidl-conversions: 7.0.0
whatwg-url@5.0.0:
@@ -23174,7 +23220,7 @@ snapshots:
dependencies:
isexe: 3.1.1
- why-is-node-running@2.2.2:
+ why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
@@ -23213,20 +23259,15 @@ snapshots:
imurmurhash: 0.1.4
signal-exit: 3.0.7
- ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4):
+ ws@8.17.1(bufferutil@4.0.9)(utf-8-validate@6.0.5):
optionalDependencies:
- bufferutil: 4.0.8
- utf-8-validate: 6.0.4
+ bufferutil: 4.0.9
+ utf-8-validate: 6.0.5
- ws@8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3):
+ ws@8.18.1(bufferutil@4.0.9)(utf-8-validate@6.0.5):
optionalDependencies:
- bufferutil: 4.0.7
- utf-8-validate: 6.0.3
-
- ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4):
- optionalDependencies:
- bufferutil: 4.0.8
- utf-8-validate: 6.0.4
+ bufferutil: 4.0.9
+ utf-8-validate: 6.0.5
xev@3.0.2: {}
@@ -23253,12 +23294,12 @@ snapshots:
y18n@5.0.8: {}
- yallist@2.1.2: {}
-
yallist@3.1.1: {}
yallist@4.0.0: {}
+ yallist@5.0.0: {}
+
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
@@ -23308,12 +23349,17 @@ snapshots:
fd-slicer: 1.1.0
optional: true
- yocto-queue@0.1.0: {}
+ yauzl@3.2.0:
+ dependencies:
+ buffer-crc32: 0.2.13
+ pend: 1.2.0
- yocto-queue@1.0.0: {}
+ yocto-queue@0.1.0: {}
yoctocolors-cjs@2.1.2: {}
+ yoctocolors@2.1.1: {}
+
zip-stream@6.0.1:
dependencies:
archiver-utils: 5.0.2
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index c1e062a789..ee060700b6 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,11 +1,30 @@
packages:
- - 'packages/backend'
- - 'packages/frontend-shared'
- - 'packages/frontend'
- - 'packages/frontend-embed'
- - 'packages/sw'
- - 'packages/misskey-js'
- - 'packages/misskey-js/generator'
- - 'packages/megalodon'
- - 'packages/misskey-reversi'
- - 'packages/misskey-bubble-game'
+ - packages/backend
+ - packages/frontend-shared
+ - packages/frontend
+ - packages/frontend-embed
+ - packages/sw
+ - packages/megalodon
+ - packages/misskey-js
+ - packages/misskey-js/generator
+ - packages/misskey-reversi
+ - packages/misskey-bubble-game
+onlyBuiltDependencies:
+ - '@nestjs/core'
+ - '@parcel/watcher'
+ - '@sentry/profiling-node'
+ - '@swc/core'
+ - '@tensorflow/tfjs-node'
+ - bufferutil
+ - canvas
+ - core-js
+ - cypress
+ - esbuild
+ - msgpackr-extract
+ - msw
+ - nice-napi
+ - re2
+ - sharp
+ - utf-8-validate
+ - v-code-diff
+ - vue-demi
diff --git a/renovate.json5 b/renovate.json5
new file mode 100644
index 0000000000..395405972d
--- /dev/null
+++ b/renovate.json5
@@ -0,0 +1,88 @@
+{
+ $schema: 'https://docs.renovatebot.com/renovate-schema.json',
+ extends: [
+ 'config:recommended',
+ ],
+ timezone: 'Asia/Tokyo',
+ schedule: [
+ '* 0 * * *',
+ ],
+ prHourlyLimit: 5,
+ labels: [
+ 'dependencies',
+ ],
+ dependencyDashboardApproval: true,
+ dependencyDashboardAutoclose: true,
+ osvVulnerabilityAlerts: true,
+ dependencyDashboardOSVVulnerabilitySummary: 'unresolved',
+ ignoreDeps: [
+ // https://github.com/misskey-dev/misskey/pull/15489#issuecomment-2660717458
+ '@typescript/lib-webworker',
+ // https://github.com/misskey-dev/misskey/pull/15494#issuecomment-2660775258
+ 'nsfwjs',
+ ],
+ packageRules: [
+ {
+ groupName: '[Backend] Update dependencies',
+ matchFileNames: [
+ 'packages/backend/**/package.json',
+ ],
+ },
+ {
+ groupName: '[Frontend] Update dependencies',
+ matchFileNames: [
+ 'packages/frontend/**/package.json',
+ 'packages/frontend-embed/**/package.json',
+ 'packages/frontend-shared/**/package.json',
+ 'packages/misskey-bubble-game/**/package.json',
+ 'packages/misskey-reversi/**/package.json',
+ 'packages/sw/**/package.json',
+ ],
+ },
+ {
+ groupName: '[misskey-js] Update dependencies',
+ matchFileNames: [
+ 'packages/misskey-js/**/package.json',
+ ],
+ },
+ {
+ groupName: '[Root] Update dependencies',
+ matchFileNames: [
+ 'package.json',
+ ],
+ },
+ {
+ groupName: '[Tools] Update dependencies',
+ matchFileNames: [
+ 'scripts/**/package.json',
+ ],
+ },
+ {
+ groupName: '[GitHub Actions] Update dependencies',
+ matchFileNames: [
+ '.github/workflows/**/*.yml',
+ ],
+ },
+ {
+ groupName: '[Node.js] Update dependencies',
+ matchFileNames: [
+ '.node-version',
+ ],
+ },
+ {
+ groupName: '[Docker] Update dependencies',
+ matchFileNames: [
+ 'compose.local-db.yml',
+ 'compose_example.yml',
+ 'packages/backend/test-federation/*.yml',
+ 'Dockerfile',
+ ],
+ },
+ {
+ groupName: '[devcontainer] Update dependencies',
+ matchFileNames: [
+ '.devcontainer/**',
+ ],
+ },
+ ],
+}
diff --git a/scripts/build-assets.mjs b/scripts/build-assets.mjs
index 66380a0e17..01eb1e3bd1 100644
--- a/scripts/build-assets.mjs
+++ b/scripts/build-assets.mjs
@@ -75,7 +75,8 @@ async function buildBackendScript() {
'./packages/backend/src/server/web/boot.js',
'./packages/backend/src/server/web/boot.embed.js',
'./packages/backend/src/server/web/bios.js',
- './packages/backend/src/server/web/cli.js'
+ './packages/backend/src/server/web/cli.js',
+ './packages/backend/src/server/web/error.js',
]) {
let source = await fs.readFile(file, { encoding: 'utf-8' });
source = source.replaceAll(/\bLANGS\b/g, JSON.stringify(Object.keys(locales)));
diff --git a/scripts/dependency-patches/re2.patch b/scripts/dependency-patches/re2.patch
new file mode 100644
index 0000000000..3e7ec9f56f
--- /dev/null
+++ b/scripts/dependency-patches/re2.patch
@@ -0,0 +1,13 @@
+diff --git a/package.json b/package.json
+index a56ab59ef647288ee6028abd2b1780eaa92ebc9d..ec2c43e63f3134b6d54d616b2ef715447f873bbe 100644
+--- a/package.json
++++ b/package.json
+@@ -28,7 +28,7 @@
+ "test": "node tests/tests.js",
+ "ts-test": "tsc",
+ "save-to-github": "save-to-github-cache --artifact build/Release/re2.node",
+- "install": "install-from-cache --artifact build/Release/re2.node --host-var RE2_DOWNLOAD_MIRROR --skip-path-var RE2_DOWNLOAD_SKIP_PATH --skip-ver-var RE2_DOWNLOAD_SKIP_VER || node-gyp -j max rebuild",
++ "install": "npm_package_github=https://github.com/uhop/node-re2 npm_package_scripts_verify_build=true install-from-cache --artifact build/Release/re2.node --host-var RE2_DOWNLOAD_MIRROR --skip-path-var RE2_DOWNLOAD_SKIP_PATH --skip-ver-var RE2_DOWNLOAD_SKIP_VER || node-gyp -j max rebuild",
+ "verify-build": "node scripts/verify-build.js",
+ "build:dev": "node-gyp -j max build --debug",
+ "build": "node-gyp -j max build",
diff --git a/scripts/tarball.mjs b/scripts/tarball.mjs
index 93cd78a06a..5b7e5f0f80 100644
--- a/scripts/tarball.mjs
+++ b/scripts/tarball.mjs
@@ -9,7 +9,7 @@ import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import glob from 'fast-glob';
import walk from 'ignore-walk';
-import Pack from 'tar/lib/pack.js';
+import { Pack } from 'tar/pack';
import meta from '../package.json' with { type: "json" };
const cwd = fileURLToPath(new URL('..', import.meta.url));
diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml
index 4e426e863a..8dbf7ef811 100644
--- a/sharkey-locales/en-US.yml
+++ b/sharkey-locales/en-US.yml
@@ -530,3 +530,7 @@ _authorizedFetchValueDescription:
essential: "Allow some limited unsigned requests. Provides a hybrid between \"Never\" and \"Always\" by exposing only the minimum profile metadata that is required for federation with older software."
staff: "Use the default value of \"{value}\" recommended by the instance staff."
authorizedFetchLegacyWarning: "The configuration property 'checkActivityPubGetSignature' has been deprecated and replaced with the new Authorized Fetch setting. Please remove it from your configuration file."
+
+_followRequest:
+ recieved: "Received"
+ sent: "Sent"