summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.config/docker_example.yml38
-rw-r--r--.config/example.yml34
-rw-r--r--CHANGELOG.md95
-rw-r--r--CONTRIBUTING.md24
-rw-r--r--COPYING2
-rw-r--r--Dockerfile9
-rw-r--r--SECURITY.md5
-rw-r--r--locales/ar-SA.yml3
-rw-r--r--locales/bn-BD.yml3
-rw-r--r--locales/ca-ES.yml161
-rw-r--r--locales/cs-CZ.yml3
-rw-r--r--locales/de-DE.yml102
-rw-r--r--locales/en-US.yml104
-rw-r--r--locales/es-ES.yml31
-rw-r--r--locales/fr-FR.yml3
-rw-r--r--locales/id-ID.yml3
-rw-r--r--locales/it-IT.yml155
-rw-r--r--locales/ja-JP.yml104
-rw-r--r--locales/ja-KS.yml77
-rw-r--r--locales/ko-GS.yml3
-rw-r--r--locales/ko-KR.yml107
-rw-r--r--locales/lo-LA.yml3
-rw-r--r--locales/nl-NL.yml41
-rw-r--r--locales/no-NO.yml3
-rw-r--r--locales/pl-PL.yml3
-rw-r--r--locales/pt-PT.yml130
-rw-r--r--locales/ro-RO.yml3
-rw-r--r--locales/ru-RU.yml13
-rw-r--r--locales/sk-SK.yml3
-rw-r--r--locales/th-TH.yml3
-rw-r--r--locales/tr-TR.yml1
-rw-r--r--locales/uk-UA.yml3
-rw-r--r--locales/uz-UZ.yml3
-rw-r--r--locales/vi-VN.yml3
-rw-r--r--locales/zh-CN.yml155
-rw-r--r--locales/zh-TW.yml145
-rw-r--r--package.json2
-rw-r--r--packages/backend/migration/1709126576000-optimize-emoji-index.js18
-rw-r--r--packages/backend/package.json2
-rw-r--r--packages/backend/src/GlobalModule.ts10
-rw-r--r--packages/backend/src/boot/entry.ts18
-rw-r--r--packages/backend/src/boot/master.ts33
-rw-r--r--packages/backend/src/config.ts23
-rw-r--r--packages/backend/src/const.ts12
-rw-r--r--packages/backend/src/core/AbuseReportNotificationService.ts32
-rw-r--r--packages/backend/src/core/AiService.ts0
-rw-r--r--packages/backend/src/core/CaptchaService.ts299
-rw-r--r--packages/backend/src/core/CustomEmojiService.ts242
-rw-r--r--packages/backend/src/core/FetchInstanceMetadataService.ts12
-rw-r--r--packages/backend/src/core/FileInfoService.ts1
-rw-r--r--packages/backend/src/core/MfmService.ts33
-rw-r--r--packages/backend/src/core/NoteCreateService.ts35
-rw-r--r--packages/backend/src/core/S3Service.ts2
-rw-r--r--packages/backend/src/core/SearchService.ts347
-rw-r--r--packages/backend/src/core/SystemWebhookService.ts31
-rw-r--r--packages/backend/src/core/UserBlockingService.ts8
-rw-r--r--packages/backend/src/core/UserFollowingService.ts32
-rw-r--r--packages/backend/src/core/UserService.ts9
-rw-r--r--packages/backend/src/core/UserWebhookService.ts25
-rw-r--r--packages/backend/src/core/UtilityService.ts7
-rw-r--r--packages/backend/src/core/WebAuthnService.ts4
-rw-r--r--packages/backend/src/core/activitypub/ApRendererService.ts3
-rw-r--r--packages/backend/src/core/activitypub/ApResolverService.ts28
-rw-r--r--packages/backend/src/core/activitypub/misc/contexts.ts5
-rw-r--r--packages/backend/src/core/activitypub/models/ApNoteService.ts4
-rw-r--r--packages/backend/src/core/activitypub/type.ts5
-rw-r--r--packages/backend/src/core/entities/EmojiEntityService.ts90
-rw-r--r--packages/backend/src/core/entities/MetaEntityService.ts1
-rw-r--r--packages/backend/src/core/entities/NoteEntityService.ts9
-rw-r--r--packages/backend/src/misc/json-schema.ts7
-rw-r--r--packages/backend/src/models/json-schema/emoji.ts83
-rw-r--r--packages/backend/src/models/json-schema/meta.ts5
-rw-r--r--packages/backend/src/postgres.ts59
-rw-r--r--packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts26
-rw-r--r--packages/backend/src/queue/processors/CleanChartsProcessorService.ts1
-rw-r--r--packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts7
-rw-r--r--packages/backend/src/queue/processors/ResyncChartsProcessorService.ts1
-rw-r--r--packages/backend/src/queue/processors/TickChartsProcessorService.ts1
-rw-r--r--packages/backend/src/queue/types.ts17
-rw-r--r--packages/backend/src/server/ActivityPubServerService.ts4
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts1614
-rw-r--r--packages/backend/src/server/api/endpoint-list.ts399
-rw-r--r--packages/backend/src/server/api/endpoints.ts818
-rw-r--r--packages/backend/src/server/api/endpoints/admin/captcha/current.ts70
-rw-r--r--packages/backend/src/server/api/endpoints/admin/captcha/save.ts129
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add.ts31
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/copy.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/update.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/ap/show.ts65
-rw-r--r--packages/backend/src/server/api/endpoints/i/apps.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/pages/update.ts20
-rw-r--r--packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts126
-rw-r--r--packages/backend/src/server/api/openapi/schemas.ts4
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts9
-rw-r--r--packages/backend/test-federation/compose.tpl.yml1
-rw-r--r--packages/backend/test-federation/compose.yml3
-rw-r--r--packages/backend/test-federation/test/note.test.ts6
-rw-r--r--packages/backend/test/e2e/timelines.ts2
-rw-r--r--packages/backend/test/unit/AbuseReportNotificationService.ts50
-rw-r--r--packages/backend/test/unit/CaptchaService.ts622
-rw-r--r--packages/backend/test/unit/CustomEmojiService.ts817
-rw-r--r--packages/backend/test/unit/MfmService.ts18
-rw-r--r--packages/backend/test/unit/SystemWebhookService.ts49
-rw-r--r--packages/backend/test/unit/UserWebhookService.ts91
-rw-r--r--packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts12
-rw-r--r--packages/frontend-embed/package.json5
-rw-r--r--packages/frontend-embed/src/boot.ts20
-rw-r--r--packages/frontend-embed/src/components/EmAcct.vue2
-rw-r--r--packages/frontend-embed/src/components/EmMention.vue2
-rw-r--r--packages/frontend-embed/src/components/EmNotes.vue3
-rw-r--r--packages/frontend-embed/src/components/EmUrl.vue2
-rw-r--r--packages/frontend-embed/src/index.html38
-rw-r--r--packages/frontend-embed/src/theme.ts23
-rw-r--r--packages/frontend-embed/tsconfig.json4
-rw-r--r--packages/frontend-embed/vite.config.local-dev.ts96
-rw-r--r--packages/frontend-embed/vite.config.ts13
-rw-r--r--packages/frontend-shared/build.js8
-rw-r--r--packages/frontend-shared/package.json1
-rw-r--r--packages/frontend/.storybook/fake-utils.ts154
-rw-r--r--packages/frontend/.storybook/fakes.ts91
-rw-r--r--packages/frontend/.storybook/generate.tsx4
-rw-r--r--packages/frontend/package.json7
-rw-r--r--packages/frontend/src/_dev_boot_.ts90
-rw-r--r--packages/frontend/src/account.ts8
-rw-r--r--packages/frontend/src/boot/common.ts5
-rw-r--r--packages/frontend/src/boot/main-boot.ts40
-rw-r--r--packages/frontend/src/components/MkAsUi.vue19
-rw-r--r--packages/frontend/src/components/MkCaptcha.vue64
-rw-r--r--packages/frontend/src/components/MkChannelPreview.vue2
-rw-r--r--packages/frontend/src/components/MkContainer.vue10
-rw-r--r--packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue2
-rw-r--r--packages/frontend/src/components/MkDriveFileThumbnail.vue28
-rw-r--r--packages/frontend/src/components/MkFolder.vue6
-rw-r--r--packages/frontend/src/components/MkFormFooter.vue9
-rw-r--r--packages/frontend/src/components/MkInstanceStats.vue18
-rw-r--r--packages/frontend/src/components/MkInstanceTicker.vue41
-rw-r--r--packages/frontend/src/components/MkMention.vue2
-rw-r--r--packages/frontend/src/components/MkModal.vue29
-rw-r--r--packages/frontend/src/components/MkNote.vue32
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue4
-rw-r--r--packages/frontend/src/components/MkNoteMediaGrid.vue109
-rw-r--r--packages/frontend/src/components/MkPagingButtons.vue124
-rw-r--r--packages/frontend/src/components/MkPoll.vue4
-rw-r--r--packages/frontend/src/components/MkPostForm.vue32
-rw-r--r--packages/frontend/src/components/MkPostFormAttaches.vue8
-rw-r--r--packages/frontend/src/components/MkRemoteEmojiEditDialog.vue132
-rw-r--r--packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts106
-rw-r--r--packages/frontend/src/components/MkRoleSelectDialog.vue200
-rw-r--r--packages/frontend/src/components/MkSignin.input.vue2
-rw-r--r--packages/frontend/src/components/MkSignupDialog.form.vue2
-rw-r--r--packages/frontend/src/components/MkSignupDialog.rules.vue6
-rw-r--r--packages/frontend/src/components/MkSortOrderEditor.define.ts11
-rw-r--r--packages/frontend/src/components/MkSortOrderEditor.vue118
-rw-r--r--packages/frontend/src/components/MkSubNoteContent.vue4
-rw-r--r--packages/frontend/src/components/MkSuperMenu.vue2
-rw-r--r--packages/frontend/src/components/MkTagItem.stories.impl.ts70
-rw-r--r--packages/frontend/src/components/MkTagItem.vue76
-rw-r--r--packages/frontend/src/components/MkUserSelectDialog.vue11
-rw-r--r--packages/frontend/src/components/MkVisitorDashboard.vue6
-rw-r--r--packages/frontend/src/components/MkWidgets.vue19
-rw-r--r--packages/frontend/src/components/global/MkAcct.vue2
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.vue12
-rw-r--r--packages/frontend/src/components/global/MkUrl.vue2
-rw-r--r--packages/frontend/src/components/grid/MkCellTooltip.vue35
-rw-r--r--packages/frontend/src/components/grid/MkDataCell.vue418
-rw-r--r--packages/frontend/src/components/grid/MkDataRow.vue72
-rw-r--r--packages/frontend/src/components/grid/MkGrid.stories.impl.ts223
-rw-r--r--packages/frontend/src/components/grid/MkGrid.vue1374
-rw-r--r--packages/frontend/src/components/grid/MkHeaderCell.vue216
-rw-r--r--packages/frontend/src/components/grid/MkHeaderRow.vue60
-rw-r--r--packages/frontend/src/components/grid/MkNumberCell.vue61
-rw-r--r--packages/frontend/src/components/grid/cell-validators.ts110
-rw-r--r--packages/frontend/src/components/grid/cell.ts88
-rw-r--r--packages/frontend/src/components/grid/column.ts53
-rw-r--r--packages/frontend/src/components/grid/grid-event.ts46
-rw-r--r--packages/frontend/src/components/grid/grid-utils.ts215
-rw-r--r--packages/frontend/src/components/grid/grid.ts49
-rw-r--r--packages/frontend/src/components/grid/row.ts68
-rw-r--r--packages/frontend/src/components/hook/useLoading.ts52
-rw-r--r--packages/frontend/src/index.html39
-rw-r--r--packages/frontend/src/os.ts26
-rw-r--r--packages/frontend/src/pages/about-misskey.vue0
-rw-r--r--packages/frontend/src/pages/about.vue47
-rw-r--r--packages/frontend/src/pages/admin/bot-protection.vue277
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.impl.ts57
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.list.logs.vue39
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.list.search.vue213
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue660
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue481
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.vue35
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.logs.vue88
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue503
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts160
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager2.vue51
-rw-r--r--packages/frontend/src/pages/admin/index.vue16
-rw-r--r--packages/frontend/src/pages/admin/roles.editor.vue10
-rw-r--r--packages/frontend/src/pages/admin/roles.vue13
-rw-r--r--packages/frontend/src/pages/channel.vue6
-rw-r--r--packages/frontend/src/pages/channels.vue35
-rw-r--r--packages/frontend/src/pages/clip.vue5
-rw-r--r--packages/frontend/src/pages/custom-emojis-manager.vue29
-rw-r--r--packages/frontend/src/pages/emoji-edit-dialog.vue2
-rw-r--r--packages/frontend/src/pages/explore.users.vue3
-rw-r--r--packages/frontend/src/pages/miauth.vue21
-rw-r--r--packages/frontend/src/pages/note.vue12
-rw-r--r--packages/frontend/src/pages/search.note.vue24
-rw-r--r--packages/frontend/src/pages/search.user.vue5
-rw-r--r--packages/frontend/src/pages/settings/accounts.vue107
-rw-r--r--packages/frontend/src/pages/settings/general.vue3
-rw-r--r--packages/frontend/src/pages/settings/index.vue2
-rw-r--r--packages/frontend/src/pages/settings/mute-block.vue29
-rw-r--r--packages/frontend/src/pages/settings/privacy.vue9
-rw-r--r--packages/frontend/src/pages/settings/statusbar.statusbar.vue3
-rw-r--r--packages/frontend/src/pages/theme-editor.vue2
-rw-r--r--packages/frontend/src/pages/user/files.vue56
-rw-r--r--packages/frontend/src/pages/user/home.vue9
-rw-r--r--packages/frontend/src/pages/user/index.files.vue58
-rw-r--r--packages/frontend/src/pages/user/index.vue13
-rw-r--r--packages/frontend/src/pages/welcome.entrance.a.vue2
-rw-r--r--packages/frontend/src/router/definition.ts4
-rw-r--r--packages/frontend/src/scripts/aiscript/api.ts46
-rw-r--r--packages/frontend/src/scripts/aiscript/common.ts15
-rw-r--r--packages/frontend/src/scripts/aiscript/ui.ts139
-rw-r--r--packages/frontend/src/scripts/autocomplete.ts2
-rw-r--r--packages/frontend/src/scripts/check-word-mute.ts6
-rw-r--r--packages/frontend/src/scripts/code-highlighter.ts6
-rw-r--r--packages/frontend/src/scripts/file-drop.ts121
-rw-r--r--packages/frontend/src/scripts/get-note-menu.ts27
-rw-r--r--packages/frontend/src/scripts/get-user-menu.ts2
-rw-r--r--packages/frontend/src/scripts/key-event.ts153
-rw-r--r--packages/frontend/src/scripts/lookup.ts38
-rw-r--r--packages/frontend/src/scripts/merge.ts8
-rw-r--r--packages/frontend/src/scripts/misskey-api.ts18
-rw-r--r--packages/frontend/src/scripts/please-login.ts12
-rw-r--r--packages/frontend/src/scripts/select-file.ts20
-rw-r--r--packages/frontend/src/scripts/sound.ts4
-rw-r--r--packages/frontend/src/server-context.ts12
-rw-r--r--packages/frontend/src/store.ts6
-rw-r--r--packages/frontend/src/ui/_common_/common.ts20
-rw-r--r--packages/frontend/src/ui/_common_/navbar.vue22
-rw-r--r--packages/frontend/src/ui/_common_/statusbars.vue3
-rw-r--r--packages/frontend/src/ui/universal.vue2
-rw-r--r--packages/frontend/src/widgets/index.ts10
-rw-r--r--packages/frontend/test/aiscript/api.test.ts401
-rw-r--r--packages/frontend/test/aiscript/common.test.ts23
-rw-r--r--packages/frontend/test/aiscript/ui.test.ts825
-rw-r--r--packages/frontend/tsconfig.json4
-rw-r--r--packages/frontend/vite.config.local-dev.ts102
-rw-r--r--packages/frontend/vite.config.ts13
-rw-r--r--packages/misskey-bubble-game/build.js8
-rw-r--r--packages/misskey-js/LICENSE2
-rw-r--r--packages/misskey-js/api-extractor.json2
-rw-r--r--packages/misskey-js/build.js9
-rw-r--r--packages/misskey-js/package.json2
-rw-r--r--packages/misskey-js/src/api.ts2
-rw-r--r--packages/misskey-js/src/autogen/apiClientJSDoc.ts806
-rw-r--r--packages/misskey-js/src/autogen/entities.ts266
-rw-r--r--packages/misskey-js/src/autogen/models.ts1
-rw-r--r--packages/misskey-js/src/streaming.ts16
-rw-r--r--packages/misskey-reversi/build.js8
-rw-r--r--packages/shared/eslint.config.js7
-rw-r--r--pnpm-lock.yaml1080
-rw-r--r--scripts/dev.mjs27
264 files changed, 15764 insertions, 4908 deletions
diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index ce2daf3aec..1e03e902bf 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -38,14 +38,14 @@
# 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
-# DISCLOSE THE SOURCE CODE WEHN A USER REQUESTS IT BY
+# 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
# license again carefully. Anyway, enabling this
# option will automatically generate and publish a
# tarball at build time, protecting you from
# inadvertent license violations. (There is no legal
-# guarantee, of course.) The tarball will generated
+# guarantee, of course.) The tarball will be generated
# from the root directory of your codebase. So it is
# also recommended to check <built/tarball> directory
# once after building and before activating the server
@@ -171,10 +171,28 @@ redis:
# #prefix: example-prefix
# #db: 1
-# ┌───────────────────────────â”
-#───┘ MeiliSearch configuration └─────────────────────────────
+# ┌───────────────────────────────â”
+#───┘ Fulltext search configuration └─────────────────────────────
-# You can set scope to local (default value) or global
+# These are the setting items for the full-text search provider.
+fulltextSearch:
+ # You can select the ID generation method.
+ # - sqlLike (default)
+ # Use SQL-like search.
+ # This is a standard feature of PostgreSQL, so no special extensions are required.
+ # - sqlPgroonga
+ # Use pgroonga.
+ # You need to install pgroonga and configure it as a PostgreSQL extension.
+ # In addition to the above, you need to create a pgroonga index on the text column of the note table.
+ # see: https://pgroonga.github.io/tutorial/
+ # - meilisearch
+ # Use Meilisearch.
+ # You need to install Meilisearch and configure.
+ provider: sqlLike
+
+# For Meilisearch settings.
+# If you select "meilisearch" for "fulltextSearch.provider", it must be set.
+# You can set scope to local or global (default value)
# (include notes from remote).
#meilisearch:
@@ -317,3 +335,13 @@ checkActivityPubGetSignature: false
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
#filePermissionBits: '644'
+
+# Log settings
+# logging:
+# sql:
+# # Outputs query parameters during SQL execution to the log.
+# # default: false
+# enableQueryParamLogging: false
+# # Disable query truncation. If set to true, the full text of the query will be output to the log.
+# # default: false
+# disableQueryTruncation: false
diff --git a/.config/example.yml b/.config/example.yml
index f781b72b91..6303e5ecf4 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -196,10 +196,28 @@ redis:
# # You can specify more ioredis options...
# #username: example-username
-# ┌───────────────────────────â”
-#───┘ MeiliSearch configuration └─────────────────────────────
+# ┌───────────────────────────────â”
+#───┘ Fulltext search configuration └─────────────────────────────
-# You can set scope to local (default value) or global
+# These are the setting items for the full-text search provider.
+fulltextSearch:
+ # You can select the ID generation method.
+ # - sqlLike (default)
+ # Use SQL-like search.
+ # This is a standard feature of PostgreSQL, so no special extensions are required.
+ # - sqlPgroonga
+ # Use pgroonga.
+ # You need to install pgroonga and configure it as a PostgreSQL extension.
+ # In addition to the above, you need to create a pgroonga index on the text column of the note table.
+ # see: https://pgroonga.github.io/tutorial/
+ # - meilisearch
+ # Use Meilisearch.
+ # You need to install Meilisearch and configure.
+ provider: sqlLike
+
+# For Meilisearch settings.
+# If you select "meilisearch" for "fulltextSearch.provider", it must be set.
+# You can set scope to local or global (default value)
# (include notes from remote).
#meilisearch:
@@ -353,3 +371,13 @@ checkActivityPubGetSignature: false
# Permission bits are specified as a base-8 string representing User/Group/Other permissions.
# This setting is only useful for custom deployments, such as using a reverse proxy to serve media.
#filePermissionBits: '644'
+
+# Log settings
+# logging:
+# sql:
+# # Outputs query parameters during SQL execution to the log.
+# # default: false
+# enableQueryParamLogging: false
+# # Disable query truncation. If set to true, the full text of the query will be output to the log.
+# # default: false
+# disableQueryTruncation: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2dbc457710..f12e24209b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,96 @@
+## 2025.2.0
+
+### General
+- Fix: Docker ã®ãƒ“ルドã«å¤±æ•—ã™ã‚‹å•題を修正
+ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/883)
+
+### Client
+- Fix: 一部環境ã§ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ãªãƒ•ァイルをå«ã‚€ãƒŽãƒ¼ãƒˆã®éžè¡¨ç¤ºãŒåйã‹ãªã„å•題
+- Fix: データセーãƒãƒ¼æœ‰åŠ¹æ™‚ã«ã‚‚ユーザーページã®ã€Œãƒ•ァイルã€ã‚¿ãƒ–ã§ç”»åƒãŒèª­ã¿è¾¼ã¾ã‚Œã¦ã—ã¾ã†å•題を修正
+
+### Server
+- Fix: 個別ãŠçŸ¥ã‚‰ã›ãƒšãƒ¼ã‚¸ã®metaã‚¿ã‚°å‡ºåŠ›ã®æ¡ä»¶ãŒé–“é•ã£ã¦ã„ãŸã®ã‚’修正
+
+
+## 2025.1.0
+
+### Note
+- [é‡è¦] ノート検索プロãƒã‚¤ãƒ€ã®è¿½åŠ ã«ä¼´ã„ã€configファイル(default.ymlãªã©ï¼‰ã®æ§‹æˆãŒå°‘ã—変ã‚りã¾ã™.
+ - æ–°ã—ã„設定項目"fulltextSearch.provider"ãŒè¿½åŠ ã•れã¾ã—ãŸ. sqlLike, sqlPgroonga, meilisearchã®ã„ãšã‚Œã‹ã‚’設定出æ¥ã¾ã™.
+ - ã™ã§ã«Meilisearchã‚’ãŠä½¿ã„ã®å ´åˆã€ **"fulltextSearch.provider"ã‚’"meilisearch"ã«è¨­å®šã™ã‚‹å¿…è¦** ãŒã‚りã¾ã™.
+ - 詳細㯠#14730 ãŠã‚ˆã³ `.config/example.yml` ã¾ãŸã¯ `.config/docker_example.yml`ã®'Fulltext search configuration'ã‚’ã”å‚照願ã„ã¾ã™.
+- ã€é–‹ç™ºè€…å‘ã‘】従æ¥ã®é–‹ç™ºãƒ¢ãƒ¼ãƒ‰ã§HMRãŒæ©Ÿèƒ½ã—ãªã„å•題ãŒä¿®æ­£ã•れãŸãŸã‚ã€ãƒãƒƒã‚¯ã‚¨ãƒ³ãƒ‰ãƒ»ãƒ•ロントエンド分離型ã®é–‹ç™ºãƒ¢ãƒ¼ãƒ‰ãŒå‰Šé™¤ã•れã¾ã—ãŸã€‚開発環境ã«ãŠã„ã¦configã®å¤‰æ›´ãŒå¿…è¦ã¨ãªã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚
+
+### General
+- Feat: カスタム絵文字管ç†ç”»é¢ã‚’リニューアル #10996
+ * β版ã¨ã—ã¦å…¬é–‹ã®ãŸã‚ã€æ—§ç”»é¢ã‚‚引ãç¶šã利用å¯èƒ½ã§ã™
+
+### Client
+- Enhance: PCç”»é¢ã§ãƒãƒ£ãƒ³ãƒãƒ«ãŒè¤‡æ•°åˆ—ã§è¡¨ç¤ºã•れるよã†ã«
+ (Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13)
+- Enhance: 照会ã«å¤±æ•—ã—ãŸå ´åˆã€ãã®ç†ç”±ã‚’表示ã™ã‚‹ã‚ˆã†ã«
+- Enhance: ãƒ¯ãƒ¼ãƒ‰ãƒŸãƒ¥ãƒ¼ãƒˆã§æ¤œçŸ¥ã•れãŸãƒ¯ãƒ¼ãƒ‰ã‚’表示ã§ãるよã†ã«
+- Enhance: リモートã®ãƒŽãƒ¼ãƒˆã®ãƒªãƒ³ã‚¯ã‚’コピーã§ãるよã†ã«
+- Enhance: 連åˆãŒãƒ›ãƒ¯ã‚¤ãƒˆãƒªã‚¹ãƒˆåŒ–・無効化ã•れã¦ã„るサーãƒãƒ¼å‘ã‘ã®ãƒ‡ã‚¶ã‚¤ãƒ³ä¿®æ­£
+- Enhance: AiScriptã®ã‚»ãƒ¼ãƒ–データを明示的ã«å‰Šé™¤ã™ã‚‹é–¢æ•°`Mk:remove`を追加
+- Enhance: ãƒŽãƒ¼ãƒˆã®æ·»ä»˜ãƒ•ァイルを一覧ã§é¡ã‚Œã‚‹ã€Œãƒ•ァイルã€ã‚¿ãƒ–を追加
+ (Based on https://github.com/Otaku-Social/maniakey/pull/14)
+- Enhance: AiScriptã®æ‹¡å¼µAPI関数ã«ãŠã„ã¦å¼•æ•°ã®åž‹ãƒã‚§ãƒƒã‚¯ã‚’より厳格ã«
+- Enhance: クエリパラメータã§uiを一時的ã«å¤‰æ›´ã§ãるよã†ã« #15240
+- Enhance: リモート絵文字ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆæ™‚ã«è©³ç´°ã‚’確èªã§ãるよã†ã« #15336
+- Fix: ç”»é¢ã‚µã‚¤ã‚ºãŒå¤‰ã‚ã£ãŸéš›ã«ãƒŠãƒ“ゲーションãƒãƒ¼ãŒè‡ªå‹•ã§æŠ˜ã‚ŠãŸãŸã¾ã‚Œãªã„å•題を修正
+- Fix: サーãƒãƒ¼æƒ…報メニューã«åŒºåˆ‡ã‚Šç·šãŒä¸è¶³ã—ã¦ã„ãŸã®ã‚’修正
+- Fix: ノートãŒãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ã„るユーザーã—ã‹è¦‹ã‚Œãªã„å ´åˆã«ãƒ­ã‚°ã‚¤ãƒ³ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ã‚’é–‰ã˜ã‚‹ã¨ãã®å¾Œã®å‹•ç·šãŒãªããªã‚‹å•題を修正
+- Fix: 公開範囲ãŒãƒ›ãƒ¼ãƒ ã®ãƒŽãƒ¼ãƒˆã®åŸ‹ã‚è¾¼ã¿ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆãŒèª­ã¿è¾¼ã¾ã‚Œãªã„å•題を修正
+ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803)
+- Fix: 絵文字管ç†ç”»é¢ã§ä¸€éƒ¨ã®çµµæ–‡å­—ãŒè¡¨ç¤ºã•れãªã„å•題を修正
+- Fix: プラグイン `register_note_view_interruptor` ã§ãƒŽãƒ¼ãƒˆã®ã‚µãƒ¼ãƒãƒ¼æƒ…å ±ã®æ›¸ãæ›ãˆãŒã§ããªã„å•題を修正
+- Fix: Botプロテクションã®è¨­å®šå¤‰æ›´æ™‚ã¯å®Ÿéš›ã«æ¤œè¨¼ã‚’通éŽã—ãªã„ã¨ä¿å­˜ã§ããªã„よã†ã«( #15137 )
+- Fix: ノート検索ãŒä½¿ç”¨ã§ããªã„å ´åˆã§ã‚‚ãƒãƒ£ãƒ³ãƒãƒ«ã®ãƒŽãƒ¼ãƒˆæ¤œç´¢æ¬„ãŒã§ã¦ã„ãŸå•題を修正
+- Fix: `Ui:C:select`ã§å€¤ã®å¤‰æ›´ãŒç”»é¢ã«å映ã•れãªã„å•題を修正
+- Fix: MiAuthèªå¯ç”»é¢ã§ã€èªå¯å‡¦ç†ã«å¤±æ•—ã—ãŸå ´åˆã§ã‚‚コールãƒãƒƒã‚¯URLã«é·ç§»ã—ã¦ã—ã¾ã†å•題を修正
+ (Cherry-picked from https://github.com/TeamNijimiss/misskey/commit/800359623e41a662551d774de15b0437b6849bb4)
+- Fix: ノート作æˆç”»é¢ã§ãƒ•ã‚¡ã‚¤ãƒ«ã®æ·»ä»˜å¯èƒ½å€‹æ•°ã‚’è¶…ãˆã¦ã‚‚ãƒŽãƒ¼ãƒˆãƒœã‚¿ãƒ³ãŒæŠ¼ã›ã¦ã„ãŸå•題を修正
+- Fix: 「アカウントを管ç†ã€ç”»é¢ã§ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼æƒ…å ±ã®å–å¾—ã«å¤±æ•—ã—ãŸã‚¢ã‚«ã‚¦ãƒ³ãƒˆï¼ˆå‰Šé™¤ã•れãŸã‚¢ã‚«ã‚¦ãƒ³ãƒˆãªã©ï¼‰ãŒè¡¨ç¤ºã•れãªã„å•題を修正
+- Fix: MacOSã§Chrome系ブラウザを使用ã—ã¦ã„ã‚‹å ´åˆã«ã€Misskeyã‚’é–‰ã˜ãŸéš›ã«ä»–ã®ã‚¿ãƒ–ã®ã‚ªãƒ¼ãƒ‡ã‚£ã‚ªæ©Ÿèƒ½ã¨å¹²æ¸‰ã™ã‚‹å•題を修正
+- Fix: 言語データã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥çжæ³ã«ã‚ˆã£ã¦ã¯ã€åŸ‹ã‚è¾¼ã¿ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆãŒæ­£ã—ãèµ·å‹•ã—ãªã„å•題を修正
+- Fix: 「削除ã—ã¦ç·¨é›†ã€ã§ãƒŽãƒ¼ãƒˆã®å¼•用を解除出æ¥ãªã‹ã£ãŸå•題を修正( #14476 )
+- Fix: RSSã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆãŒæ­£ã—ã表示ã•れãªã„å•題を修正
+ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/857)
+- Fix: ワードミュートã®ä¿å­˜å¤±æ•—時ã«APIã‚¨ãƒ©ãƒ¼ãŒæ¡ã‚Šã¤ã¶ã•れる事ãŒã‚ã‚‹ã®ã‚’修正
+- Fix: アンケートã§ãƒªãƒ¢ãƒ¼ãƒˆã®çµµæ–‡å­—ãŒæ­£ã—ãæç”»ã§ããªã„å•題ã®ä¿®æ­£
+ (Cherry-picked from https://github.com/yojo-art/cherrypick/pull/153)
+- Fix: éžãƒ­ã‚°ã‚¤ãƒ³æ™‚ã®ã‚µãƒ¼ãƒãƒ¼æ¦‚è¦ç”»é¢ã®ãƒ¡ãƒ‹ãƒ¥ãƒ¼ãƒœã‚¿ãƒ³ãŒæŠ¼ã›ãªã„ã“ã¨ãŒã‚ã‚‹ã®ã‚’修正
+ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/656)
+- Fix: URLã«ã¯ã˜ã‚ã‹ã‚‰`#pswp`ãŒå«ã¾ã‚Œã¦ã„ã‚‹å ´åˆã«ç”»åƒãƒ“ューワーãŒãƒ–ãƒ©ã‚¦ã‚¶ã®æˆ»ã‚‹ãƒœã‚¿ãƒ³ã§é–‰ã˜ã‚‰ã‚Œãªã„å•題を修正
+- Fix: ロール作æˆç”»é¢ã§è¨­å®šã§ãã‚‹ã‚¢ã‚¤ã‚³ãƒ³ãƒ‡ã‚³ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã®æœ€å¤§å–付個数を16ã«åˆ¶é™
+- Fix: Firefox Nightlyãªã©ã§ã‚¢ã‚¤ã‚³ãƒ³ãŒèª­ã¿è¾¼ã‚ãªã„å•題を修正
+
+### Server
+- Enhance: pg_bigmãŒåˆ©ç”¨ã§ãるよã†ã€ãƒŽãƒ¼ãƒˆã®æ¤œç´¢ã‚’ILIKE演算å­ã§ãªãLIKE演算å­ã§LOWER()ã‚’ã‹ã‘ãŸãƒ†ã‚­ã‚¹ãƒˆã«å¯¾ã—ã¦è¡Œã†ã‚ˆã†ã«
+- Enhance: ノート検索ã®é¸æŠžè‚¢ã¨ã—ã¦pgroongaã«å¯¾å¿œ ( #14730 )
+- Enhance: ãƒãƒ£ãƒ¼ãƒˆæ›´æ–°æ™‚ã«DBã«åŒæ™‚接続ã—ãªã„よã†ã«
+ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/830)
+- Enhance: config(default.yml)ã‹ã‚‰SQLログ全文を出力ã™ã‚‹ã‹å¦ã‹ã‚’設定å¯èƒ½ã« ( #15266 )
+- Fix: ユーザーã®ãƒ—ロフィール画é¢ã‚’アドレス入力ãªã©ã§ç›´æŽ¥è¡¨ç¤ºã—ãŸéš›ã«æ¦‚è¦ã‚¿ãƒ–ã®æç”»ã«å¤±æ•—ã™ã‚‹å•題ã®ä¿®æ­£( #15032 )
+- Fix: èµ·å‹•å‰ã®ç–Žé€šãƒã‚§ãƒƒã‚¯ãŒæ©Ÿèƒ½ã—ãªããªã£ã¦ã„ãŸå•題を修正
+ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737)
+- Fix: ノートã®é–²è¦§ã«ãƒ­ã‚°ã‚¤ãƒ³å¿…é ˆã«ã—ã¦ã‚‚Feedã§ãƒŽãƒ¼ãƒˆãŒè¡¨ç¤ºã•れã¦ã—ã¾ã†å•題を修正
+- Fix: 絵文字ã®é€£åˆã§ãƒ©ã‚¤ã‚»ãƒ³ã‚¹æ¬„を相互ã«ã‚„りå–りã™ã‚‹ã‚ˆã†ã« ( #10859, #14109 )
+- Fix: ロックダウンã•ã‚ŒãŸæœŸé–“指定ã®ãƒŽãƒ¼ãƒˆãŒStreaming経由ã§LTLã«å‡ºç¾ã™ã‚‹ã®ã‚’修正 ( #15200 )
+- Fix: disableClustering設定時ã®åˆæœŸåŒ–ロジックを調整( #15223 )
+- Fix: URLã¨URIãŒç•°ãªã‚‹ã‚¨ãƒ³ãƒ†ã‚£ãƒ†ã‚£ã®ç…§ä¼šã«å¤±æ•—ã™ã‚‹å•題を修正( #15039 )
+- Fix: ActivityPubリクエストã‹ã©ã†ã‹ã®åˆ¤å®šãŒæ­£ã—ããªã„å•題を修正
+ (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/869)
+- Fix: `/api/pages/update`ã«ã¦`name`を指定ã›ãšã«ãƒªã‚¯ã‚¨ã‚¹ãƒˆã™ã‚‹ã¨ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã™ã‚‹å•題を修正
+- Fix: AIセンシティブ判定㌠arm64 環境ã§å‹•作ã—ãªã„å•題を修正
+- Fix: éžMisskeyç³»ã®ã‚½ãƒ•トウェアã‹ã‚‰HTML`<ruby>`ã‚¿ã‚°ã‚’å«ã‚€ãƒŽãƒ¼ãƒˆã‚’å—ä¿¡ã—ãŸå ´åˆã€MFMã®èª­ã¿ä»®å(ルビ)文法ã«å¤‰æ›ã—ã¦è¡¨ç¤º
+- Fix: 連åˆOFFã§æŠ•ç¨¿ã•れãŸãƒŽãƒ¼ãƒˆã«å¯¾ã™ã‚‹å†—é•·ãªå‡¦ç†ã‚’抑止 ( #15018 )
+- Fix: `/api.json`ã®ãƒ¬ã‚¹ãƒãƒ³ã‚¹ãŒ2回目ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆä»¥é™ãŠã‹ã—ããªã‚‹å•題を修正
+
+### Misskey.js
+- Feat: allow setting `binaryType` of WebSocket connection
+
## 2024.11.0
### Note
@@ -37,7 +130,7 @@
- Fix: デッキã®ã‚¿ã‚¤ãƒ ãƒ©ã‚¤ãƒ³ã‚«ãƒ©ãƒ ã§ã€Œã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ãªãƒ•ァイルをå«ã‚€ãƒŽãƒ¼ãƒˆã‚’表示ã€è¨­å®šãŒä½¿ç”¨ã§ããªã‹ã£ãŸå•題を修正
- Fix: Encode RSS urls with escape sequences before fetching allowing query parameters to be used
- Fix: リンク切れを修正
-= Fix: ノート投稿ボタンã«ãƒ›ãƒãƒ¼æ™‚ã®ã‚¹ã‚¿ã‚¤ãƒ«ãŒé©ç”¨ã•れã¦ã„ãªã„ã®ã‚’修正
+- Fix: ノート投稿ボタンã«ãƒ›ãƒãƒ¼æ™‚ã®ã‚¹ã‚¿ã‚¤ãƒ«ãŒé©ç”¨ã•れã¦ã„ãªã„ã®ã‚’修正
(Cherry-picked from https://github.com/taiyme/misskey/pull/305)
- Fix: メールアドレス登録有効化時ã®ã€Œå®Œäº†ã€ãƒ€ã‚¤ã‚¢ãƒ­ã‚°ãƒœãƒƒã‚¯ã‚¹ã®è¡¨ç¤ºæ¡ä»¶ã‚’修正
- Fix: ç”»é¢å¹…ãŒç‹­ã„環境ã§ãƒ‡ã‚¶ã‚¤ãƒ³ãŒå´©ã‚Œã‚‹å•題を修正
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6b01135d11..e7cd453f72 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -204,25 +204,10 @@ pnpm dev
command.
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
-- Vite HMR (just the `vite` command) is available. The behavior may be different from production.
- Service Worker is watched by esbuild.
-- The front end can be viewed by accessing `http://localhost:5173`.
-- The backend listens on the port configured with `port` in .config/default.yml.
-If you have not changed it from the default, it will be "http://localhost:3000".
-If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts.
-
-### `MK_DEV_PREFER=backend pnpm dev`
-pnpm dev has another mode with `MK_DEV_PREFER=backend`.
-
-```
-MK_DEV_PREFER=backend pnpm dev
-```
-
-- This mode is closer to the production environment than the default mode.
-- Vite runs behind the backend (the backend will proxy Vite at /vite).
+- Vite HMR (just the `vite` command) is available. The behavior may be different from production.
+- Vite runs behind the backend (the backend will proxy Vite at /vite and /embed_vite except for websocket used for HMR).
- You can see Misskey by accessing `http://localhost:3000` (Replace `3000` with the port configured with `port` in .config/default.yml).
-- To change the port of Vite, specify with `VITE_PORT` environment variable.
-- HMR may not work in some environments such as Windows.
## Testing
@@ -499,6 +484,11 @@ describe('test', () => {
コード上ã§Misskeyã®ãƒ‰ãƒ¡ã‚¤ãƒ³å›ºæœ‰ã®æ¦‚念ã«ã¯`Mi`ã‚’prefixã™ã‚‹ã“ã¨ã§ã€ä»–ã®ãƒ‰ãƒ¡ã‚¤ãƒ³ã®åŒæ§˜ã®æ¦‚念ã¨åŒºåˆ¥ã§ãã‚‹ã»ã‹ã€åå‰ã®è¡çªã‚’防ã。
ãŸã ã—ã€æ–‡è„ˆä¸ŠMisskeyã®ã‚‚ã®ã‚’指ã™ã“ã¨ãŒæ˜Žã‚‰ã‹ã§ã‚りã€åå‰ã®è¡çªã®æã‚ŒãŒãªã„å ´åˆã¯ã€ä¸€æ™‚çš„ãªãƒ­ãƒ¼ã‚«ãƒ«å¤‰æ•°ã«é™ã£ã¦`Mi`ã‚’çœç•¥ã—ã¦ã‚‚よã„。
+### Misskey.jsã®åž‹ç”Ÿæˆ
+```bash
+pnpm build-misskey-js-with-types
+```
+
### How to resolve conflictions occurred at pnpm-lock.yaml?
Just execute `pnpm` to fix it.
diff --git a/COPYING b/COPYING
index 6a5f3ca1d5..7635bfc913 100644
--- a/COPYING
+++ b/COPYING
@@ -1,5 +1,5 @@
Unless otherwise stated this repository is
-Copyright © 2014-2024 syuilo and contributors
+Copyright © 2014-2025 syuilo and contributors
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
diff --git a/Dockerfile b/Dockerfile
index aff4074079..5c610c3caf 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,6 +6,15 @@ FROM node:${NODE_VERSION} as build
RUN apk add git linux-headers build-base
+ENV COREPACK_DEFAULT_TO_LATEST=0
+
+RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
+ --mount=type=cache,target=/var/lib/apt,sharing=locked \
+ rm -f /etc/apt/apt.conf.d/docker-clean \
+ ; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
+ && apt-get update \
+ && apt-get install -yqq --no-install-recommends \
+ build-essential
ENV PYTHONUNBUFFERED=1
ENV COREPACK_DEFAULT_TO_LATEST=0
RUN apk add --update python3 && ln -sf python3 /usr/bin/python
diff --git a/SECURITY.md b/SECURITY.md
index 8d3d44db41..499fa2c1fa 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -8,6 +8,11 @@ bug report to the GitLab repository.
Thanks for helping make Sharkey safe for everyone.
+> [!note]
+> CNA [requires](https://www.cve.org/ResourcesSupport/AllResources/CNARules#section_5-2_Description) that CVEs include a description in English for inclusion in the CVE Catalog.
+>
+> When creating a security advisory, all content must be written in English (it is acceptable to include a non-English description along with the English one).
+
## When create a patch
If you can also create a patch to fix the vulnerability, please create a PR on the private fork.
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 2f1b391b53..1f885c66a2 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -1584,3 +1584,6 @@ _reversi:
_offlineScreen:
title: "غير متصل - يتعذر الاتصال بالخادم"
header: "يتعذر الاتصال بالخادم"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "غير موجود"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index 6cd577b4a9..9c8566c5b7 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -1348,3 +1348,6 @@ _moderationLogTypes:
resetPassword: "পাসওয়ারà§à¦¡ রিসেট করà§à¦¨"
_reversi:
total: "মোট"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "পাওয়া যায়নি"
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 1aca3390e6..9954b2a747 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -5,6 +5,7 @@ introMisskey: "Benvingut! Misskey és un servei de microblogging descentralitzat
poweredByMisskeyDescription: "{name} És un dels serveis (anomenats instàncies de Misskey) que utilitzen la plataforma de codi obert <b>Misskey</b>."
monthAndDay: "{day}/{month}"
search: "Cercar"
+reset: "Reiniciar"
notifications: "Notificacions"
username: "Nom d'usuari"
password: "Contrasenya"
@@ -43,11 +44,12 @@ favorites: "Favorits"
unfavorite: "Eliminar dels preferits"
favorited: "Afegit als preferits."
alreadyFavorited: "Ja s'ha afegit als preferits."
-cantFavorite: "No s'ha pogut afegir als preferits."
-pin: "Fixar al perfil"
+cantFavorite: "No es pot afegir als preferits."
+pin: "Fixa al perfil"
unpin: "Para de fixar del perfil"
-copyContent: "Copiar el contingut"
-copyLink: "Copiar l'enllaç"
+copyContent: "Copia el contingut"
+copyLink: "Copia l'enllaç"
+copyRemoteLink: "Copiar l'enllaç remot"
copyLinkRenote: "Copiar l'enllaç de la renota"
delete: "Elimina"
deleteAndEdit: "Eliminar i editar"
@@ -103,9 +105,9 @@ enterListName: "Introdueix un nom per a la llista"
privacy: "Privadesa"
makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
defaultNoteVisibility: "Visibilitat per defecte"
-follow: "Seguint"
+follow: "Segueix"
followRequest: "Enviar sol·licitud de seguiment"
-followRequests: "Sol·licituds de seguiment"
+followRequests: "Peticions de seguiment"
unfollow: "Deixar de seguir"
followRequestPending: "Sol·licituds de seguiment pendents"
enterEmoji: "Introduir un emoji"
@@ -195,7 +197,7 @@ setWallpaper: "Defineix el fons de pantalla"
removeWallpaper: "Elimina el fons de pantalla"
searchWith: "Cerca: {q}"
youHaveNoLists: "No tens cap llista"
-followConfirm: "Estàs segur que vols deixar de seguir {name}?"
+followConfirm: "Segur que vols seguir a {name}?"
proxyAccount: "Compte de proxy"
proxyAccountDescription: "Un compte proxy és un compte que actua com a seguidor remot per als usuaris en determinades condicions. Per exemple, quan un usuari afegeix un usuari remot a la llista, l'activitat de l'usuari remot no es lliurarà al servidor si cap usuari local segueix aquest usuari, de manera que el compte proxy el seguirà."
host: "Amfitrió"
@@ -222,7 +224,7 @@ version: "Versió"
metadata: "Metadades"
withNFiles: "{n} fitxer(s)"
monitor: "Monitor"
-jobQueue: "Cua de tasques"
+jobQueue: "Cua de feines"
cpuAndMemory: "CPU i memòria"
network: "Xarxa"
disk: "Disc"
@@ -325,7 +327,7 @@ dark: "Fosc"
lightThemes: "Temes clars"
darkThemes: "Temes foscos"
syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu"
-drive: "Unitat"
+drive: "Disc"
fileName: "Nom del Fitxer"
selectFile: "Selecciona un fitxer"
selectFiles: "Selecciona fitxers"
@@ -340,11 +342,11 @@ deleteFolder: "Elimina la carpeta"
folder: "Carpeta "
addFile: "Afegeix un fitxer"
showFile: "Mostrar fitxer"
-emptyDrive: "La teva unitat és buida"
+emptyDrive: "El teu Disc és buit"
emptyFolder: "La carpeta està buida"
unableToDelete: "No es pot eliminar"
inputNewFileName: "Introduïu el nom de fitxer nou"
-inputNewDescription: "Inserta una nova llegenda"
+inputNewDescription: "Escriu el peu de foto."
inputNewFolderName: "Introduïu el nom de la carpeta nova"
circularReferenceFolder: "La carpeta destinatària és una subcarpeta de la carpeta a la qual la desitges moure"
hasChildFilesOrFolders: "No és possible esborrar aquesta carpeta ja que no és buida"
@@ -537,7 +539,7 @@ mediaListWithOneImageAppearance: "Altura de la llista de fitxers amb una única
limitTo: "Limita a {x}"
noFollowRequests: "No tens sol·licituds de seguiment"
openImageInNewTab: "Obre imatges a una nova pestanya"
-dashboard: "Panell de control"
+dashboard: "Taulell de control"
local: "Local"
remote: "Remot"
total: "Total"
@@ -573,7 +575,7 @@ serverLogs: "Registres del servidor"
deleteAll: "Elimina-ho tot"
showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la línia de temps"
showFixedPostFormInChannel: "Mostrar el formulari d'escriptura al principi de la línia de temps (Canals)"
-withRepliesByDefaultForNewlyFollowed: "Inclou les respostes d'usuaris nous seguits a la línia de temps per defecte."
+withRepliesByDefaultForNewlyFollowed: "Inclou les respostes d'usuaris nous que segueixes a la línia de temps per defecte."
newNoteRecived: "Hi ha publicacions noves"
sounds: "Sons"
sound: "So"
@@ -614,7 +616,7 @@ unsetUserBanner: "Desactiva el bàner "
unsetUserBannerConfirm: "Segur que vols desactivar el bàner?"
deleteAllFiles: "Esborra tots els arxius"
deleteAllFilesConfirm: "Segur que vols esborrar tots els arxius?"
-removeAllFollowing: "Deixa de seguir tots els usuaris seguits"
+removeAllFollowing: "Deixa de seguir tots els usuaris que segueixes"
removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix."
userSuspended: "Aquest usuari ha sigut suspès"
userSilenced: "Aquest usuari està sent silenciat"
@@ -645,7 +647,7 @@ expandTweet: "Expandir post"
themeEditor: "Editor de temes"
description: "Descripció"
describeFile: "Afegir subtitulació"
-enterFileDescription: "Afegeix un títol"
+enterFileDescription: "Escriu un peu de foto"
author: "Autor"
leaveConfirm: "Hi ha canvis sense guardar. Els vols descartar?"
manage: "Administració"
@@ -684,11 +686,15 @@ smtpSecure: "Fes servir SSL/TLS per connexions SMTP"
smtpSecureInfo: "Desactiva això quan facis servir connexions STARTTLS"
testEmail: "Prova l'enviament de correu "
wordMute: "Silenciar paraules "
+wordMuteDescription: "Minimitza les notes que contenen la paraula o frase especificada. Les notes minimitzades poden visualitzar-se fent clic sobre elles."
hardWordMute: "Silenciar paraules fortes"
+showMutedWord: "Mostrar paraules silenciades"
+hardWordMuteDescription: "Oculta les notes que contenen la paraula o frase especificada. A diferència de Silenciar paraula, la nota quedarà completament oculta a la vista."
regexpError: "Error de l'expressió regular "
regexpErrorDescription: "S'ha produït un error a l'expressió regular a la línia {line} de les paraules silenciades {tab}:"
instanceMute: "Silenciar servidor"
userSaysSomething: "{name} n'ha dit alguna cosa"
+userSaysSomethingAbout: "{name} està parlant sobre \"{word}\""
makeActive: "Activar"
display: "Veure"
copy: "Copiar"
@@ -729,7 +735,7 @@ instanceTicker: "Informació de notes de la instància "
waitingFor: "Esperant {x}"
random: "Aleatori "
system: "Sistema"
-switchUi: "Canviar interfície d'usuari "
+switchUi: "Canviar la interfície"
desktop: "Escriptori"
clip: "Retalls"
createNew: "Crear"
@@ -747,7 +753,7 @@ repliesCount: "Nombre de respostes"
renotesCount: "Impulsos fets"
repliedCount: "Nombre de respostes rebudes"
renotedCount: "Impulsos rebuts"
-followingCount: "Nombre de comptes seguits"
+followingCount: "Nombre de comptes que segueixes"
followersCount: "Nombre de seguidors"
sentReactionsCount: "Nombre de reaccions enviades"
receivedReactionsCount: "Nombre de reaccions rebudes"
@@ -779,7 +785,7 @@ thisIsExperimentalFeature: "Aquesta és una característica experimental. La sev
developer: "Programador"
makeExplorable: "Fes que el compte sigui visible a la secció \"Explorar\""
makeExplorableDescription: "Si desactives aquesta opció, el teu compte no sortirà a la secció \"Explorar\""
-showGapBetweenNotesInTimeline: "Mostra una separació entre els articles a la línia de temps"
+showGapBetweenNotesInTimeline: "Notes separades a la línia de temps"
duplicate: "Duplicat"
left: "Esquerra"
center: "Centre"
@@ -910,7 +916,7 @@ off: "Desactivar"
emailRequiredForSignup: "Demanar correu electrònic per registrar-se "
unread: "Sense llegir"
filter: "Filtrar"
-controlPanel: "Panel de control"
+controlPanel: "Taulell de control"
manageAccounts: "Gestionar comptes"
makeReactionsPublic: "Reaccions públiques "
makeReactionsPublicDescription: "Això fa que totes les teves reaccions siguin visibles públicament "
@@ -929,7 +935,7 @@ useDrawerReactionPickerForMobile: "Mostrar el selector de reaccions com un calai
welcomeBackWithName: "Benvingut de nou, {name}"
clickToFinishEmailVerification: "Si us plau, fes clic a [{ok}] per completar la verificació per correu electrònic "
overridedDeviceKind: "Tipus de dispositiu"
-smartphone: "Telèfon intel·ligent"
+smartphone: "Mòbil "
tablet: "Tauleta"
auto: "Automàtic "
themeColor: "Color del tema"
@@ -1010,7 +1016,7 @@ sendPushNotificationReadMessageCaption: "Això pot fer que el teu dispositiu con
windowMaximize: "Maximitzar "
windowMinimize: "Minimitzar"
windowRestore: "Restaurar"
-caption: "Llegenda"
+caption: "Peu de foto"
loggedInAsBot: "Identificat com a bot"
tools: "Eines"
cannotLoad: "No es pot carregar"
@@ -1133,7 +1139,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: "Seguit"
+youFollowing: "Seguint"
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"
@@ -1199,7 +1205,7 @@ showRenotes: "Mostrar impulsos"
edited: "Editat"
notificationRecieveConfig: "Paràmetres de notificacions"
mutualFollow: "Seguidor mutu"
-followingOrFollower: "Seguit o seguidor"
+followingOrFollower: "Seguint o seguidor"
fileAttachedOnly: "Només notes amb adjunts"
showRepliesToOthersInTimeline: "Mostrar les respostes a altres a la línia de temps"
hideRepliesToOthersInTimeline: "Amagar les respostes a altres a la línia de temps"
@@ -1301,6 +1307,8 @@ lockdown: "Bloquejat"
pleaseSelectAccount: "Seleccionar un compte"
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."
_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ó."
@@ -1473,7 +1481,7 @@ _accountMigration:
startMigration: "Migrar"
migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podràs tornar a fer servir aquest compte mai més."
movedAndCannotBeUndone: "Aquest compte ha migrat.\nLes migracions no es poden desfer."
- postMigrationNote: "Aquest compte deixarà de seguir tots els comptes que segueix 24 hores després de germinar la migració.\nEl nombre de seguidors i seguits passarà a ser de zero. Per evitar que els teus seguidors no puguin veure les publicacions marcades com a només seguidors continuaren seguint aquest compte."
+ postMigrationNote: "Aquest compte deixarà de seguir tots els comptes que segueix 24 hores després de terminar la migració.\nEl nombre de seguidors i seguits passarà a ser de zero. Per evitar que els teus seguidors no puguin veure les publicacions marcades com a només seguidors continuaren seguint aquest compte."
movedTo: "Nou compte:"
_achievements:
earnedAt: "Desbloquejat el"
@@ -1832,7 +1840,7 @@ _emailUnavailable:
smtp: "Aquest servidor de correu electrònic no respon"
banned: "No pots registrar-te amb aquesta adreça de correu electrònic "
_ffVisibility:
- public: "Publicar"
+ public: "Públic "
followers: "Visible només per a seguidors "
private: "Privat"
_signup:
@@ -1866,7 +1874,7 @@ _gallery:
unlike: "Ja no m'agrada"
_email:
_follow:
- title: "t'ha seguit"
+ title: "Tens un nou seguidor"
_receiveFollowRequest:
title: "Has rebut una sol·licitud de seguiment"
_plugin:
@@ -1900,7 +1908,7 @@ _registry:
domain: "Domini"
createKey: "Crear una clau"
_aboutMisskey:
- about: "Misskey és un programa de codi obert desenvolupar per syuilo des de 2014"
+ about: "Misskey és un programa de codi obert desenvolupat des del 2014 per syuilo"
contributors: "Col·laboradors principals"
allContributors: "Tots els col·laboradors "
source: "Codi font"
@@ -2219,7 +2227,7 @@ _widgets:
slideshow: "Presentació"
button: "Botó "
onlineUsers: "Usuaris actius"
- jobQueue: "Cua de tasques"
+ jobQueue: "Cua de feines"
serverMetric: "Mètriques del servidor"
aiscript: "Consola AiScript"
aiscriptApp: "Aplicació AiScript"
@@ -2299,7 +2307,7 @@ _exportOrImport:
allNotes: "Totes les publicacions"
favoritedNotes: "Notes preferides"
clips: "Retalls"
- followingList: "Seguint"
+ followingList: "Seguint "
muteList: "Silencia"
blockingList: "Bloqueja"
userLists: "Llistes"
@@ -2438,7 +2446,7 @@ _notification:
_types:
all: "Tots"
note: "Notes noves"
- follow: "Seguint"
+ follow: "Segueix-me"
mention: "Menció"
reply: "Respostes"
renote: "Renotar"
@@ -2454,7 +2462,7 @@ _notification:
test: "Prova la notificació"
app: "Notificacions d'aplicacions"
_actions:
- followBack: "t'ha seguit també"
+ followBack: "També et segueix"
reply: "Respondre"
renote: "Renotar"
_deck:
@@ -2721,6 +2729,66 @@ _contextMenu:
app: "Aplicació "
appWithShift: "Aplicació amb la tecla shift"
native: "Interfície del navegador"
+_gridComponent:
+ _error:
+ requiredValue: "Aquest camp és obligatori"
+ columnTypeNotSupport: "La validació d'expressions regulars només s'admet per columnes de tipus text."
+ patternNotMatch: "Aquest valor no coincideix amb {pattern}"
+ notUnique: "Aquest valor ha de ser únic "
+_roleSelectDialog:
+ notSelected: "No seleccionat"
+_customEmojisManager:
+ _gridCommon:
+ copySelectionRows: "Copiar línies seleccionades "
+ copySelectionRanges: "Copiar selecció "
+ deleteSelectionRows: "Esborrar línies seleccionades"
+ deleteSelectionRanges: "Esborrar files de la selecció "
+ searchSettings: "Configuració del cercador"
+ searchSettingCaption: "Defineix criteris de cerca detallats."
+ searchLimit: "Nombre de pantalles"
+ sortOrder: "Ordenar"
+ registrationLogs: "Registres d'inscripcions "
+ registrationLogsCaption: "Quan s'actualitzin o s'esborrin emojis es mostrarà un registre. Desapareixeran quan s'actualitzin, s'esborrin, visitis una nova pàgina o la recarreguis."
+ alertEmojisRegisterFailedDescription: "No s'ha pogut actualitzar o esborrar l'emoji. Si us plau, dona una ullada al registre per més detalls."
+ _logs:
+ showSuccessLogSwitch: "Mostrar el registre d'èxit "
+ failureLogNothing: "No hi ha registres de fallades."
+ logNothing: "No hi ha registres."
+ _remote:
+ selectionRowDetail: "Detall de la línia seleccionada"
+ importSelectionRows: "Importar les files seleccionades"
+ importSelectionRangesRows: "Importar les files de la selecció "
+ importEmojisButton: "Importar els Emojis marcats"
+ confirmImportEmojisTitle: "Importar Emojis"
+ confirmImportEmojisDescription: "Importar {count} Emojis d'una adreça remota. Tingues cura de les llicències dels Emojis. Vols importar-los?"
+ _local:
+ tabTitleList: "Llistar els Emojis registrats"
+ tabTitleRegister: "Registre d'Emojis"
+ _list:
+ emojisNothing: "No hi ha Emojis registrats"
+ markAsDeleteTargetRows: "Files seleccionades que s'han d'esborrar "
+ markAsDeleteTargetRanges: "Selecció de files per la seva eliminació "
+ alertUpdateEmojisNothingDescription: "No hi ha Emojis actualitzats."
+ alertDeleteEmojisNothingDescription: "No hi ha Emoji per esborrar."
+ confirmMovePage: "Vols canviar de pàgina?"
+ confirmChangeView: "Vols canviar la pantalla?"
+ confirmUpdateEmojisDescription: "Actualitzar {count} Emojis. Vols executar-ho?"
+ confirmDeleteEmojisDescription: "Esborrar {count} Emojis marcats. Vols continuar?"
+ confirmResetDescription: "Es restabliran tots els canvis fets fins ara."
+ confirmMovePageDesciption: "S'han fet canvis als Emojis d'aquesta pàgina. Si continues navegant sense guardar els canvis, es perdran tots els canvis fets en aquesta pàgina."
+ dialogSelectRoleTitle: "Buscar Emojis per rol"
+ _register:
+ uploadSettingTitle: "Actualitza la configuració "
+ uploadSettingDescription: "En aquesta pantalla pots configurar el que s'ha de fer quan es puja un Emoji."
+ directoryToCategoryLabel: "Escriu el nom del directori al camp de \"categoria\""
+ directoryToCategoryCaption: "Quan arrossegues un directori, escriu el nom del directori al camp categoria."
+ emojiInputAreaCaption: "Selecciona els Emojis que vols registrar gent servir un dels mètodes."
+ emojiInputAreaList1: "Arrossega i deixar anar fitxers o directoris dintre del quadrat."
+ emojiInputAreaList2: "Clica l'enllaç per seleccionar un fitxer des del teu ordinador."
+ emojiInputAreaList3: "Clica aquest enllaç per seleccionar del Disc"
+ confirmRegisterEmojisDescription: "Registrar els Emojis de la llista com a nous Emojis personalitzats. Vols continuar? (Per evitar una sobrecàrrega només {count} Emojis es poden registrar d'una sola vegada)"
+ confirmClearEmojisDescription: "Descartar els canvis i esborrar els Emojis de la llista. Vols continuar?"
+ confirmUploadEmojisDescription: "Pujar els {count} fitxers que has arrossegat al disc. Vols continuar?"
_embedCodeGen:
title: "Personalitza el codi per incrustar"
header: "Mostrar la capçalera"
@@ -2744,3 +2812,34 @@ _selfXssPrevention:
_followRequest:
recieved: "Sol·licituds rebudes"
sent: "Sol·licituds enviades"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "No es pot establir connexió amb aquest servidor"
+ description: "És possible que s'hagi desactivat la comunicació amb aquest servidor o que hagi estat bloquejat.\nPosa't en contacte amb l'administrador del servidor."
+ _uriInvalid:
+ title: "L'adreça és incorrecte"
+ description: "Hi ha un problema amb l'adreça introduïda; comprova que no hagis escrit caràcters que no es puguin fer servir."
+ _requestFailed:
+ title: "La sol·licitud a fallat"
+ description: "La comunicació amb aquest servidor a fallat. És possible que l'altre servidor no funcioni. Comprova també que no has posat una adreça no vàlida o inexistent."
+ _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."
+_captcha:
+ verify: "Passar pel CAPTCHA"
+ testSiteKeyMessage: "Pots comprovar una vista prèvia introduïnt valors de prova per la clau del lloc i la clau secreta. Si vols més informació consulteu la següent pàgina."
+ _error:
+ _requestFailed:
+ title: "Ha fallat la sol·licitud del CAPTCHA"
+ text: "Si us plau, torna a intentar-ho d'aquí una estona o comprova els ajustos de nou."
+ _verificationFailed:
+ title: "Ha fallat la validació CAPTCHA"
+ text: "Comprova que els ajustos són els correctes."
+ _unknown:
+ title: "Error CAPTCHA"
+ text: "S'ha produït un error inesperat."
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 504ba1f8c8..20bea96b7f 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -2024,3 +2024,6 @@ _moderationLogTypes:
createInvitation: "Vygenerovat pozvánku"
_reversi:
total: "Celkem"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Nenalezeno"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index d85c930b73..e99a32a364 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -5,6 +5,7 @@ introMisskey: "Willkommen! Misskey ist eine dezentralisierte Open-Source Microbl
poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Misskey</b> betriebenen Dienste."
monthAndDay: "{day}.{month}."
search: "Suchen"
+reset: "Zurücksetzen"
notifications: "Benachrichtigungen"
username: "Benutzername"
password: "Passwort"
@@ -48,6 +49,7 @@ pin: "An dein Profil anheften"
unpin: "Von deinem Profil lösen"
copyContent: "Inhalt kopieren"
copyLink: "Link kopieren"
+copyRemoteLink: "Renote-Link kopieren"
copyLinkRenote: "Renote-Link kopieren"
delete: "Löschen"
deleteAndEdit: "Löschen und Bearbeiten"
@@ -185,7 +187,9 @@ addAccount: "Benutzerkonto hinzufügen"
reloadAccountsList: "Benutzerkontoliste aktualisieren"
loginFailed: "Anmeldung fehlgeschlagen"
showOnRemote: "Auf Ursprungsinstanz ansehen"
+continueOnRemote: "Weiter auf Remote-Server"
chooseServerOnMisskeyHub: "Wähle einen Server aus dem Misskey Hub"
+specifyServerHost: "Server-Host auswählen"
inputHostName: "Gib die Domain an"
general: "Allgemein"
wallpaper: "Hintergrund"
@@ -237,6 +241,8 @@ silencedInstances: "Stummgeschaltete Instanzen"
silencedInstancesDescription: "Gib die Hostnamen der Instanzen, welche stummgeschaltet werden sollen, durch Zeilenumbrüche getrennt an. Alle Konten dieser Instanzen werden als stummgeschaltet behandelt, können nur noch Follow-Anfragen stellen und wenn nicht gefolgt keine lokalen Konten erwähnen. Blockierte Instanzen sind davon nicht betroffen."
mediaSilencedInstances: "Medien-stummgeschaltete Server"
mediaSilencedInstancesDescription: "Gib pro Zeile die Hostnamen der Server ein, dessen Medien du stummschalten möchtest. Alle Benutzerkonten der aufgeführten Server werden als sensibel behandelt und können keine benutzerdefinierten Emojis verwenden. Gesperrte Server sind davon nicht betroffen."
+federationAllowedHosts: "Föderierte Instanzen"
+federationAllowedHostsDescription: "Trage die Hostnamen ein mit den du eine Föderation eingehen möchtest. Trenne mit Zeilenumbruch."
muteAndBlock: "Stummschaltungen und Blockierungen"
mutedUsers: "Stummgeschaltete Benutzer"
blockedUsers: "Blockierte Benutzer"
@@ -449,6 +455,7 @@ totpDescription: "Logge dich via Authentifizierungs-App mit Einmalpasswort ein"
moderator: "Moderator"
moderation: "Moderation"
moderationNote: "Moderationsnotiz"
+moderationNoteDescription: "Trage hier Notizen ein. Diese sind nur für die Moderatoren sichtbar."
addModerationNote: "Moderationsnotiz hinzufügen"
moderationLogs: "Moderationsprotokolle"
nUsersMentioned: "Von {n} Benutzern erwähnt"
@@ -488,6 +495,7 @@ noMessagesYet: "Noch keine Nachrichten vorhanden"
newMessageExists: "Du hast eine neue Nachricht"
onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden"
signinRequired: "Bitte registriere oder melde dich an, um fortzufahren"
+signinOrContinueOnRemote: "Um fortzufahren, gehe zu deiner Instanz oder registriere bzw. melde dich an dieser Instanz an. "
invitations: "Einladungen"
invitationCode: "Einladungscode"
checking: "Wird überprüft …"
@@ -511,6 +519,7 @@ emojiStyle: "Emoji-Stil"
native: "Nativ"
menuStyle: "Menü Stil"
style: "Stil"
+drawer: "App-Übersicht"
popup: "Pop-up"
showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen"
showReactionsCount: "Zeige die Anzahl der Reaktionen auf Notizen an"
@@ -579,6 +588,7 @@ masterVolume: "Gesamtlautstärke"
notUseSound: "Gebe kein Ton aus"
useSoundOnlyWhenActive: "Gebe nur Ton aus, wenn Misskey aktiv ist"
details: "Details"
+renoteDetails: "Renote Details"
chooseEmoji: "Emoji auswählen"
unableToProcess: "Der Vorgang konnte nicht abgeschlossen werden"
recentUsed: "Vor kurzem verwendet"
@@ -595,6 +605,7 @@ descendingOrder: "Absteigende Reihenfolge"
scratchpad: "Testumgebung"
scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen."
uiInspector: "UI-Inspektor"
+uiInspectorDescription: "Die Liste der UI-Komponenten-Server können im Zwischenspeicher angesehen werden. Die UI-Komponente wird von der Funktion Ui:C: generiert."
output: "Ausgabe"
script: "Skript"
disablePagesScript: "AiScript auf Seiten deaktivieren"
@@ -675,11 +686,15 @@ smtpSecure: "Für SMTP-Verbindungen implizit SSL/TLS verwenden"
smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest."
testEmail: "Emailversand testen"
wordMute: "Wortstummschaltung"
-hardWordMute: "Harte Wort-Stummschaltung"
+wordMuteDescription: "Minimiert Notizen, die das angegebene Wort oder den angegebenen Ausdruck enthalten. Minimierte Notizen können angezeigt werden, indem du auf sie klickst."
+hardWordMute: "Harte Wortstummschaltung"
+showMutedWord: "Stummgeschaltete Wörter anzeigen"
+hardWordMuteDescription: "Blendet Notizen aus, die das angegebene Wort oder die angegebene Phrase enthalten. Im Gegensatz zur Wortstummschaltung wird die Notiz vollständig ausgeblendet."
regexpError: "Fehler in einem regulären Ausdruck"
regexpErrorDescription: "Im regulären Ausdruck deiner in Zeile {line} von {tab}en Wortstummschaltungen ist ein Fehler aufgetreten:"
instanceMute: "Instanzstummschaltungen"
userSaysSomething: "{name} hat etwas gesagt"
+userSaysSomethingAbout: "{name} sagt etwas über '{word}'"
makeActive: "Aktivieren"
display: "Anzeigeart"
copy: "Kopieren"
@@ -848,6 +863,7 @@ administration: "Verwaltung"
accounts: "Benutzerkonten"
switch: "Wechseln"
noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert."
+noInquiryUrlWarning: "Keine gültige URL."
noBotProtectionWarning: "Schutz vor Bots ist nicht konfiguriert."
configure: "Konfigurieren"
postToGallery: "Neuen Galeriebeitrag erstellen"
@@ -1080,12 +1096,15 @@ retryAllQueuesConfirmTitle: "Wirklich erneut versuchen?"
retryAllQueuesConfirmText: "Dies wird zu einer temporären Erhöhung der Serverlast führen."
enableChartsForRemoteUser: "Diagramme für Nutzer fremder Instanzen erstellen"
enableChartsForFederatedInstances: "Diagramme für fremde Instanzen erstellen"
+enableStatsForFederatedInstances: "Abruf von Informationen über förderierte Server"
showClipButtonInNoteFooter: "\"Clip\" zum Notizmenu hinzufügen"
reactionsDisplaySize: "Reaktionsanzeigegröße"
limitWidthOfReaction: "Begrenze die Breite der Reaktion und zeige sie verkleinert an"
noteIdOrUrl: "Notiz-ID oder URL"
video: "Video"
videos: "Videos"
+audio: "Audio"
+audioFiles: "Audio"
dataSaver: "Datensparmodus"
accountMigration: "Kontomigration"
accountMoved: "Dieser Benutzer ist zu einem neuen Konto migriert:"
@@ -1125,6 +1144,9 @@ preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive
preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
options: "Optionen"
specifyUser: "Spezifischer Benutzer"
+lookupConfirm: "Zustimmen?"
+openTagPageConfirm: "Hashtag Seite wirklich öffnen?"
+specifyHost: "Host"
failedToPreviewUrl: "Vorschau nicht anzeigbar"
update: "Aktualisieren"
rolesThatCanBeUsedThisEmojiAsReaction: "Rollen, die dieses Emoji als Reaktion verwenden können"
@@ -1183,6 +1205,7 @@ showRenotes: "Renotes anzeigen"
edited: "Bearbeitet"
notificationRecieveConfig: "Benachrichtigungseinstellungen"
mutualFollow: "Gegenseitig gefolgt"
+followingOrFollower: "Follow oder Follower"
fileAttachedOnly: "Nur Notizen mit Dateien"
showRepliesToOthersInTimeline: "Antworten in Chronik anzeigen"
hideRepliesToOthersInTimeline: "Antworten nicht in Chronik anzeigen"
@@ -1194,7 +1217,10 @@ externalServices: "Externe Dienste"
sourceCode: "Quellcode"
sourceCodeIsNotYetProvided: "Der Quellcode ist noch nicht verfügbar. Kontaktiere den Administrator, um das Problem zu lösen."
repositoryUrl: "Repository URL"
+repositoryUrlDescription: "Solltest du Misskey so wie es ist verwenden (im unveränderten Quellcode), gebe Folgendes an:\nhttps://github.com/misskey-dev/misskey"
repositoryUrlOrTarballRequired: "Wenn du kein Repository veröffentlicht hast, musst du stattdessen einen Tarball bereitstellen. Siehe .config/example.yml für weitere Informationen."
+feedback: "Feedback"
+feedbackUrl: "Feedback-Website"
impressum: "Impressum"
impressumUrl: "Impressums-URL"
impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung, ist die Angabe von Betreiberinformationen (ein Impressum) bei kommerziellem Betrieb zwingend."
@@ -1204,6 +1230,7 @@ tosAndPrivacyPolicy: "Nutzungsbedingungen und Datenschutzerklärung"
avatarDecorations: "Profilbilddekoration"
attach: "Anbringen"
detach: "Entfernen"
+detachAll: "Alles Entfernen"
angle: "Winkel"
flip: "Umdrehen"
showAvatarDecorations: "Profilbilddekoration anzeigen"
@@ -1216,18 +1243,27 @@ signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen.
cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden."
doReaction: "Reagieren"
code: "Code"
+reloadRequiredToApplySettings: "Eine Aktualisierung ist erforderlich, um die Einstellungen zu übernehmen."
remainingN: "Verbleibend: {n}"
overwriteContentConfirm: "Bist du sicher, dass du den aktuellen Inhalt überschreiben willst?"
seasonalScreenEffect: "Saisonaler Bildschirmeffekt"
decorate: "Dekorieren"
addMfmFunction: "MFM hinzufügen"
enableQuickAddMfmFunction: "Erweiterte MFM-Auswahl anzeigen"
+bubbleGame: "Bubble Game"
sfx: "Soundeffekte"
soundWillBePlayed: "Es wird Ton wiedergegeben"
showReplay: "Wiederholung anzeigen"
+replay: "Aufzeichnen"
+replaying: "Aufzeichnung"
+endReplay: "Aufzeichnung verlassen"
+copyReplayData: "Aufzeichnung kopieren"
ranking: "Rangliste"
lastNDays: "Letzten {n} Tage"
backToTitle: "Zurück zum Startbildschirm"
+hemisphere: "Hemisphäre"
+withSensitive: "Zeige \"sensitive Inhalte\" an"
+userSaysSomethingSensitive: "{name} sagt etwas mit sensiblem Inhalt."
enableHorizontalSwipe: "Wischen, um zwischen Tabs zu wechseln"
loading: "Laden"
surrender: "Abbrechen"
@@ -1240,6 +1276,8 @@ useNativeUIForVideoAudioPlayer: "Browser-Benutzeroberfläche für die Video- und
keepOriginalFilename: "Ursprünglichen Dateinamen beibehalten"
keepOriginalFilenameDescription: "Wenn diese Einstellung deaktiviert ist, wird der Dateiname beim Hochladen automatisch durch eine zufällige Zeichenfolge ersetzt."
noDescription: "Keine Beschreibung vorhanden"
+alwaysConfirmFollow: "Folgen immer bestätigen"
+inquiry: "Kontakt"
tryAgain: "Bitte später erneut versuchen"
confirmWhenRevealingSensitiveMedia: "Das Anzeigen von sensiblen Medien bestätigen"
sensitiveMediaRevealConfirm: "Es könnte sich um sensible Medien handeln. Möchtest du sie anzeigen?"
@@ -1249,29 +1287,41 @@ fromX: "Von {x}"
genEmbedCode: "Einbettungscode generieren"
noteOfThisUser: "Notizen dieses Benutzers"
clipNoteLimitExceeded: "Zu diesem Clip können keine weiteren Notizen hinzugefügt werden."
+performance: "Leistung"
+modified: "Bearbeitet"
discard: "Verwerfen"
thereAreNChanges: "Es gibt {n} Änderung(en)"
signinWithPasskey: "Mit Passkey anmelden"
+unknownWebAuthnKey: "Unbekannter Passkey"
passkeyVerificationFailed: "Die Passkey-Verifizierung ist fehlgeschlagen."
passkeyVerificationSucceededButPasswordlessLoginDisabled: "Die Verifizierung des Passkeys war erfolgreich, aber die passwortlose Anmeldung ist deaktiviert."
messageToFollower: "Nachricht an die Follower"
+target: "Speicherort"
testCaptchaWarning: "Diese Funktion ist für CAPTCHA-Testzwecke gedacht.\n<strong>Nicht in einer Produktivumgebung verwenden.</strong>"
prohibitedWordsForNameOfUser: "Verbotene Begriffe für Benutzernamen"
prohibitedWordsForNameOfUserDescription: "Wenn eine Zeichenfolge aus dieser Liste im Namen eines Benutzers enthalten ist, wird der Benutzername abgelehnt. Benutzer mit Moderatorenrechten sind von dieser Einschränkung nicht betroffen."
yourNameContainsProhibitedWords: "Dein Name enthält einen verbotenen Begriff"
yourNameContainsProhibitedWordsDescription: "Der Name enthält eine verbotene Zeichenfolge. Wende dich an deinen Serveradministrator, wenn du diesen Namen verwenden möchtest."
+thisContentsAreMarkedAsSigninRequiredByAuthor: "Logge dich ein, um weitere Inhalte von diesem Nutzer zu sehen."
+lockdown: "Sperren"
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."
_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."
+ requireSigninToViewContentsDescription2: "Der Inhalt wird nicht in URL-Vorschauen (OGP), eingebettet in Webseiten oder auf Servern, die keine Zitate unterstützen, angezeigt."
requireSigninToViewContentsDescription3: "Diese Einschränkungen gelten möglicherweise nicht für föderierte Inhalte von anderen Servern."
makeNotesFollowersOnlyBefore: "Macht frühere Notizen nur für Follower sichtbar"
makeNotesHiddenBefore: "Frühere Notizen privat machen"
+ makeNotesHiddenBeforeDescription: ""
mayNotEffectForFederatedNotes: "Dies hat möglicherweise keine Auswirkungen auf Notizen, die an andere Server föderiert werden."
+ notesOlderThanSpecifiedDateAndTime: "Notizen vor einem bestimmtem Datum und Uhrzeit"
_abuseUserReport:
forward: "Weiterleiten"
forwardDescription: "Leite die Meldung an einen entfernten Server als anonymes Systemkonto weiter."
+ resolve: "lösen"
accept: "Akzeptieren"
reject: "Ablehnen"
_delivery:
@@ -1381,6 +1431,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. "
+ 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:
moveFrom: "Von einem anderen Konto zu diesem migrieren"
@@ -1842,6 +1893,7 @@ _channel:
notesCount: "{n} Notizen"
nameAndDescription: "Name und Beschreibung"
nameOnly: "Nur Name"
+ allowRenoteToExternal: "Renotes und Zitierungen außerhalb des Kanals erlauben"
_menuDisplay:
sideFull: "Seitlich"
sideIcon: "Seitlich (Icons)"
@@ -1930,6 +1982,7 @@ _sfx:
note: "Notizen"
noteMy: "Meine Notizen"
notification: "Benachrichtigungen"
+ reaction: "Auswählen einer Reaktion"
_soundSettings:
driveFile: "Audiodatei aus dem Drive verwenden"
driveFileWarn: "Wähle eine Audiodatei aus dem Drive"
@@ -2028,12 +2081,22 @@ _permissions:
"read:admin:server-info": "Serverinformationen anzeigen"
"read:admin:show-moderation-log": "Moderationsprotokoll einsehen"
"read:admin:show-user": "Private Benutzerinformationen einsehen"
+ "write:admin:roles": "Rollen verwalten"
+ "read:admin:roles": "Rollen anzeigen"
+ "write:admin:relays": "Relays verwalten"
+ "read:admin:relays": "Relays anzeigen"
"write:admin:invite-codes": "Einladungscodes verwalten"
"read:admin:invite-codes": "Einladungscodes anzeigen"
"write:admin:announcements": "Ankündigungen verwalten"
"read:admin:announcements": "Ankündigungen einsehen"
"write:admin:avatar-decorations": "Kann Avatar-Dekorationen verwalten"
"read:admin:avatar-decorations": "Avatar-Dekorationen ansehen"
+ "write:admin:account": "Benutzerkonten verwalten"
+ "read:admin:account": "Benutzerkonten anzeigen"
+ "write:admin:emoji": "Emojis verwalten"
+ "read:admin:emoji": "Emojis anzeigen"
+ "write:admin:queue": "Job-Warteschlange verwalten"
+ "read:admin:queue": "Job-Warteschlange anzeigen"
_auth:
shareAccessTitle: "Verteilung von App-Berechtigungen"
shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?"
@@ -2151,6 +2214,8 @@ _profile:
changeAvatar: "Profilbild ändern"
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."
+ followedMessageDescription: "Du kannst eine kurze Nachricht festlegen, die dem Empfänger angezeigt wird, wenn er dir folgt."
_exportOrImport:
allNotes: "Alle Notizen"
favoritedNotes: "Als Favorit markierte Notizen"
@@ -2283,6 +2348,7 @@ _notification:
reactedBySomeUsers: "{n} Benutzer haben eine Reaktion geschickt"
renotedBySomeUsers: "Renote von {n} Benutzern"
followedBySomeUsers: "Von {n} Benutzern gefolgt"
+ login: "Neue Anmeldung erfolgt"
_types:
all: "Alle"
note: "Neue Notizen"
@@ -2295,6 +2361,7 @@ _notification:
pollEnded: "Ende von Umfragen"
receiveFollowRequest: "Erhaltene Follow-Anfragen"
followRequestAccepted: "Akzeptierte Follow-Anfragen"
+ roleAssigned: "Rolle zugewiesen"
achievementEarned: "Errungenschaft freigeschaltet"
login: "Anmelden"
app: "Benachrichtigungen von Apps"
@@ -2346,6 +2413,7 @@ _webhookSettings:
createWebhook: "Webhook erstellen"
name: "Name"
secret: "Secret"
+ trigger: "Auslöser"
active: "Aktiviert"
_events:
follow: "Wenn du jemandem folgst"
@@ -2357,8 +2425,10 @@ _webhookSettings:
mention: "Wenn du erwähnt wirst"
_abuseReport:
_notificationRecipient:
+ createRecipient: "Meldungsempfänger hinzufügen"
_recipientType:
mail: "Email"
+ keywords: "Schlüsselwort"
_moderationLogTypes:
createRole: "Rolle erstellt"
deleteRole: "Rolle gelöscht"
@@ -2393,6 +2463,13 @@ _moderationLogTypes:
createAvatarDecoration: "Profilbilddekoration erstellt"
updateAvatarDecoration: "Profilbilddekoration aktualisiert"
deleteAvatarDecoration: "Profilbilddekoration gelöscht"
+ unsetUserAvatar: "Profilbild zurückgesetzt"
+ unsetUserBanner: "Profilbanner zurückgesetzt"
+ createSystemWebhook: "System-Webhook erstellt"
+ updateSystemWebhook: "System-Webhook aktualisiert"
+ deleteSystemWebhook: "System-Webhook gelöscht"
+ deletePage: "Seite gelöscht"
+ deleteGalleryPost: "Galeriebeitrag gelöscht"
_fileViewer:
title: "Dateiinformationen"
type: "Dateityp"
@@ -2442,6 +2519,10 @@ _externalResourceInstaller:
_themeInstallFailed:
title: "Das Farbschema konnte nicht installiert werden"
description: "Während der Installation des Farbschemas ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden."
+_hemisphere:
+ N: "Nördliche Erdhalbkugel"
+ S: "Südliche Erdhalbkugel"
+ caption: "Wird in einigen Client-Einstellungen zur Bestimmung der Jahreszeit verwendet."
_reversi:
blackOrWhite: "Schwarz/Weiß"
rules: "Regeln"
@@ -2449,17 +2530,26 @@ _reversi:
white: "Weiß"
total: "Gesamt"
_offlineScreen:
+ title: "Offline - keine Verbindung zum Server möglich"
header: "Verbindung zum Server nicht möglich"
_urlPreviewSetting:
title: "Einstellungen der URL-Vorschau"
enable: "URL-Vorschau aktivieren"
timeout: "Zeitüberschreitung beim Abrufen der Vorschau (ms)"
+ timeoutDescription: "Übersteigt die für die Vorschau benötigte Zeit diesen Wert, wird keine Vorschau generiert."
maximumContentLength: "Maximale Content-Length (Bytes)"
+ maximumContentLengthDescription: "Wenn die Content-Length diesen Wert überschreitet, wird keine Vorschau erzeugt."
+ requireContentLength: "Vorschau nur generieren, wenn Content-Length verfügbar ist"
+ requireContentLengthDescription: "Wenn der Server keine Content-Length zurückgibt, wird keine Vorschau erzeugt."
+ userAgent: "User-Agent"
_mediaControls:
playbackRate: "Wiedergabegeschwindigkeit"
_contextMenu:
title: "Kontextmenü"
app: "Anwendung"
+_gridComponent:
+ _error:
+ requiredValue: "Dieser Wert ist ein Pflichtfeld"
_embedCodeGen:
title: "Einbettungscode anpassen"
header: "Kopfzeile anzeigen"
@@ -2476,3 +2566,13 @@ _selfXssPrevention:
title: "„Füge in diesen Bereich etwas ein“ ist eine Betrugsmasche."
description1: "Wenn du hier etwas einfügst, könnte ein böswilliger Benutzer dein Konto übernehmen oder deine persönlichen Daten stehlen."
description3: "Weitere Informationen findest du hier. {link}"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "Kommunikation mit diesem Server nicht möglich"
+ description: "Möglicherweise wurde die Kommunikation mit diesem Server deaktiviert oder dieser Server ist blockiert.\nWende dich bitte an den Serveradministrator."
+ _uriInvalid:
+ title: "URI ist fehlerhaft"
+ description: "Es gibt ein Problem mit der von dir eingegebenen URI. Bitte prüfe, ob du Zeichen eingegeben hast, die in der URI nicht verwendet werden können."
+ _noSuchObject:
+ title: "Nicht gefunden"
+ description: "Die angeforderte Ressource konnte nicht gefunden werden, bitte überprüfe die URI erneut."
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 2f2d10c6b5..d4803310c6 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -5,6 +5,7 @@ introMisskey: "Welcome! Misskey is an open source, decentralized microblogging s
poweredByMisskeyDescription: "{name} is one of the services powered by the open source platform <b>Misskey</b> (referred to as a \"Misskey instance\")."
monthAndDay: "{month}/{day}"
search: "Search"
+reset: "Reset"
notifications: "Notifications"
username: "Username"
password: "Password"
@@ -48,6 +49,7 @@ pin: "Pin to profile"
unpin: "Unpin from profile"
copyContent: "Copy contents"
copyLink: "Copy link"
+copyRemoteLink: "Copy remote link"
copyLinkRenote: "Copy renote link"
delete: "Delete"
deleteAndEdit: "Delete and edit"
@@ -684,11 +686,15 @@ smtpSecure: "Use implicit SSL/TLS for SMTP connections"
smtpSecureInfo: "Turn this off when using STARTTLS"
testEmail: "Test email delivery"
wordMute: "Word mute"
+wordMuteDescription: "Minimize notes that contain the specified word or phrase. Minimized notes can be displayed by clicking on them."
hardWordMute: "Hard word mute"
+showMutedWord: "Show muted words"
+hardWordMuteDescription: "Hide notes that contain the specified word or phrase. Unlike word mute, the note will be completely hidden from view."
regexpError: "Regular Expression error"
regexpErrorDescription: "An error occurred in the regular expression on line {line} of your {tab} word mutes:"
instanceMute: "Instance Mutes"
userSaysSomething: "{name} said something"
+userSaysSomethingAbout: "{name} said something about \"{word}\""
makeActive: "Activate"
display: "Display"
copy: "Copy"
@@ -1300,6 +1306,9 @@ thisContentsAreMarkedAsSigninRequiredByAuthor: "Set by the author to require log
lockdown: "Lockdown"
pleaseSelectAccount: "Select an account"
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."
_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."
@@ -1456,9 +1465,9 @@ _serverSettings:
reactionsBufferingDescription: "When enabled, performance during reaction creation will be greatly improved, reducing the load on the database. However, Redis memory usage will increase."
inquiryUrl: "Inquiry URL"
inquiryUrlDescription: "Specify a URL for the inquiry form to the server maintainer or a web page for the contact information."
+ openRegistration: "Make the account creation open"
+ openRegistrationWarning: "Opening registration carries risks. It is recommended to only enable it if you have a system in place to continuously monitor the server and respond immediately in case of any issues."
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "If no moderator activity is detected for a while, this setting will be automatically turned off to prevent spam."
- openRegistration: "Open account creation"
- openRegistrationWarning: "Opening up registration is risky, so we recommend turning it on only if you are able to constantly monitor your server and respond immediately if any problems arise."
_accountMigration:
moveFrom: "Migrate another account to this one"
moveFromSub: "Create alias to another account"
@@ -2720,6 +2729,65 @@ _contextMenu:
app: "Application"
appWithShift: "Application with shift key"
native: "Native"
+_gridComponent:
+ _error:
+ requiredValue: "This value is required"
+ columnTypeNotSupport: "Validation with regular expression is supported only for type:text columns."
+ patternNotMatch: "This value doesn't match the pattern in {pattern}"
+ notUnique: "This value must be unique"
+_roleSelectDialog:
+ notSelected: "Not selected"
+_customEmojisManager:
+ _gridCommon:
+ copySelectionRows: "Copy selected rows"
+ copySelectionRanges: "Copy selected ranges"
+ deleteSelectionRows: "Delete selected rows"
+ deleteSelectionRanges: "Delete rows in the selection"
+ searchSettings: "Search settings"
+ searchSettingCaption: "Set detailed search criteria."
+ searchLimit: ""
+ sortOrder: "Sort order"
+ registrationLogs: "Registration log"
+ registrationLogsCaption: "Logs will be displayed when updating or deleting Emojis. They will disappear after updating or deleting them, moving to a new page, or reloading."
+ alertEmojisRegisterFailedDescription: "Failed to update or delete Emojis. Please check the registration log for details."
+ _logs:
+ showSuccessLogSwitch: "Show success log"
+ failureLogNothing: "There is no failure log."
+ logNothing: "There is no log."
+ _remote:
+ selectionRowDetail: "Selected row's detail"
+ importSelectionRows: "Import selected rows"
+ importSelectionRangesRows: "Import rows in the selection"
+ importEmojisButton: "Import checked Emojis"
+ confirmImportEmojisTitle: "Import Emojis"
+ confirmImportEmojisDescription: "Import {count} Emoji(s) received from the remote server. Please pay close attention to the license of the Emoji. Are you sure to continue?"
+ _local:
+ tabTitleList: "List of registered Emojis"
+ tabTitleRegister: "Emoji registration"
+ _list:
+ emojisNothing: "There are no registered Emojis."
+ markAsDeleteTargetRows: "Mark selected rows as a target to delete"
+ markAsDeleteTargetRanges: "Mark rows in the selection as a target to delete"
+ alertUpdateEmojisNothingDescription: "There are no updated Emojis."
+ alertDeleteEmojisNothingDescription: "There are no Emojis to be deleted."
+ confirmMovePage: ""
+ confirmChangeView: ""
+ confirmUpdateEmojisDescription: "Update {count} Emoji(s). Are you sure to continue?"
+ confirmDeleteEmojisDescription: "Delete checked {count} Emoji(s). Are you sure to continue?"
+ confirmResetDescription: ""
+ dialogSelectRoleTitle: "Search by roll set in Emojis"
+ _register:
+ uploadSettingTitle: "Upload settings"
+ uploadSettingDescription: "On this screen, you can configure the behavior when uploading Emojis."
+ directoryToCategoryLabel: "Enter the directory name in the \"category\" field"
+ directoryToCategoryCaption: "When you drag and drop a directory, enter the directory name in the \"category\" field."
+ emojiInputAreaCaption: "Select the Emojis you wish to register using one of the methods."
+ emojiInputAreaList1: "Drag and drop image files or a directory into this frame"
+ emojiInputAreaList2: "Click this link to select from your computer"
+ emojiInputAreaList3: "Click this link to select from the drive"
+ confirmRegisterEmojisDescription: "Register the Emojis from the list as new custom Emojis. Are you sure to continue? (To avoid overload, only {count} Emoji(s) can be registered in a single operation)"
+ confirmClearEmojisDescription: "Discard the edits and clear the Emojis from the list. Are you sure to continue?"
+ confirmUploadEmojisDescription: "Upload the dragged and dropped {count} file(s) to the drive. Are you sure to continue?"
_embedCodeGen:
title: "Customize embed code"
header: "Show header"
@@ -2743,4 +2811,34 @@ _selfXssPrevention:
_followRequest:
recieved: "Received"
sent: "Sent"
-acknowledgeNotesAndEnable: "Be sure you have understood the warnings before turning this on"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "Unable to communicate with this server"
+ description: "Communication with this server may have been disabled or this server may be blocked.\nPlease contact the server administrator."
+ _uriInvalid:
+ title: "URI is invalid"
+ description: "There is a problem with the URI you entered. Please check if you entered characters that cannot be used in the URI."
+ _requestFailed:
+ title: "Request failed"
+ description: "Communication with this server failed. The server may be down. Also, please make sure that you have not entered an invalid or nonexistent URI."
+ _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."
+_captcha:
+ verify: "Please verify the CAPTCHA"
+ testSiteKeyMessage: "You can check the preview by entering the test values for the site and secret keys.\nPlease see the following page for details."
+ _error:
+ _requestFailed:
+ title: "Failed to request CAPTCHA"
+ text: "Please run it after a while or check the settings again."
+ _verificationFailed:
+ title: "Failed to validate CAPTCHA"
+ text: "Please check again if the settings are correct."
+ _unknown:
+ title: "CAPTCHA error"
+ text: "An unexpected error occurred."
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index a4ec114b15..28cfba1c20 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -5,11 +5,13 @@ introMisskey: "¡Bienvenido/a! Misskey es un servicio de microblogging descentra
poweredByMisskeyDescription: "{name} es uno de los servicios (también llamado instancia) que usa la plataforma de código abierto <b>Misskey</b>"
monthAndDay: "{day}/{month}"
search: "Buscar"
+reset: "Reiniciar"
notifications: "Notificaciones"
username: "Nombre de usuario"
password: "Contraseña"
initialPasswordForSetup: "Contraseña para iniciar la inicialización"
initialPasswordIsIncorrect: "La contraseña para iniciar la configuración inicial es incorrecta."
+initialPasswordForSetupDescription: "Si ha instalado Misskey usted mismo, utilice la contraseña introducida en el archivo de configuración.\nSi utiliza un servicio de alojamiento de Misskey o similar, utilice la contraseña proporcionada.\nSi no ha establecido una contraseña, déjela en blanco para continuar."
forgotPassword: "Olvidé mi contraseña"
fetchingAsApObject: "Buscando en el fediverso"
ok: "OK"
@@ -47,6 +49,7 @@ pin: "Fijar al perfil"
unpin: "Desfijar"
copyContent: "Copiar contenido"
copyLink: "Copiar enlace"
+copyRemoteLink: "Copiar enlace remoto"
copyLinkRenote: "Copiar enlace de renota"
delete: "Borrar"
deleteAndEdit: "Borrar y editar"
@@ -198,6 +201,7 @@ followConfirm: "¿Desea seguir a {name}?"
proxyAccount: "Cuenta proxy"
proxyAccountDescription: "Una cuenta proxy es una cuenta que actúa como un seguidor remoto de un usuario bajo ciertas condiciones. Por ejemplo, cuando un usuario añade un usuario remoto a una lista, si ningún usuario local sigue al usuario agregado a la lista, la instancia no puede obtener su actividad. Así que la cuenta proxy sigue al usuario añadido a la lista"
host: "Host"
+selectSelf: "Elígete a ti mismo"
selectUser: "Elegir usuario"
recipient: "Recipiente"
annotation: "Anotación"
@@ -213,6 +217,7 @@ perDay: "por día"
stopActivityDelivery: "Dejar de enviar actividades"
blockThisInstance: "Bloquear instancia"
silenceThisInstance: "Silenciar esta instancia"
+mediaSilenceThisInstance: "Silencia la Multimedia(Imágenes,videos...) para este servidor"
operations: "Operaciones"
software: "Software"
version: "Versión"
@@ -234,6 +239,10 @@ blockedInstances: "Instancias bloqueadas"
blockedInstancesDescription: "Seleccione los hosts de las instancias que desea bloquear, separadas por una linea nueva. Las instancias bloqueadas no podrán comunicarse con esta instancia."
silencedInstances: "Instancias silenciadas"
silencedInstancesDescription: "Listar los hostname de las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas."
+mediaSilencedInstances: "Servidores silenciados (Multimedia)"
+mediaSilencedInstancesDescription: "Listar las instancias que quieres silenciar. Todas las cuentas de las instancias listadas serán tratadas como silenciadas, solo podrán hacer peticiones de seguimiento, y no podrán mencionar cuentas locales si no las siguen. Esto no afecta a las instancias bloqueadas."
+federationAllowedHosts: "Servidores federados"
+federationAllowedHostsDescription: "Establezca los nombres de los servidores que pueden federarse, separados por una nueva línea."
muteAndBlock: "Silenciar y bloquear"
mutedUsers: "Usuarios silenciados"
blockedUsers: "Usuarios bloqueados"
@@ -324,6 +333,7 @@ selectFile: "Elegir archivo"
selectFiles: "Elegir archivos"
selectFolder: "Seleccione una carpeta"
selectFolders: "Seleccione carpetas"
+fileNotSelected: "Archivo no seleccionado."
renameFile: "Renombrar archivo"
folderName: "Nombre de la carpeta"
createFolder: "Crear carpeta"
@@ -331,6 +341,7 @@ renameFolder: "Renombrar carpeta"
deleteFolder: "Borrar carpeta"
folder: "Carpeta"
addFile: "Agregar archivo"
+showFile: "Examinar archivos"
emptyDrive: "El drive está vacío"
emptyFolder: "La carpeta está vacía"
unableToDelete: "No se puede borrar"
@@ -444,6 +455,7 @@ totpDescription: "Ingresa una contaseña de un sólo uso usando la aplicación a
moderator: "Moderador"
moderation: "Moderación"
moderationNote: "Nota de moderación"
+moderationNoteDescription: "Puedes rellenar notas que solo se comparten entre moderadores."
addModerationNote: "Añadir nota de moderación"
moderationLogs: "Log de moderación"
nUsersMentioned: "{n} usuarios mencionados"
@@ -478,10 +490,12 @@ retype: "Ingrese de nuevo"
noteOf: "Notas de {user}"
quoteAttached: "Cita añadida"
quoteQuestion: "¿Quiere añadir una cita?"
+attachAsFileQuestion: "El texto del portapapeles es demasiado grande ¿Desea adjuntarlo como archivo de texto?"
noMessagesYet: "Aún no hay chat"
newMessageExists: "Tienes un mensaje nuevo"
onlyOneFileCanBeAttached: "Solo se puede añadir un archivo al mensaje"
signinRequired: "Iniciar sesión"
+signinOrContinueOnRemote: "Para continuar, tendrá que ir a su servidor o registrarse e iniciar sesión en este servidor"
invitations: "Invitar"
invitationCode: "Código de invitación"
checking: "Comprobando"
@@ -505,6 +519,8 @@ emojiStyle: "Estilo de emoji"
native: "Nativo"
menuStyle: "Diseño del menú"
style: "Diseño"
+drawer: "Cajón de Aplicaciones"
+popup: "Ventana emergente"
showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor"
showReactionsCount: "Mostrar el número de reacciones en las notas"
noHistory: "No hay datos en el historial"
@@ -572,6 +588,7 @@ masterVolume: "Volumen principal"
notUseSound: "Sin sonido"
useSoundOnlyWhenActive: "Sonar solo cuando Misskey esté activo"
details: "Detalles"
+renoteDetails: "Detalles(Renota)"
chooseEmoji: "Elije un emoji"
unableToProcess: "La operación no se puede llevar a cabo"
recentUsed: "Usado recientemente"
@@ -587,6 +604,7 @@ ascendingOrder: "Ascendente"
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"
output: "Salida"
script: "Script"
disablePagesScript: "Deshabilitar AiScript en Páginas"
@@ -667,7 +685,10 @@ smtpSecure: "Usar SSL/TLS implícito en la conexión SMTP"
smtpSecureInfo: "Apagar cuando se use STARTTLS"
testEmail: "Prueba de envío"
wordMute: "Silenciar palabras"
+wordMuteDescription: "Minimiza las notas que contienen la palabra o frase especificada. Las notas minimizadas pueden visualizarse haciendo clic sobre ellas."
hardWordMute: "Filtro de palabra fuerte"
+showMutedWord: "Mostrar palabras silenciadas."
+hardWordMuteDescription: "Oculta las notas que contienen la palabra o frase especificada. A diferencia de Silenciar palabra, la nota quedará completamente oculta a la vista."
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"
@@ -1117,6 +1138,8 @@ preventAiLearning: "Rechazar el uso en el Aprendizaje de Máquinas. (IA Generati
preventAiLearningDescription: "Pedirle a las arañas (crawlers) no usar los textos publicados o imágenes en el aprendizaje automático (IA Predictiva / Generativa). Ésto se logra añadiendo una marca respuesta HTML con la cadena \"noai\" al cantenido. Una prevención total no podría lograrse sólo usando ésta marca, ya que puede ser simplemente ignorada."
options: "Opción"
specifyUser: "Especificar usuario"
+lookupConfirm: "¿Quiere informarse?"
+specifyHost: "Especificar Host"
failedToPreviewUrl: "No se pudo generar la vista previa"
update: "Actualizar"
rolesThatCanBeUsedThisEmojiAsReaction: "Roles que pueden usar este emoji como reacción"
@@ -1251,6 +1274,11 @@ tryAgain: "Por favor , inténtalo de nuevo"
performance: "Rendimiento"
unknownWebAuthnKey: "Esto no se ha registrado llave maestra."
messageToFollower: "Mensaje a seguidores"
+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"
+_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."
_abuseUserReport:
accept: "Acepte"
reject: "repudio"
@@ -2535,3 +2563,6 @@ _mediaControls:
pip: "Picture in Picture"
playbackRate: "Velocidad de reproducción"
loop: "Reproducción en bucle"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "No se encuentra"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index b105a86b5e..473774114e 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -2364,3 +2364,6 @@ _mediaControls:
_embedCodeGen:
title: "Personnaliser le code d'intégration"
generateCode: "Générer le code d'intégration"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Non trouvé"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index fe3f207618..9a28cee275 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -2610,3 +2610,6 @@ _mediaControls:
pip: "Gambar dalam Gambar"
playbackRate: "Kecepatan Pemutaran"
loop: "Ulangi Pemutaran"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Tidak dapat ditemukan"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 66ca935b1b..d2942c389c 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -5,6 +5,7 @@ introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato,
poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>."
monthAndDay: "{day}/{month}"
search: "Cerca"
+reset: "Ripristinare"
notifications: "Notifiche"
username: "Nome utente"
password: "Password"
@@ -48,6 +49,7 @@ pin: "Fissa sul profilo"
unpin: "Non fissare sul profilo"
copyContent: "Copia il contenuto"
copyLink: "Copia il link"
+copyRemoteLink: "Copia link remoto"
copyLinkRenote: "Copia collegamento alla Rinota"
delete: "Elimina"
deleteAndEdit: "Elimina e modifica"
@@ -56,7 +58,7 @@ addToList: "Aggiungi alla lista"
addToAntenna: "Aggiungi all'antenna"
sendMessage: "Invia messaggio"
copyRSS: "Copia RSS"
-copyUsername: "Copia nome utente"
+copyUsername: "Copia indirizzo del profilo"
copyUserId: "Copia ID del profilo"
copyNoteId: "Copia ID della Nota"
copyFileId: "Copia ID del file"
@@ -105,7 +107,7 @@ makeFollowManuallyApprove: "Approva i follower manualmente"
defaultNoteVisibility: "Privacy predefinita delle note"
follow: "Segui"
followRequest: "Richiesta di follow"
-followRequests: "Richieste di follow"
+followRequests: "Relazioni"
unfollow: "Togli Following"
followRequestPending: "Richiesta in approvazione"
enterEmoji: "Inserisci emoji"
@@ -440,7 +442,7 @@ recentlyRegisteredUsers: "Profili iscritti di recente"
recentlyDiscoveredUsers: "Profili scoperti di recente"
exploreUsersCount: "Ci sono {count} profili"
exploreFediverse: "Esplora il Fediverso"
-popularTags: "Tag di tendenza"
+popularTags: "Hashtag popolari"
userList: "Liste"
about: "Informazioni"
aboutMisskey: "Informazioni di Misskey"
@@ -535,7 +537,7 @@ regenerate: "Generare di nuovo"
fontSize: "Dimensione carattere"
mediaListWithOneImageAppearance: "Altezza dell'elenco media con una sola immagine "
limitTo: "Limita a {x}"
-noFollowRequests: "Non hai alcuna richiesta di follow"
+noFollowRequests: "Non ci sono richieste di relazione"
openImageInNewTab: "Apri le immagini in un nuovo tab"
dashboard: "Pannello di controllo"
local: "Locale"
@@ -551,8 +553,8 @@ promote: "Pubblicizza"
numberOfDays: "Numero di giorni"
hideThisNote: "Nasconda la nota"
showFeaturedNotesInTimeline: "Mostrare le note di tendenza nella tua timeline"
-objectStorage: "Stoccaggio oggetti"
-useObjectStorage: "Utilizza stoccaggio oggetti"
+objectStorage: "Storage S3"
+useObjectStorage: "Utilizza lo storage S3 in cloud"
objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "URL di riferimento. In caso di utilizzo di proxy o CDN l'URL è 'https://<bucket>.s3.amazonaws.com' per S3, 'https://storage.googleapis.com/<bucket>' per GCS eccetera. "
objectStorageBucket: "Bucket"
@@ -586,6 +588,7 @@ masterVolume: "Volume principale"
notUseSound: "Non emettere suoni"
useSoundOnlyWhenActive: "Emetti suoni solo quando Misskey è in attività"
details: "Dettagli"
+renoteDetails: "Dettagli della Rinota"
chooseEmoji: "Scegli emoji"
unableToProcess: "Impossibile compiere l'operazione"
recentUsed: "Usato di recente"
@@ -683,11 +686,15 @@ smtpSecure: "Usare SSL/TLS implicito per le connessioni SMTP"
smtpSecureInfo: "Disabilitare quando è attivo STARTTLS."
testEmail: "Verifica il funzionamento"
wordMute: "Filtri parole"
+wordMuteDescription: "Contrae le Note con la parola o la frase specificata. Permette di espandere le Note, cliccandole."
hardWordMute: "Filtro parole forte"
+showMutedWord: "Elenca le parole silenziate"
+hardWordMuteDescription: "Nasconde le Note con la parola o la frase specificata. A differenza delle parole silenziate, la Nota non verrà federata."
regexpError: "errore regex"
regexpErrorDescription: "Si è verificato un errore nell'espressione regolare alla riga {line} della parola muta {tab}:"
instanceMute: "Silenziare l'istanza"
userSaysSomething: "{name} ha detto qualcosa"
+userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\""
makeActive: "Attiva"
display: "Visualizza"
copy: "Copia"
@@ -699,7 +706,7 @@ database: "Base dati"
channel: "Canale"
create: "Crea"
notificationSetting: "Impostazioni notifiche"
-notificationSettingDesc: "Seleziona il tipo di notifiche da visualizzare."
+notificationSettingDesc: "Scegli quali notifiche mostrare."
useGlobalSetting: "Usa impostazioni generali"
useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifiche del profilo. Altrimenti si possono segliere impostazioni personalizzate."
other: "Eccetera"
@@ -906,7 +913,7 @@ itsOn: "Abilitato"
itsOff: "Disabilitato"
on: "Acceso"
off: "Spento"
-emailRequiredForSignup: "L'ndirizzo e-mail è obbligatorio per registrarsi"
+emailRequiredForSignup: "L'indirizzo e-mail è obbligatorio per registrarsi"
unread: "Non lette"
filter: "Filtri"
controlPanel: "Pannello di controllo"
@@ -969,7 +976,7 @@ requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso
isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema"
typeToConfirm: "Digita {x} per continuare"
deleteAccount: "Eliminazione profilo"
-document: "Documento"
+document: "Documentazione"
numberOfPageCache: "Numero di pagine cache"
numberOfPageCacheDescription: "Aumenta l'usabilità, ma aumenta anche il carico e l'utilizzo della memoria."
logoutConfirm: "Vuoi davvero uscire da Misskey? "
@@ -1105,7 +1112,7 @@ accountMovedShort: "Questo profilo è stato migrato"
operationForbidden: "Operazione non consentita"
forceShowAds: "Mostra sempre i banner"
addMemo: "Aggiungi Memo"
-editMemo: "Modifica Memo"
+editMemo: "Modifica il promemoria"
reactionsList: "Chi ha reagito?"
renotesList: "Chi ha Rinotato?"
notificationDisplay: "Stile delle notifiche"
@@ -1139,7 +1146,7 @@ options: "Opzioni del ruolo"
specifyUser: "Profilo specifico"
lookupConfirm: "Vuoi davvero richiedere informazioni?"
openTagPageConfirm: "Vuoi davvero aprire la pagina dell'hashtag?"
-specifyHost: "Specifica l'host"
+specifyHost: "Host specifici"
failedToPreviewUrl: "Anteprima non disponibile"
update: "Aggiorna"
rolesThatCanBeUsedThisEmojiAsReaction: "Ruoli che possono usare questa emoji come reazione"
@@ -1239,7 +1246,7 @@ code: "Codice"
reloadRequiredToApplySettings: "Per applicare le impostazioni, occorre ricaricare."
remainingN: "Rimangono: {n}"
overwriteContentConfirm: "Vuoi davvero sostituire l'attuale contenuto?"
-seasonalScreenEffect: "Schermate in base alla stagione"
+seasonalScreenEffect: "Abilita gli effetti speciali stagionali"
decorate: "Decora"
addMfmFunction: "Aggiungi decorazioni"
enableQuickAddMfmFunction: "Attiva il selettore di funzioni MFM"
@@ -1298,6 +1305,10 @@ yourNameContainsProhibitedWordsDescription: "Se desideri comunque utilizzare que
thisContentsAreMarkedAsSigninRequiredByAuthor: "L'autore richiede di iscriversi per vedere il contenuto"
lockdown: "Isolamento"
pleaseSelectAccount: "Per favore, seleziona un profilo"
+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."
_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."
@@ -1454,6 +1465,8 @@ _serverSettings:
reactionsBufferingDescription: "Attivando questa opzione, puoi migliorare significativamente le prestazioni durante la creazione delle reazioni e ridurre il carico sul database. Tuttavia, aumenterà l'impiego di memoria Redis."
inquiryUrl: "URL di contatto"
inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione."
+ openRegistration: "Registrazioni aperte"
+ openRegistrationWarning: "L’apertura della registrazione comporta dei rischi. Ti consigliamo di attivarla solo se hai predisposto il monitoraggio continuo del tuo server e puoi rispondere immediatamente se si verifica un problema."
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Per prevenire SPAM, questa impostazione verrà disattivata automaticamente, se non si rileva alcuna attività di moderazione durante un certo periodo di tempo."
_accountMigration:
moveFrom: "Migra un altro profilo dentro a questo"
@@ -1920,10 +1933,10 @@ _serverDisconnectedBehavior:
quiet: "Visualizza avviso in modo discreto"
_channel:
create: "Nuovo canale"
- edit: "Gerisci canale"
+ edit: "Modifica il canale"
setBanner: "Scegli intestazione"
removeBanner: "Rimuovi intestazione"
- featured: "Di tendenza"
+ featured: "Popolari nel canale"
owned: "I miei canali"
following: "Following"
usersCount: "{n} partecipanti"
@@ -1948,7 +1961,7 @@ _instanceMute:
_theme:
explore: "Esplora temi"
install: "Installa un tema"
- manage: "Gestione temi"
+ manage: "Gestione dei temi"
code: "Codice tema"
description: "Descrizione"
installed: "{name} è installato"
@@ -2095,12 +2108,12 @@ _permissions:
"read:messaging": "Visualizzare la chat"
"write:messaging": "Gestire la chat"
"read:mutes": "Vedi i profili silenziati"
- "write:mutes": "Gestisci i profili silenziati"
+ "write:mutes": "Gestione dei profili silenziati"
"write:notes": "Creare / Eliminare note"
"read:notifications": "Visualizzare notifiche"
- "write:notifications": "Gestire notifiche"
+ "write:notifications": "Gestione delle notifiche"
"read:reactions": "Vedi reazioni"
- "write:reactions": "Gerisci reazioni"
+ "write:reactions": "Gestione delle reazioni"
"write:votes": "Votare"
"read:pages": "Visualizzare pagine"
"write:pages": "Gestire pagine"
@@ -2109,7 +2122,7 @@ _permissions:
"read:user-groups": "Vedere i gruppi di utenti"
"write:user-groups": "Gestire i gruppi di utenti"
"read:channels": "Visualizza canali"
- "write:channels": "Gerisci canali"
+ "write:channels": "Gestione dei canali"
"read:gallery": "Visualizza la galleria."
"write:gallery": "Gestione della galleria"
"read:gallery-likes": "Visualizza i contenuti della galleria."
@@ -2200,7 +2213,7 @@ _widgets:
notifications: "Notifiche"
timeline: "Timeline"
calendar: "Calendario"
- trends: "Di tendenza"
+ trends: "Hashtag popolari"
clock: "Orologio"
rss: "Lettura RSS"
rssTicker: "Nastro RSS"
@@ -2440,13 +2453,13 @@ _notification:
quote: "Cita"
reaction: "Reazioni"
pollEnded: "Sondaggio chiuso."
- receiveFollowRequest: "Richiesta di follow ricevuta"
- followRequestAccepted: "Richiesta di follow accettata"
+ receiveFollowRequest: "Richieste di follow in arrivo"
+ followRequestAccepted: "Richieste di follow accettate"
roleAssigned: "Ruolo concesso"
achievementEarned: "Risultato raggiunto"
exportCompleted: "Esportazione completata"
- login: "Accedi"
- test: "Prova la notifica"
+ login: "Accessi"
+ test: "Notifiche di test"
app: "Notifiche da applicazioni"
_actions:
followBack: "Following ricambiato"
@@ -2716,6 +2729,66 @@ _contextMenu:
app: "Applicazione"
appWithShift: "Applicazione Shift+Tasto"
native: "Interfaccia utente del browser"
+_gridComponent:
+ _error:
+ requiredValue: "Campo obbligatorio"
+ columnTypeNotSupport: "Solo le colonne type:text permettono la convalida delle Espresioni Regolari"
+ patternNotMatch: "Il valore non coincide con {pattern}"
+ notUnique: "Il valore deve essere univoco"
+_roleSelectDialog:
+ notSelected: "Niente selezioato"
+_customEmojisManager:
+ _gridCommon:
+ copySelectionRows: "Copia le righe selezionate"
+ copySelectionRanges: "Copia l'intervallo selezionato"
+ deleteSelectionRows: "Elimina le righe selezionate"
+ deleteSelectionRanges: "Elimina le righe nell'intervallo selezionato"
+ searchSettings: "Impostazioni di ricerca"
+ searchSettingCaption: "Imposta condizioni di ricerca dettagliate."
+ searchLimit: "Risultati visualizzati"
+ sortOrder: "Ordine"
+ registrationLogs: "Storico della registrazione"
+ registrationLogsCaption: "Lo storico verrà visualizzato in base alla attività sulle emoji. Scompare quando si esegue un'operazione di aggiornamento/eliminazione o si modifica/ricarica la pagina."
+ alertEmojisRegisterFailedDescription: "Attenzione, è impossibile modificare la emoji. Si prega di controllare lo storico per ulteriori dettagli."
+ _logs:
+ showSuccessLogSwitch: "Mostra le azioni a buon fine"
+ failureLogNothing: "Non ci sono errori nello storico delle emoji"
+ logNothing: "Lo storico è vuoto."
+ _remote:
+ selectionRowDetail: "Dettagli della riga selezionata"
+ importSelectionRows: "Importa le righe selezionate"
+ importSelectionRangesRows: "Importa le righe nell'intervallo selezionato"
+ importEmojisButton: "Importa le emoji selezionate"
+ confirmImportEmojisTitle: "Importazione emoji"
+ confirmImportEmojisDescription: "Importazione di {count} emoji ricevute da remoto. Si prega di prestare molta attenzione al tipo di licenza delle emoji. Vuoi confermare?"
+ _local:
+ tabTitleList: "Elenco delle emoji registrate"
+ tabTitleRegister: "Registrazione emoji"
+ _list:
+ emojisNothing: "Non ci sono emoji registrate."
+ markAsDeleteTargetRows: "Selezionare le righe come eliminabili"
+ markAsDeleteTargetRanges: "Selezionare le righe nell'intervallo come eliminabili"
+ alertUpdateEmojisNothingDescription: "Non ci sono emoji aggiornate."
+ alertDeleteEmojisNothingDescription: "Non ci sono emoji da eliminare."
+ confirmMovePage: "Vuoi davvero spostare la pagina?"
+ confirmChangeView: "Vuoi davvero cambiare la vista?"
+ confirmUpdateEmojisDescription: "Aggiornamento di {count} emoji. Vuoi davvero continuare?"
+ confirmDeleteEmojisDescription: "Eliminazione delle {count} emoji selezionate. Vuoi davvero continuare?"
+ confirmResetDescription: "Verranno ripristinate tutte le modifiche apportate finora."
+ confirmMovePageDesciption: "Sono state modificate le emoji in questa pagina.\nUscendo senza salvare, tutte le modifiche verranno ignorate."
+ dialogSelectRoleTitle: "Cerca emoji per ruolo"
+ _register:
+ uploadSettingTitle: "Caricamento impostazioni"
+ uploadSettingDescription: "Questa schermata ti permette di scegliere il comportamento durante il caricamento delle emoji."
+ directoryToCategoryLabel: "Inseriscile in una cartella omonima alla categoria"
+ directoryToCategoryCaption: "Crea il campo categoria in base alla cartella."
+ emojiInputAreaCaption: "Seleziona l'emoji da registrare utilizzando uno dei metodi."
+ emojiInputAreaList1: "Trascina una immagine o una cartella in quest'area"
+ emojiInputAreaList2: "Clicca per scegliere file dal tuo dispositivo"
+ emojiInputAreaList3: "Clicca per selezionare dal Drive"
+ confirmRegisterEmojisDescription: "Registrazione delle emoji elencate come nuove emoji personalizzate. Vuoi davvero procedere? (Per evitare sovraccarichi, puoi registrare al massimo {count} emoji per volta)"
+ confirmClearEmojisDescription: "Annullare le modifiche e cancella le emoji nell'elenco. Confermi?"
+ confirmUploadEmojisDescription: "Caricamento sul Drive di {count} file locali. Vuoi davvero procedere?"
_embedCodeGen:
title: "Personalizza il codice di incorporamento"
header: "Mostra la testata"
@@ -2736,3 +2809,37 @@ _selfXssPrevention:
description1: "Incollando qualcosa qui, malintenzionati potrebbero prendere il controllo del tuo profilo o rubare i tuoi dati personali."
description2: "Se non sai esattamente cosa stai facendo, %c smetti subito e chiudi questa finestra."
description3: "Per favore, controlla questo collegamento per avere maggiori dettagli. {link}"
+_followRequest:
+ recieved: "Ricezione richiesta di Follow"
+ sent: "Richiesta di Follow, inviata"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "Server irraggiungibile"
+ description: "La comunicazione con questo server potrebbe essere disattivata. Hai bloccato il server? Oppure potrebbero averlo bloccato gli amministratori. Contattali per ulteriori informazioni."
+ _uriInvalid:
+ title: "URL non valido"
+ description: "Controlla che l'indirizzo sia valido e sia privo di caratteri non validi."
+ _requestFailed:
+ title: "Richiesta fallita"
+ description: "La comunicazione col server non è riuscita. Potrebbe essere inattivo. Assicurati anche che la URL sia valida."
+ _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."
+_captcha:
+ verify: "Per favore, controlla la verifica CAPTCHA"
+ testSiteKeyMessage: "Puoi provare l'anteprima inserendo valori di test, sia per la chiave del sito che per la chiave segreta.\nSi prega di controllare la pagina qui sotto per i dettagli."
+ _error:
+ _requestFailed:
+ title: "Errore durante la richiesta del CAPTCHA"
+ text: "Riprova più tardi o controlla nuovamente le impostazioni."
+ _verificationFailed:
+ title: "Convalida CAPTCHA non riuscita"
+ text: "Si prega di verificare nuovamente se le impostazioni sono corrette."
+ _unknown:
+ title: "Errore CAPTCHA"
+ text: "Si è verificato un errore imprevisto."
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 1b59708d85..a578704434 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.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,8 @@ lockdown: "ロックダウン"
pleaseSelectAccount: "ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’é¸æŠžã—ã¦ãã ã•ã„"
availableRoles: "利用å¯èƒ½ãªãƒ­ãƒ¼ãƒ«"
acknowledgeNotesAndEnable: "注æ„事項をç†è§£ã—ãŸä¸Šã§ã‚ªãƒ³ã«ã—ã¾ã™ã€‚"
+federationSpecified: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¯ãƒ›ãƒ¯ã‚¤ãƒˆãƒªã‚¹ãƒˆé€£åˆã§é‹ç”¨ã•れã¦ã„ã¾ã™ã€‚管ç†è€…ãŒæŒ‡å®šã—ãŸã‚µãƒ¼ãƒãƒ¼ä»¥å¤–ã¨ã‚„りå–りã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。"
+federationDisabled: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¯é€£åˆãŒç„¡åŠ¹åŒ–ã•れã¦ã„ã¾ã™ã€‚ä»–ã®ã‚µãƒ¼ãƒãƒ¼ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨ã‚„りå–りã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。"
_accountSettings:
requireSigninToViewContents: "コンテンツã®è¡¨ç¤ºã«ãƒ­ã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã™ã‚‹"
@@ -2801,6 +2809,69 @@ _contextMenu:
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: "ヘッダーを表示"
@@ -2826,3 +2897,36 @@ _selfXssPrevention:
_followRequest:
recieved: "å—ã‘å–ã£ãŸç”³è«‹"
sent: "é€ã£ãŸç”³è«‹"
+
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¨ã¯é€šä¿¡ã§ãã¾ã›ã‚“"
+ description: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¨ã®é€šä¿¡ãŒç„¡åŠ¹åŒ–ã•れã¦ã„ã‚‹ã‹ã€ã“ã®ã‚µãƒ¼ãƒãƒ¼ã‚’ブロックã—ã¦ã„る・ブロックã•れã¦ã„ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚\nサーãƒãƒ¼ç®¡ç†è€…ã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。"
+ _uriInvalid:
+ title: "URIãŒä¸æ­£ã§ã™"
+ description: "入力ã•れãŸURIã«å•題ãŒã‚りã¾ã™ã€‚URIã«ä½¿ç”¨ã§ããªã„文字を入力ã—ã¦ã„ãªã„ã‹ç¢ºèªã—ã¦ãã ã•ã„。"
+ _requestFailed:
+ title: "リクエストã«å¤±æ•—ã—ã¾ã—ãŸ"
+ description: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¨ã®é€šä¿¡ã«å¤±æ•—ã—ã¾ã—ãŸã€‚相手サーãƒãƒ¼ãŒãƒ€ã‚¦ãƒ³ã—ã¦ã„ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚ã¾ãŸã€ä¸æ­£ãªURIや存在ã—ãªã„URIを入力ã—ã¦ã„ãªã„ã‹ç¢ºèªã—ã¦ãã ã•ã„。"
+ _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: "想定外ã®ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index c3e0096926..2dd2220791 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -15,7 +15,7 @@ forgotPassword: "パスワード忘れãŸã‚“?"
fetchingAsApObject: "今ã¡ã¨é€£åˆã«ç…§ä¼šã—ã¨ã‚‹ã§"
ok: "ãˆãˆã§"
gotIt: "ã»ã„"
-cancel: "ã‚„ã‚ã¨ã"
+cancel: "ã‚„ã‚ã‚‹"
noThankYou: "ã‚„ã‚ã¨ã"
enterUsername: "ユーザーåを入れã¦ã‚„"
renotedBy: "{user}ãŒãƒªãƒŽãƒ¼ãƒˆã—ãŸã§"
@@ -26,7 +26,7 @@ settings: "設定"
notificationSettings: "通知ã®è¨­å®š"
basicSettings: "基本設定"
otherSettings: "ã»ã‹ã®è¨­å®š"
-openInWindow: "ウィンドウã§é–‹ãã§"
+openInWindow: "ウィンドウã§é–‹ã"
profile: "プロフィール"
timeline: "タイムライン"
noAccountDescription: "自己紹介食ã£ã¦ã‚‚ãŸ"
@@ -45,7 +45,7 @@ favorited: "ãŠæ°—ã«å…¥ã‚Šã«å…¥ã‚ŒãŸã§ã€‚"
alreadyFavorited: "ã‚‚ã†ãŠæ°—ã«å…¥ã‚Šã«å…¥ã‚Œã¨ã‚‹ãŒãªã€‚"
cantFavorite: "アカンã€ãŠæ°—ã«å…¥ã‚Šã«å…¥ã‚Œã‚Œã‚“ã‹ã£ãŸã‚。"
pin: "ピン留ã‚ã—ã¨ã"
-unpin: "ã‚„ã£ã±ãƒ”ン留ã‚ã›ã‚“"
+unpin: "ピン留ã‚ã‚„ã‚ã‚‹"
copyContent: "内容をコピー"
copyLink: "リンクをコピー"
copyLinkRenote: "リノートã®ãƒªãƒ³ã‚¯ã‚’コピーã™ã‚‹ã§ï¼Ÿ"
@@ -63,7 +63,7 @@ copyFileId: "ファイルIDをコピー"
copyFolderId: "フォルダーIDをコピー"
copyProfileUrl: "プロフィールURLをコピー"
searchUser: "ユーザーを探ã™"
-searchThisUsersNotes: "ユーザーã®ãƒŽãƒ¼ãƒˆã‚’検索"
+searchThisUsersNotes: "ユーザーã®ãƒŽãƒ¼ãƒˆã‚’探ã™"
reply: "返事"
loadMore: "ã¾ã ã¾ã ã‚ã‚‹ã§ï¼"
showMore: "ã¾ã ã¾ã ã‚ã‚‹ã§ï¼"
@@ -138,8 +138,8 @@ reactionSettingDescription2: "ドラッグã§ä¸¦ã³æ›¿ãˆã€ã‚¯ãƒªãƒƒã‚¯ã§å‰Šé™
rememberNoteVisibility: "公開範囲覚ãˆã¨ã„ã¦"
attachCancel: "ã®ã£ã‘ã‚‹ã®ã‚„ã‚ã‚‹"
deleteFile: "ファイルをã»ã‹ã™"
-markAsSensitive: "ã¡ã‚‡ã£ã¨ã“れã¯ã‚¢ã‚«ãƒ³"
-unmarkAsSensitive: "ãã“ã¾ã§ã‚¢ã‚«ãƒ³ã“ã¨ãªã„ã‚„ã‚"
+markAsSensitive: "ã¡ã‚‡ã£ã¨è¦‹ã›ã‚‰ã‚Œã¸ã‚“ã‚"
+unmarkAsSensitive: "別ã«ãˆãˆã‚“ã˜ã‚ƒã­ï¼Ÿ"
enterFileName: "ファイルåを入れã¦ã‚„"
mute: "ミュート"
unmute: "ミュートやã‚ãŸã‚‹"
@@ -152,13 +152,13 @@ unsuspend: "溶ã‹ã™"
blockConfirm: "ブロックã—ã¦ã‚‚ãˆãˆã‚“ã‹ï¼Ÿ"
unblockConfirm: "ブロックやã‚ãŸã‚‹ã£ã¦ã»ã‚“ã¾ã‹ï¼Ÿ"
suspendConfirm: "å‡çµã—ã¦ã—ã‚‚ã†ã¦ãˆãˆã‹ï¼Ÿ"
-unsuspendConfirm: "è§£å‡ã™ã‚‹ã‘ã©ãˆãˆã‹ï¼Ÿ"
+unsuspendConfirm: "溶ã‹ã—ãŸã‚‹ã‘ã©ãˆãˆã‹ï¼Ÿ"
selectList: "リストをé¸ã¶"
editList: "リストã„ã˜ã‚‹"
selectChannel: "ãƒãƒ£ãƒ³ãƒãƒ«ã‚’é¸ã¶"
selectAntenna: "アンテナをé¸ã¶"
editAntenna: "アンテナã„ã˜ã‚‹"
-createAntenna: "アンテナを作æˆ"
+createAntenna: "アンテナを作る"
selectWidget: "ウィジェットをé¸ã¶"
editWidgets: "ウィジェットをã„ã˜ã‚‹"
editWidgetsExit: "ã„ã˜ã‚‹ã®ã‚’ã‚„ã‚ã‚‹"
@@ -172,12 +172,12 @@ settingGuide: "ãˆãˆæ„Ÿã˜ã®è¨­å®š"
cacheRemoteFiles: "リモートã®ãƒ•ァイルをキャッシュã™ã‚‹"
cacheRemoteFilesDescription: "ã“ã®è¨­å®šã‚’入れã¨ã£ãŸã‚‰ã€ãƒªãƒ¢ãƒ¼ãƒˆã®ãƒ•ァイルを端ã‹ã‚‰ç«¯ã¾ã§ã“ã®ã‚µãƒ¼ãƒãƒ¼ã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã‚“中çªã£è¾¼ã‚€ã‚ˆã†ã«ãªã‚‹ã§ã€‚ç”»åƒæ˜ ã—出ã™ã‚“ãŒã‚ã£ã¡ã‚ƒé€Ÿã†ãªã‚‹ã‘ã©ã€ã‚µãƒ¼ãƒãƒ¼ã®å®¹é‡ã‚’ã‚„ãŸã‚‰ã¨é£Ÿã†ã‚ˆã†ã«ãªã‚‹ã§ã€‚リモートã®äººãŒã©ã‚“ã ã‘é•·ãキャッシュをæŒã£ã¨ãã‹ã¯ãƒ‰ãƒ©ã‚¤ãƒ–容é‡ã®åˆ¶é™ã§æ±ºã‚ã¨ãã§ã€‚制é™ã‚’è¶…ãˆãŸã‚‰å¤ã„ã®ã‹ã‚‰é †ã€…ã«æ¶ˆã—ã¦ã£ã¦ã€ã‹ã‚りã«ãƒªãƒ³ã‚¯ã«ãªã‚‹ã§ã€‚ã“ã®è¨­å®šã‚’切ã£ãŸã‚‰ã€ãƒªãƒ¢ãƒ¼ãƒˆã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯æœ€åˆã£ã‹ã‚‰ãƒªãƒ³ã‚¯ã¨ã—ã¦æ‰±ã†ã“ã¨ã«ã™ã‚‹ã‘ã©ã€ç”»åƒã®ã‚µãƒ ãƒä½œã‚‹ã®ã¨ã‹ã¿ã‚“ãªã®ãƒ—ライãƒã‚·ãƒ¼å®ˆã‚‹ãŸã‚ã«ã€default.ymlã®proxyRemoteFilesã‚’trueã«ã—ã¨ã„ãŸã»ã†ãŒãˆãˆã‚ˆã€‚"
youCanCleanRemoteFilesCache: "ファイル管ç†ã«ã‚る🗑ï¸ãƒœã‚¿ãƒ³ã§ã‚­ãƒ£ãƒƒã‚·ãƒ¥å…¨éƒ¨ã»ã‹ã™ã§ã€‚"
-cacheRemoteSensitiveFiles: "リモートã®ãã‚ã©ã„ファイルをキャッシュã«çªã£è¾¼ã‚€"
+cacheRemoteSensitiveFiles: "リモートã®ãã‚ã©ã„ファイルをキャッシュã™ã‚‹"
cacheRemoteSensitiveFilesDescription: "ã“ã®è¨­å®šã‚’切るã¨ã€ãƒªãƒ¢ãƒ¼ãƒˆã®ãã‚ã©ã„ファイルã¯ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã›ãšç›´ã§ãƒªãƒ³ã‚¯ã™ã‚‹ã‚ˆã†ã«ãªã‚‹ã§ã€‚"
flagAsBot: "Botã«ã™ã‚‹ã§"
flagAsBotDescription: "ã‚‚ã—ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’プログラム使ã†ã¦é‹ç”¨ã™ã‚‹ã‚“ã‚„ã£ãŸã‚‰ã€ã“ã®ãƒ•ラグをオンã«ã—ã¦ã‚„。オンã«ã™ã‚Œã°ã€å応ãŒãƒãƒ¼ãƒƒã¦é€£éŽ–ã›ã‚“よã†ã«é–‹ç™ºè€…ãŒä½¿ã†ãŸã‚Šã€Misskeyã®ã‚·ã‚¹ãƒ†ãƒ ä¸Šã§ã®æ‰±ã„ãŒBotã«åˆã£ãŸã‚‚ã‚“ã«ãªã‚‹ã‹ã‚‰ãªã€‚"
flagAsCat: "猫や。ã‹ã‚ãˆãˆãªã€‚"
-flagAsCatDescription: "ãƒã‚³ã«ãªã‚ŠãŸã„ã‚“ãªã‚‰ã“れã¤ã‘ã¨ã。"
+flagAsCatDescription: "猫ã«ãªã‚ŠãŸã„ã‚“ãªã‚‰ã“れã¤ã‘ã¨ã。"
flagShowTimelineReplies: "タイムラインã«ãƒŽãƒ¼ãƒˆã¸ã®è¿”信を表示ã™ã‚‹ã§"
flagShowTimelineRepliesDescription: "オンã«ã—ãŸã‚‰ã€ã‚¿ã‚¤ãƒ ãƒ©ã‚¤ãƒ³ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒŽãƒ¼ãƒˆã®ä»–ã«ã‚‚ãã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ä»–ã®ãƒŽãƒ¼ãƒˆã¸ã®è¿”信を表示ã™ã‚‹ã§ã€‚"
autoAcceptFollowed: "フォローã—ã¨ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‹ã‚‰ã®ãƒ•ã‚©ãƒ­ãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’å‹æ‰‹ã«è¨±å¯ã—ã¨ã"
@@ -186,9 +186,9 @@ reloadAccountsList: "ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãƒªã‚¹ãƒˆã®æƒ…報を更新"
loginFailed: "ログインã«å¤±æ•—ã—ã¦ã‚‚ã†ãŸâ€¦"
showOnRemote: "リモートã§è¦‹ã‚‹"
continueOnRemote: "リモートã§ç¶šè¡Œ"
-chooseServerOnMisskeyHub: "Misskey Hubã‹ã‚‰ã‚µãƒ¼ãƒãƒ¼ã‚’é¸æŠž"
+chooseServerOnMisskeyHub: "Misskey Hubã‹ã‚‰ã‚µãƒ¼ãƒãƒ¼ã‚’é¸ã¶"
specifyServerHost: "サーãƒãƒ¼ã®ãƒ‰ãƒ¡ã‚¤ãƒ³ã‚’直接指定"
-inputHostName: "ドメインを入力ã›ãˆã‚„"
+inputHostName: "ドメインを入力ã—ã¦ã‚„"
general: "全般"
wallpaper: "å£ç´™"
setWallpaper: "å£ç´™ã‚’設定"
@@ -586,6 +586,7 @@ masterVolume: "全体ã®ã‚„ã‹ã¾ã—ã•"
notUseSound: "音出ã•ã¸ã‚“"
useSoundOnlyWhenActive: "MisskeyãŒã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªã¨ãã ã‘音出ã™"
details: "ã‚‚ã£ã¨"
+renoteDetails: "リノートã®è©³ç´°"
chooseEmoji: "絵文字をé¸ã¶"
unableToProcess: "ãªã‚“ã‹å¥¥ã®æ–¹ã§è©°ã¾ã£ã¦ã‚‚ã†ãŸ"
recentUsed: "最近使ã£ãŸã‚„ã¤"
@@ -946,6 +947,9 @@ oneHour: "1時間"
oneDay: "1æ—¥"
oneWeek: "1週間"
oneMonth: "1ヶ月"
+threeMonths: "3ヶ月"
+oneYear: "1å¹´"
+threeDays: "3æ—¥"
reflectMayTakeTime: "åæ˜ ã•れるã¾ã§æ™‚é–“ãŒã‹ã‹ã‚‹ã“ã¨ãŒã‚ã‚‹ã§"
failedToFetchAccountInformation: "アカウントã®å–å¾—ã«å¤±æ•—ã—ãŸã¿ãŸã„や…"
rateLimitExceeded: "レート制é™ãŒè¶…ãˆãŸã¿ãŸã„ã‚„ã§"
@@ -1292,6 +1296,23 @@ prohibitedWordsForNameOfUser: "ç¦æ­¢ãƒ¯ãƒ¼ãƒ‰ï¼ˆãƒ¦ãƒ¼ã‚¶ãƒ¼å)"
prohibitedWordsForNameOfUserDescription: "ã“ã®ãƒªã‚¹ãƒˆã®ä¸­ã«ã‚る文字列ãŒãƒ¦ãƒ¼ã‚¶ãƒ¼åã«å…¥ã£ã¨ã£ãŸã‚‰ã€ãã®åå‰ã«å¤‰æ›´ã§ãã²ã‚“よã†ã«ãªã‚‹ã§ã€‚モデレーター権é™ãŒã‚るユーザーã¯é™¤å¤–や。"
yourNameContainsProhibitedWords: "ãã®åå‰ã¯ç¦æ­¢ã—ãŸæ–‡å­—列ãŒå«ã¾ã‚Œã¨ã‚‹ã§"
yourNameContainsProhibitedWordsDescription: "ãã®åå‰ã¯ç¦æ­¢ã—ãŸæ–‡å­—列ãŒå«ã¾ã‚Œã¨ã‚‹ã‚。ã©ã†ã—ã¦ã‚‚ã£ã¦è¨€ã†ãªã‚‰ã€ã‚µãƒ¼ãƒãƒ¼ç®¡ç†è€…ã«è¨€ã†ã—ã‹ãªã„ã§ã€‚"
+thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者ãŒã€è¡¨ç¤ºã«ãƒ­ã‚°ã‚¤ãƒ³ãŒè¦ã‚‹ã£ã¦è¨­å®šã—ã¦ã‚‹ã§"
+lockdown: "ロックダウン"
+pleaseSelectAccount: "アカウントé¸ã‚“ã§ã‚„"
+availableRoles: "使ãˆã‚‹ãƒ­ãƒ¼ãƒ«"
+acknowledgeNotesAndEnable: "注æ„事項をã‚ã‹ã£ãŸä¸Šã§ã‚ªãƒ³ã«ã™ã‚‹ã€‚"
+_accountSettings:
+ requireSigninToViewContents: "ログインã—ã¦ã‚‚らã£ã¦ã‹ã‚‰ã‚³ãƒ³ãƒ†ãƒ³ãƒ„見ã¦ã‚‚らã†"
+ requireSigninToViewContentsDescription1: "ã‚ãªãŸãŒä½œæˆã—ãŸå…¨éƒ¨ã®ãƒŽãƒ¼ãƒˆã¨ã‹ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を見れるよã†ã«ã™ã‚‹ã®ã«ãƒ­ã‚°ã‚¤ãƒ³ãŒã„るよã†ã«ã™ã‚‹ã§ã€‚クローラーã«ã„ã‚ã„ã‚åŽé›†ã•れるんを防ã’ã‚‹ã‹ã‚‚ã—れん。"
+ requireSigninToViewContentsDescription2: "URLプレビュー(OGP)ã€Webページã¸ã®åŸ‹ã‚è¾¼ã¿ã€ãƒŽãƒ¼ãƒˆã®å¼•用ã«å¯¾å¿œã—ã¦ãªã„サーãƒãƒ¼ã‹ã‚‰ã®è¡¨ç¤ºãŒã§ãã‚“ããªã‚‹ã§ã€‚"
+ requireSigninToViewContentsDescription3: "リモートサーãƒãƒ¼ã«é€£åˆã•れãŸã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã¯ã€ã“れらã®åˆ¶é™ãŒé©ç”¨ã•れんã‹ã‚‚ã—れんã§ã€‚"
+ makeNotesFollowersOnlyBefore: "昔ã®ãƒŽãƒ¼ãƒˆã‚’フォロワーã ã‘ã«è¦‹ã¦ã‚‚らã†"
+ makeNotesFollowersOnlyBeforeDescription: "ã“ã®æ©Ÿèƒ½ãŒæœ‰åйã«ãªã£ã¦ã‚‹é–“ã¯ã€è¨­å®šã•ã‚ŒãŸæ—¥æ™‚よりå‰ã€ãれã‹è¨­å®šã•ã‚ŒãŸæ™‚é–“ãŒçµŒã£ãŸãƒŽãƒ¼ãƒˆãŒãƒ•ォロワーã®ã¿è¦‹ã‚Œã‚‹ã‚ˆã†ã«ãªã‚‹ã§ã€‚ç„¡åŠ¹ã«æˆ»ã™ã¨ã€ãƒŽãƒ¼ãƒˆã®å…¬é–‹çŠ¶æ…‹ã‚‚æˆ»ã‚‹ã§ã€‚"
+ makeNotesHiddenBefore: "昔ã®ãƒŽãƒ¼ãƒˆã‚’見れんよã†ã«ã™ã‚‹"
+ makeNotesHiddenBeforeDescription: "ã“ã®æ©Ÿèƒ½ãŒæœ‰åйã«ãªã£ã¦ã‚‹é–“ã¯ã€è¨­å®šã•ã‚ŒãŸæ—¥æ™‚よりå‰ã€ãれã‹è¨­å®šã•ã‚ŒãŸæ™‚é–“ãŒçµŒã£ãŸãƒŽãƒ¼ãƒˆãŒãƒ•ォロワーã®ã¿è¦‹ã‚Œã‚‹ã‚ˆã†ã«ãªã‚‹ã§ã€‚ç„¡åŠ¹ã«æˆ»ã™ã¨ã€ãƒŽãƒ¼ãƒˆã®å…¬é–‹çŠ¶æ…‹ã‚‚æˆ»ã‚‹ã§ã€‚"
+ mayNotEffectForFederatedNotes: "リモートサーãƒãƒ¼ã«é€£åˆã•れãŸãƒŽãƒ¼ãƒˆã«ã¯åŠ¹æžœãŒåŠã°ã‚“ã‹ã‚‚ã—れん。"
+ notesHavePassedSpecifiedPeriod: "決ã‚ãŸæ™‚é–“ãŒçµŒã£ãŸãƒŽãƒ¼ãƒˆ"
+ notesOlderThanSpecifiedDateAndTime: "決ã‚ãŸæ—¥æ™‚よりå‰ã®ãƒŽãƒ¼ãƒˆ"
_abuseUserReport:
forward: "転é€"
forwardDescription: "匿åã®ã‚·ã‚¹ãƒ†ãƒ ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã£ã¦ã“ã¨ã«ã—ã¦ã€ãƒªãƒ¢ãƒ¼ãƒˆã‚µãƒ¼ãƒãƒ¼ã«é€šå ±ã‚’転é€ã™ã‚‹ã§ã€‚"
@@ -1436,6 +1457,8 @@ _serverSettings:
reactionsBufferingDescription: "有効ã«ã—ãŸã‚‰ã€ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä½œã‚‹ã¨ãã®ãƒ‘フォーマンスãŒã™ã£ã”ã„上ãŒã£ã¦ã€ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã¸ã®è² è·ãŒæ¸›ã‚‹ã§ã€‚代ã‚りã«ã€Redisã®ãƒ¡ãƒ¢ãƒªä½¿ç”¨ã¯å¢—ãˆã‚‹ã§ã€‚"
inquiryUrl: "å•ã„åˆã‚ã›å…ˆURL"
inquiryUrlDescription: "サーãƒãƒ¼é‹å–¶è€…ã¸ã®ãŠå•ã„åˆã‚ã›ãƒ•ォームã®URLã‚„ã€é‹å–¶è€…ã®é€£çµ¡å…ˆç­‰ãŒè¨˜è¼‰ã•れãŸWebページã®URLを指定ã™ã‚‹ã§ã€‚"
+ openRegistration: "アカウントã®ä½œæˆã‚’オープンã«ã™ã‚‹"
+ openRegistrationWarning: "登録を解放ã™ã‚‹ã®ã¯ãƒªã‚¹ã‚¯ãŒä¼´ã†ã§ã€‚サーãƒãƒ¼ã‚’ã„ã£ã¤ã‚‚監視ã—ã¦ã€ãªã‚“ã‹èµ·ããŸã‚‰ã™ãã«å¯¾å¿œã§ãã‚‹ã‚“ã‚„ã£ãŸã‚‰ã€ã‚ªãƒ³ã«ã—ã¦ã‚‚ãˆãˆã¨æ€ã†ã€‚"
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターãŒãŠã‚‰ã‚“ã‹ã£ãŸã‚‰ã€ã‚¹ãƒ‘ムを防ããŸã‚ã«ã“ã®è¨­å®šã¯å‹æ‰‹ã«åˆ‡ã‚‰ã‚Œã‚‹ã§ã€‚"
_accountMigration:
moveFrom: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¼•ã£è¶Šã™"
@@ -2156,8 +2179,11 @@ _auth:
permissionAsk: "ã“ã®ã‚¢ãƒ—ãƒªã¯æ¬¡ã®æ¨©é™ã‚’è¦æ±‚ã—ã¨ã‚‹ã§"
pleaseGoBack: "ã‚¢ãƒ—ãƒªã‚±ãƒ¼ã‚·ãƒ§ãƒ³ã«æˆ»ã£ã¦ãˆãˆã‚ˆ"
callback: "ã‚¢ãƒ—ãƒªã‚±ãƒ¼ã‚·ãƒ§ãƒ³ã«æˆ»ã£ã¨ã‚‹ã§"
+ accepted: "アクセスを許å¯ã—ãŸã§"
denied: "アクセスを拒å¦ã£ãŸã§"
+ scopeUser: "以下ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨ã—ã¦ã„ã˜ã£ã¦ã‚‹ã§"
pleaseLogin: "アプリã«ã‚¢ã‚¯ã‚»ã‚¹ã•ã›ã‚‹ã‚“ã‚„ã£ãŸã‚‰ã€ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ã‚„。"
+ byClickingYouWillBeRedirectedToThisUrl: "アクセスを許ã—ãŸã‚‰ã€è‡ªå‹•ã§ä¸‹ã®URLã«é·ç§»ã™ã‚‹ã§"
_antennaSources:
all: "ã¿ã‚“ãªã®ãƒŽãƒ¼ãƒˆ"
homeTimeline: "フォローã—ã¨ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒŽãƒ¼ãƒˆ"
@@ -2709,3 +2735,30 @@ _embedCodeGen:
generateCode: "埋ã‚è¾¼ã¿ã‚³ãƒ¼ãƒ‰ä½œã‚‹"
codeGenerated: "コード作ã£ãŸã§"
codeGeneratedDescription: "作ã£ãŸã‚³ãƒ¼ãƒ‰ã¯ã‚¦ã‚§ãƒ–サイトã«è²¼ã£ã¤ã‘ã¦ä½¿ã£ã¦ã‚„。"
+_selfXssPrevention:
+ warning: "警告"
+ title: "「ã“ã®ç”»é¢ã«ãªã‚“ã‹è²¼ã‚Šä»˜ã‘ã‚ã€ã¯å…¨éƒ¨è©æ¬ºã‚„ã§ã€‚"
+ description1: "ã“ã“ã«ãªã‚“ã‹ã¯ã¤ã£ã¤ã‘ã‚‹ã¨ã€æ‚ªã„ユーザーã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆä¹—ã£å–られãŸã‚Šã€å€‹äººæƒ…報盗ã¾ã‚ŒãŸã‚Šã™ã‚‹ã‹ã‚‚ã‚„ã§"
+ description2: "ã¯ã£ã¤ã‘よã†ã¨ã—ã¦ã‚‹ã‚‚ã®ãŒãªã‚“ãªã‚“ã‹ã‚ã‹ã‚‰ã‚“ã®ã‚„ã£ãŸã‚‰ã€%c今ã™ã作業やã‚ã¦ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã‚’é–‰ã˜ã¦ã€‚"
+ description3: "詳ã—ãã¯ã“れを見ã¦ã€‚{link}"
+_followRequest:
+ recieved: "もらã£ãŸç”³è«‹"
+ sent: "é€ã£ãŸç”³è«‹"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¨é€šä¿¡ã§ãã‚“"
+ description: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¨ã®é€šä¿¡ã¯ç„¡åŠ¹åŒ–ã•れã¦ã‚‹ã‹ã€ã“ã®ã‚µãƒ¼ãƒãƒ¼ã‚’ブロックã—ã¦ã‚‹ã‚“ã‹ã€ãƒ–ロックã•れã¦ã‚‹ã‹ã‚‚ã—れん。\nサーãƒãƒ¼ç®¡ç†è€…ã«å•ã„åˆã‚ã›ã¦ã‚„。"
+ _uriInvalid:
+ title: "URIãŒãŠã‹ã—ã„ã§"
+ description: "入力ã•れãŸURIã«å•題ãŒã‚ã‚‹ã§ã€‚URIã«ä½¿ãˆã‚“文字を入れã¦ãªã„ã‹ã‚‰ç¢ºã‹ã‚ã¦ã€‚"
+ _requestFailed:
+ title: "リクエスト失敗ã—ã¦ã‚‚ã†ãŸã§"
+ description: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¨ã®é€šä¿¡ã«å¤±æ•—ã—ã¦ã‚‚ã†ãŸã‚。相手サーãƒãƒ¼ãŒãƒ€ã‚¦ãƒ³ã—ã¦ã‚‹ã‹ã‚‚ã—れん。ã‚ã¨ã€ãŠã‹ã—ã„URIã¨ã‹ã€ã‚りãˆã‚“URIを入れã¦ãªã„ã‹ç¢ºã‹ã‚ã¦ã€‚"
+ _responseInvalid:
+ title: "レスãƒãƒ³ã‚¹ãŒãŠã‹ã—ã„ã§"
+ description: "ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¨é€šä¿¡ã™ã‚‹ã“ã¨ã¯ã§ããŸã‘ã©ã€ã‚‚らã£ãŸãƒ‡ãƒ¼ã‚¿ãŒãŠã‹ã—ã‹ã£ãŸã§ã€‚"
+ _responseInvalidIdHostNotMatch:
+ description: "入力ã•れãŸURIã®ãƒ‰ãƒ¡ã‚¤ãƒ³ã¨æœ€çµ‚çš„ã«å¾—られãŸURIã®ãƒ‰ãƒ¡ã‚¤ãƒ³ã¨ãŒé•ã†ã§ã€‚第三者ã®ã‚µãƒ¼ãƒãƒ¼ã‚’介ã—ã¦ãƒªãƒ¢ãƒ¼ãƒˆã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を照会ã—ã¦ã‚‹ã‚“ã‚„ã£ãŸã‚‰ã€ç™ºä¿¡å…ƒã®ã‚µãƒ¼ãƒãƒ¼ã§å–å¾—ã§ãã‚‹URIを使ã£ã¦ç…§ä¼šã—ç›´ã—ã¦ã€‚"
+ _noSuchObject:
+ title: "見ã¤ã‹ã‚‰ã¸ã‚“ã­"
+ description: "求ã‚られãŸãƒªã‚½ãƒ¼ã‚¹ãŒè¦‹ã¤ã‹ã‚‰ã‚“ã‹ã£ãŸã§ã€‚URIã‚’ã‚‚ã£ã‹ã„確ã‹ã‚ã¦ã‚„。"
diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml
index 60b82d5db9..4b9650b636 100644
--- a/locales/ko-GS.yml
+++ b/locales/ko-GS.yml
@@ -840,3 +840,6 @@ _reversi:
black: "꺼ë©"
white: "í—ˆì˜"
total: "합게"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "몬 찾앗십니다"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index d694d2dbae..45d7f26075 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.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 ì—°ê²°ì— Implicit SSL/TTS 사용"
smtpSecureInfo: "STARTTLS 사용 시ì—는 해제합니다."
testEmail: "ì´ë©”ì¼ ì „ì†¡ 테스트"
wordMute: "단어 뮤트"
+wordMuteDescription: "ì •í•´ì§„ 단어가 í¬í•¨ëœ 노트를 최소화 한 ìƒíƒœë¡œ 표시합니다. 최소화 ëœ ë…¸íŠ¸ëŠ” í´ë¦­í•´ì„œ 표시할 수 있습니다."
hardWordMute: "하드 단어 뮤트"
+showMutedWord: "뮤트한 단어를 표시하기"
+hardWordMuteDescription: "정한 단어가 들어간 노트를 숨ê¹ë‹ˆë‹¤. 단어 뮤트와 ì°¨ì´ì ì€ 노트가 아예 ë³´ì´ì§€ 않습니다."
regexpError: "ì •ê·œ í‘œí˜„ì‹ ì˜¤ë¥˜"
regexpErrorDescription: "{tab}단어 뮤트 {line}í–‰ì˜ ì •ê·œ 표현ì‹ì— 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤:"
instanceMute: "서버 뮤트"
userSaysSomething: "{name}ë‹˜ì´ ë¬´ì–¸ê°€ë¥¼ ë§í–ˆìŠµë‹ˆë‹¤"
+userSaysSomethingAbout: "{name}ë‹˜ì´ \"{word}\"를 언급했습니다."
makeActive: "활성화"
display: "보기"
copy: "복사"
@@ -1277,7 +1283,7 @@ confirmWhenRevealingSensitiveMedia: "민ê°í•œ 미디어를 ì—´ 때 ë‘ ë²ˆ 확ì
sensitiveMediaRevealConfirm: "민ê°í•œ 미디어입니다. 표시할까요?"
createdLists: "만든 리스트"
createdAntennas: "만든 안테나"
-fromX: "{x}부터"
+fromX: "{x}ì—서"
genEmbedCode: "임베디드 코드 만들기"
noteOfThisUser: "ì´ ìœ ì €ì˜ ë…¸íŠ¸ 목ë¡"
clipNoteLimitExceeded: "ë” ì´ìƒ ì´ í´ë¦½ì— 노트를 추가 í•  수 없습니다."
@@ -1301,13 +1307,15 @@ lockdown: "잠금"
pleaseSelectAccount: "ê³„ì •ì„ ì„ íƒí•´ì£¼ì„¸ìš”."
availableRoles: "사용 가능한 역할"
acknowledgeNotesAndEnable: "활성화 하기 ì „ì— ì£¼ì˜ ì‚¬í•­ì„ í™•ì¸í–ˆìŠµë‹ˆë‹¤."
+federationSpecified: "ì´ ì„œë²„ëŠ” í™”ì´íЏ 리스트 ì œë„로 ìš´ì˜ ì¤‘ 입니다. ì •í•´ì§„ 리모트 서버가 아닌 경우 ì—°í•©ë˜ì§€ 않습니다."
+federationDisabled: "ì´ ì„œë²„ëŠ” ì—°í•©ì„ í•˜ì§€ 않고 있습니다. 리모트 서버 유저와 í†µì‹ ì„ í•  수 없습니다."
_accountSettings:
- requireSigninToViewContents: "콘í…츠 ì—´ëžŒì„ ìœ„í•´ 로그ì¸ìœ¼ 필수로 설정하기"
+ requireSigninToViewContents: "콘í…츠 ì—´ëžŒì„ ìœ„í•´ 로그ì¸ì„ 필수로 설정하기"
requireSigninToViewContentsDescription1: "ìžì‹ ì´ 작성한 모든 노트 ë“±ì˜ ì½˜í…츠를 보기 위해 로그ì¸ì„ 필수로 설정합니다. í¬ë¡¤ëŸ¬ê°€ ì •ë³´ 수집하는 ê²ƒì„ ë°©ì§€í•˜ëŠ” 효과를 기대할 수 있습니다."
requireSigninToViewContentsDescription2: "URL 미리보기(OGP), 웹페ì´ì§€ì— 삽입, 노트 ì¸ìš©ì„ ì§€ì›í•˜ì§€ 않는 서버ì—서 ë³¼ 수 없게 ë©ë‹ˆë‹¤."
requireSigninToViewContentsDescription3: "ì›ê²© ì„œë²„ì— ì—°í•©ëœ ì½˜í…츠ì—는 ì´ëŸ¬í•œ ì œí•œì´ ì ìš©ë˜ì§€ ì•Šì„ ìˆ˜ 있습니다."
makeNotesFollowersOnlyBefore: "과거 노트는 팔로워만 ë³¼ 수 있ë„ë¡ ì„¤ì •í•˜ê¸°"
- makeNotesFollowersOnlyBeforeDescription: "ì´ ê¸°ëŠ¥ì´ í™œì„±í™”ë˜ì–´ 있는 ë™ì•ˆ, ì„¤ì •ëœ ë‚ ì§œ ë° ì‹œê°„ë³´ë‹¤ 과거 ë˜ëŠ” ì„¤ì •ëœ ì‹œê°„ì´ ì§€ë‚œ 노트는 팔로워만 ë³¼ 수 있게 ë©ë‹ˆë‹¤.비활성화하면 ë…¸íŠ¸ì˜ ê³µê°œ ìƒíƒœë„ ì›ëž˜ëŒ€ë¡œ ëŒì•„갑니다."
+ makeNotesFollowersOnlyBeforeDescription: "ì´ ê¸°ëŠ¥ì´ í™œì„±í™”ë˜ì–´ 있는 ë™ì•ˆ, ì„¤ì •ëœ ë‚ ì§œ ë° ì‹œê°„ë³´ë‹¤ 과거 ë˜ëŠ” ì„¤ì •ëœ ì‹œê°„ì´ ì§€ë‚œ 노트는 팔로워만 ë³¼ 수 있게 ë©ë‹ˆë‹¤. 비활성화하면 ë…¸íŠ¸ì˜ ê³µê°œ ìƒíƒœë„ ì›ëž˜ëŒ€ë¡œ ëŒì•„갑니다."
makeNotesHiddenBefore: "과거 노트 비공개로 전환하기"
makeNotesHiddenBeforeDescription: "ì´ ê¸°ëŠ¥ì´ í™œì„±í™”ë˜ì–´ 있는 ë™ì•ˆ 설정한 ë‚ ì§œ ë° ì‹œê°„ë³´ë‹¤ 과거 ë˜ëŠ” 설정한 ì‹œê°„ì´ ì§€ë‚œ 노트는 본ì¸ë§Œ ë³¼ 수 있게(비공개로 전환) ë©ë‹ˆë‹¤. 비활성화하면 ë…¸íŠ¸ì˜ ê³µê°œ ìƒíƒœë„ ì›ëž˜ëŒ€ë¡œ ëŒì•„갑니다."
mayNotEffectForFederatedNotes: "ì›ê²© ì„œë²„ì— ì—°í•©ëœ ë…¸íŠ¸ì—는 효과가 ì—†ì„ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤."
@@ -2514,7 +2522,7 @@ _webhookSettings:
reaction: "누군가 ë‚´ ë…¸íŠ¸ì— ë¦¬ì•¡ì…˜í–ˆì„ ë•Œ"
mention: "누군가 나를 ë©˜ì…˜í–ˆì„ ë•Œ"
_systemEvents:
- abuseReport: "유저롭"
+ abuseReport: "유저로부터 신고를 ë°›ì•˜ì„ ë•Œ"
abuseReportResolved: "ë°›ì€ ì‹ ê³ ë¥¼ ì²˜ë¦¬í–ˆì„ ë•Œ"
userCreated: "유저가 ìƒì„±ë˜ì—ˆì„ 때"
inactiveModeratorsWarning: "모ë”ë ˆì´í„°ê°€ ì¼ì • 기간ë™ì•ˆ 활ë™í•˜ì§€ ì•Šì€ ê²½ìš°"
@@ -2721,6 +2729,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: "í•´ë”를 표시"
@@ -2744,3 +2812,34 @@ _selfXssPrevention:
_followRequest:
recieved: "ë°›ì€ ì‹ ì²­"
sent: "보낸 신청"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "ì´ ì„œë²„ì™€ 통신할 수 ì—†ìŒ"
+ description: "ì´ ì„œë²„ì™€ì˜ í†µì‹ ì´ ë¹„í™œì„±í™” ë˜ì—ˆê±°ë‚˜, ì´ ì„œë²„ë¥¼ 차단 중ì´ê±°ë‚˜ 서버ì—게 차단ë˜ì—ˆì„ 수 있습니다.\n서버 관리ìžì—게 문ì˜í•˜ì„¸ìš”."
+ _uriInvalid:
+ title: "URIê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤."
+ description: "입력한 URIì— ë¬¸ì œê°€ 있습니다. URIì— ì“¸ 수 없는 문ìžë¥¼ 넣었는지 확ì¸í•´ë³´ì„¸ìš”."
+ _requestFailed:
+ title: "ìš”ì²­ì„ ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤."
+ description: "해당 서버와 í†µì‹ ì„ ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤. ìƒëŒ€ë°© ì„œë²„ì— ì ‘ì† ë¶ˆê°€ëŠ¥í•œ ìƒíƒœì¼ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ë˜ëŠ” ìž˜ëª»ëœ URI ë˜ëŠ” 없는 URI를 입력했는지 확ì¸í•´ë³´ì„¸ìš”."
+ _responseInvalid:
+ title: "유효하지 ì•Šì€ ë°˜ì‘입니다."
+ description: "ì´ ì„œë²„ì™€ 통신할 수 있지만, ë°ì´í„°ê°€ 올바르지 않습니다."
+ _responseInvalidIdHostNotMatch:
+ description: "ìž…ë ¥ëœ URIê³¼ 실제 URIê°€ 다릅니다. ì œ 3ìž ì„œë²„ë¥¼ 통한 리모트 컨í…츠를 조회하는 경우, ì›ëž˜ 서버 측ì—서 받아올 수 있는 URI를 사용하여 조회하시길 ë°”ëžë‹ˆë‹¤."
+ _noSuchObject:
+ title: "ì°¾ì„ ìˆ˜ 없습니다"
+ description: "ìš”êµ¬ëœ ë¦¬ì†ŒìŠ¤ë¥¼ ì°¾ì„ ìˆ˜ 없습니다. URI를 다시 한 번 확ì¸í•´ë³´ì„¸ìš”."
+_captcha:
+ verify: "CAPTCHA를 먼저 해결하세요."
+ testSiteKeyMessage: "사ì´íЏ 키와 비밀 í‚¤ì— í…ŒìŠ¤íŠ¸ìš© ê°’ì„ ìž…ë ¥í•˜ì—¬ 미리보기를 확ì¸í•  수 있습니다.\nìžì„¸í•œ ë‚´ìš©ì€ ì•„ëž˜ 페ì´ì§€ë¥¼ 확ì¸í•´ë³´ì„¸ìš”."
+ _error:
+ _requestFailed:
+ title: "CAPTCHA ìš”êµ¬ì— ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤."
+ text: "잠시 í›„ì— ë‹¤ì‹œ 실행하거나, ì„¤ì •ì„ ë‹¤ì‹œ 한 번 확ì¸í•´ë³´ì„¸ìš”."
+ _verificationFailed:
+ title: "CAPTCHA ê²€ì¦ì„ 실패했습니다."
+ text: "ì„¤ì •ì´ ì˜¬ë°”ë¥¸ì§€ 다시 한 번 확ì¸í•´ë³´ì„¸ìš”."
+ _unknown:
+ title: "CAPTCHA ì—러"
+ text: "알 수 없는 ì—러가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."
diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml
index 38965119fe..2d55c289aa 100644
--- a/locales/lo-LA.yml
+++ b/locales/lo-LA.yml
@@ -474,3 +474,6 @@ _abuseReport:
mail: "ອີເມວ"
_moderationLogTypes:
suspend: "ລະງັບ"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "ບà»à»ˆàºžàº»àºš"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index 7e5e9cbbfb..685094b4a5 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -8,6 +8,9 @@ search: "Zoeken"
notifications: "Meldingen"
username: "Gebruikersnaam"
password: "Wachtwoord"
+initialPasswordForSetup: "Initiële wachtwoord voor configuratie"
+initialPasswordIsIncorrect: "Initiële wachtwoord voor configuratie is onjuist"
+initialPasswordForSetupDescription: "Gebruik het initiële wachtwoord uit de configuratie, als je Misskey zelf hebt geïnstalleerd.\nAls je een Misskey hosting provider gebruikt, gebruik dan het gegeven wachtwoord.\nAls je geen wachtwoord hebt gezet, laat het dan leeg om verder te gaan."
forgotPassword: "Wachtwoord vergeten"
fetchingAsApObject: "Ophalen vanuit de Fediverse"
ok: "Ok"
@@ -108,9 +111,12 @@ enterEmoji: "Voer een emoji in"
renote: "Herdelen"
unrenote: "Stop herdelen"
renoted: "Herdeeld"
+renotedToX: "Renoted naar {name}"
cantRenote: "Dit bericht kan niet worden herdeeld"
cantReRenote: "Een herdeling kan niet worden herdeeld"
quote: "Quote"
+renoteToChannel: "Renote naar kanaal"
+renoteToOtherChannel: "Renote naar ander kanaal"
pinnedNote: "Vastgemaakte notitie"
pinned: "Vastmaken aan profielpagina"
you: "Jij"
@@ -119,6 +125,10 @@ sensitive: "NSFW"
add: "Toevoegen"
reaction: "Reacties"
reactions: "Reacties"
+emojiPicker: "Emoji kiezer"
+pinnedEmojisForReactionSettingDescription: "Kies de emojis die als eerste getoond worden tijdens het reageren"
+pinnedEmojisSettingDescription: "Kies de emojis die als eerste getoond worden tijdens het reageren"
+emojiPickerDisplay: "Emoji kiezer weergave"
reactionSettingDescription2: "Sleep om opnieuw te ordenen, Klik om te verwijderen, Druk op \"+\" om toe te voegen"
rememberNoteVisibility: "Vergeet niet de notitie zichtbaarheidsinstellingen"
attachCancel: "Verwijder bijlage"
@@ -140,7 +150,7 @@ selectAntenna: "Kies een antenne"
selectWidget: "Kies een widget"
editWidgets: "Bewerk widgets"
editWidgetsExit: "Klaar"
-customEmojis: "Maatwerk emoji"
+customEmojis: "Eigen emoji"
emoji: "Emoji"
emojis: "Emoji"
emojiName: "Naam emoji"
@@ -403,7 +413,31 @@ help: "Help"
inputMessageHere: "Voer hier je bericht in"
close: "Sluiten"
invites: "Uitnodigen"
+members: "Leden"
+transfer: "Overdracht"
+title: "Titel"
+text: "Tekst"
+enable: "Inschakelen"
+next: "Volgende"
+retype: "Opnieuw invoeren"
+noteOf: "Notitie van {user}"
+quoteAttached: "Citaat"
+quoteQuestion: "Toevoegen als citaat?"
invitations: "Uitnodigen"
+dashboard: "Overzicht"
+local: "Lokaal"
+remote: "Remote"
+total: "Totaal"
+weekOverWeekChanges: "Wijzigingen sinds vorige week"
+dayOverDayChanges: "Dagelijkse wijzigingen"
+appearance: "Weergave"
+clientSettings: "Clientinstellingen"
+accountSettings: "Accountinstellingen"
+promotion: "Promotie"
+promote: "Promoot"
+numberOfDays: "Aantal dagen"
+hideThisNote: "Verberg deze notitie"
+showFeaturedNotesInTimeline: "Laat featured notities in tijdlijn zien"
sound: "Geluid"
smtpHost: "Server"
smtpUser: "Gebruikersnaam"
@@ -501,3 +535,8 @@ _webhookSettings:
_moderationLogTypes:
suspend: "Opschorten"
resetPassword: "Wachtwoord terugzetten"
+_reversi:
+ total: "Totaal"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Niet gevonden"
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index 87ea01764d..474e05ba67 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -727,3 +727,6 @@ _abuseReport:
mail: "E-post"
_moderationLogTypes:
suspend: "Suspender"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Ikke funnet"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 203f44b334..98465ea82b 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -1583,3 +1583,6 @@ _moderationLogTypes:
resetPassword: "Zresetuj hasło"
_reversi:
total: "ÅÄ…cznie"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Nie znaleziono"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 7ef9e3a946..aae63805c3 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -8,6 +8,9 @@ search: "Pesquisar"
notifications: "Notificações"
username: "Nome de usuário"
password: "Senha"
+initialPasswordForSetup: "Senha para a configuração inicial"
+initialPasswordIsIncorrect: "Senha para configuração inicial está incorreta"
+initialPasswordForSetupDescription: "Use a senha configurada no arquivo de configuração se você instalou o Misskey manualmente.\nSe você estiver utilizando um serviço de hospedagem, utilize a senha fornecida.\nSe uma senha não foi configurada, deixe em branco e continue."
forgotPassword: "Esqueci-me da senha"
fetchingAsApObject: "Buscando no Fediverso..."
ok: "OK"
@@ -196,7 +199,7 @@ followConfirm: "Tem certeza que quer seguir {name}?"
proxyAccount: "Conta proxy"
proxyAccountDescription: "Uma conta de proxy é uma conta que assume o acompanhamento remoto de um usuário sob certas condições específicas. Por exemplo, quando um usuário inclui um usuário remoto em uma lista, mas ninguém na lista está seguindo o usuário remoto, a atividade não é entregue ao servidor. Nesse caso, a conta de proxy entra em ação para seguir o usuário remoto em vez disso."
host: "Host"
-selectSelf: "Escolher manualmente"
+selectSelf: "Selecionar a mim"
selectUser: "Selecionar usuário"
recipient: "Destinatário"
annotation: "Anotação"
@@ -236,6 +239,8 @@ silencedInstances: "Instâncias silenciadas"
silencedInstancesDescription: "Liste o nome de hospedagem dos servidores que você deseja silenciar, separados por linha. Todas as contas desses servidores serão silenciada e poderão enviar solicitações para seguir, mas não poderão mencionar usuários locais sem segui-los. Isso não afetará servidores bloqueados."
mediaSilencedInstances: "Instâncias com mídia silenciadas"
mediaSilencedInstancesDescription: "Liste o nome de hospedagem dos servidores cuja mídia você deseja silenciar, separados por linha. Todas as contas desses servidores serão consideradas sensíveis e não poderão utilizar emojis personalizados. Isso não afetará servidores bloqueados."
+federationAllowedHosts: "Servidores com federação permitida"
+federationAllowedHostsDescription: "Especifique o endereço dos servidores em que deseja permitir a federação separados por linha."
muteAndBlock: "Silenciar e bloquear"
mutedUsers: "Usuários silenciados"
blockedUsers: "Usuários bloqueados"
@@ -334,6 +339,7 @@ renameFolder: "Renomear Pasta"
deleteFolder: "Excluir pasta"
folder: "Pasta"
addFile: "Adicionar arquivo"
+showFile: "Mostrar arquivos"
emptyDrive: "O drive está vazio"
emptyFolder: "A pasta está vazia"
unableToDelete: "Não é possível excluir"
@@ -447,6 +453,7 @@ totpDescription: "Digite a senha de uso único informado pelo aplicativo autenti
moderator: "Moderador"
moderation: "Moderação"
moderationNote: "Nota de moderação"
+moderationNoteDescription: "Você pode preencher notas que serão compartilhadas apenas com moderadores."
addModerationNote: "Adicionar nota de moderação"
moderationLogs: "Logs de moderação"
nUsersMentioned: "Postado por {n} pessoas"
@@ -508,6 +515,10 @@ uiLanguage: "Idioma de exibição da interface "
aboutX: "Sobre {x}"
emojiStyle: "Estilo de emojis"
native: "Nativo"
+menuStyle: "Estilo do menu"
+style: "Estilo"
+drawer: "Gaveta"
+popup: "Pop-up"
showNoteActionsOnlyHover: "Exibir as ações da nota somente ao passar o cursor sobre ela"
showReactionsCount: "Ver o número de reações nas notas"
noHistory: "Ainda não há histórico"
@@ -575,6 +586,7 @@ masterVolume: "volume principal"
notUseSound: "Desabilitar som"
useSoundOnlyWhenActive: "Apenas reproduzir sons quando Misskey estiver aberto."
details: "Detalhes"
+renoteDetails: "Detalhes da repostagem"
chooseEmoji: "Selecione um emoji"
unableToProcess: "Não é possível concluir a operação"
recentUsed: "Usado recentemente"
@@ -590,6 +602,8 @@ ascendingOrder: "Ascendente"
descendingOrder: "Descendente"
scratchpad: "Bloco de rascunho"
scratchpadDescription: "O Bloco de rascunho fornece um ambiente experimental para AiScript. Permite escrever, executar e verificar os resultados do código para interagir com o Misskey."
+uiInspector: "Inspecionador de interface"
+uiInspectorDescription: "Você pode ver a lista de servidores de componentes de interface na memória. Componentes da interface serão gerados pela função Ui:C:."
output: "Resultado"
script: "Script"
disablePagesScript: "Desabilitar scripts nas páginas"
@@ -670,7 +684,7 @@ smtpSecure: "Use SSL/TLS implícito para conexões SMTP"
smtpSecureInfo: "Desative esta opção ao utilizar STARTTLS."
testEmail: "Testar envio de e-mail"
wordMute: "Silenciar palavras"
-hardWordMute: "SIlenciamento pesado de palavra"
+hardWordMute: "Silenciar palavras (esconder posts)"
regexpError: "Erro na expressão regular"
regexpErrorDescription: "Ocorreu um erro na expressão regular na linha {line} da palavra mutada {tab}:"
instanceMute: "Instâncias silenciadas"
@@ -908,6 +922,7 @@ followersVisibility: "Visibilidade dos seguidores"
continueThread: "Ver mais desta conversa"
deleteAccountConfirm: "Deseja realmente excluir a conta?"
incorrectPassword: "Senha inválida."
+incorrectTotp: "A senha de uso único está incorreta ou expirou."
voteConfirm: "Deseja confirmar o seu voto em \"{choice}\"?"
hide: "Ocultar"
useDrawerReactionPickerForMobile: "Mostrar em formato de gaveta"
@@ -932,6 +947,9 @@ oneHour: "1 hora"
oneDay: "1 dia"
oneWeek: "1 semana"
oneMonth: "1 mês"
+threeMonths: "3 meses"
+oneYear: "1 ano"
+threeDays: "3 dias"
reflectMayTakeTime: "As mudanças podem demorar a aparecer."
failedToFetchAccountInformation: "Não foi possível obter informações da conta"
rateLimitExceeded: "Taxa limite excedido"
@@ -1072,6 +1090,7 @@ retryAllQueuesConfirmTitle: "Gostaria de tentar novamente agora?"
retryAllQueuesConfirmText: "Isso irá temporariamente aumentar a carga do servidor."
enableChartsForRemoteUser: "Gerar gráficos estatísticos de usuários remotos"
enableChartsForFederatedInstances: "Gerar gráficos estatísticos de instâncias remotas"
+enableStatsForFederatedInstances: "Receber estatísticas de servidores remotos"
showClipButtonInNoteFooter: "Adicionar \"Clip\" ao menu de ação de notas"
reactionsDisplaySize: "Tamanho de exibição das reações"
limitWidthOfReaction: "Limita o comprimento máximo de reações e as exibe em tamanho reduzido"
@@ -1258,7 +1277,49 @@ confirmWhenRevealingSensitiveMedia: "Confirmar ao revelar mídia sensível"
sensitiveMediaRevealConfirm: "Essa mídia pode ser sensível. Deseja revelá-la?"
createdLists: "Listas criadas"
createdAntennas: "Antenas criadas"
+fromX: "De {x}"
+genEmbedCode: "Gerar código de embed"
+noteOfThisUser: "Notas por este usuário"
clipNoteLimitExceeded: "Não é possível adicionar mais notas ao clipe."
+performance: "Desempenho"
+modified: "Modificado"
+discard: "Descartar"
+thereAreNChanges: "Há {n} mudança(s)"
+signinWithPasskey: "Entrar com Passkey"
+unknownWebAuthnKey: "Passkey desconhecida"
+passkeyVerificationFailed: "A verificação com Passkey falhou."
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "A verificação com Passkey teve êxito, mas a entrada sem senha está desabilitada."
+messageToFollower: "Mensagem aos seguidores"
+target: "Alvo"
+testCaptchaWarning: "Essa função é utilizada apenas para testar CAPTCHA. <strong>Não a use num ambiente de produção.</strong>"
+prohibitedWordsForNameOfUser: "Palavras proibidas para nomes de usuário"
+prohibitedWordsForNameOfUserDescription: "Se quaisquer palavras dessa lista forem incluídas no nome de usuário, seu uso será negado. Usuários com privilégios de moderador não serão afetados pela restrição."
+yourNameContainsProhibitedWords: "O seu nome possui palavras proibidas"
+yourNameContainsProhibitedWordsDescription: "Se você deseja utilizar esse nome, entre em contato com o administrador do servidor."
+thisContentsAreMarkedAsSigninRequiredByAuthor: "O autor exige que você esteja cadastrado para ver"
+lockdown: "Lockdown"
+pleaseSelectAccount: "Selecione uma conta"
+availableRoles: "Cargos disponíveis"
+acknowledgeNotesAndEnable: "Ative após compreender as precauções."
+_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."
+ requireSigninToViewContentsDescription2: "Conteúdo não será exibido nas prévias de URL (OGP), incorporado em outras páginas web ou em servidores que não têm suporte a citações."
+ requireSigninToViewContentsDescription3: "Essas restrições podem não ser aplicadas a conteúdo federado de outros servidores."
+ makeNotesFollowersOnlyBefore: "Tornar notas passadas visíveis apenas para seguidores."
+ makeNotesFollowersOnlyBeforeDescription: "Com essa função ativada, apenas seguidores podem ver as notas anteriores à data e hora marcadas. Se isso for desativado, o status de publicação da nota será reestabelecido."
+ makeNotesHiddenBefore: "Tornar notas passadas privadas"
+ makeNotesHiddenBeforeDescription: "Com essa função ativada, apenas você poderá ver as notas anteriores à data e hora marcadas. Se isso for desativado, o status de publicação da nota será reestabelecido."
+ mayNotEffectForFederatedNotes: "Notas federadas a servidores remotos podem não ser afetadas."
+ notesHavePassedSpecifiedPeriod: "Notas que duraram um tempo específico."
+ notesOlderThanSpecifiedDateAndTime: "Notas antes do tempo específico."
+_abuseUserReport:
+ forward: "Encaminhar"
+ forwardDescription: "Encaminhar a denúncia ao servidor remoto como uma conta anônima do sistema."
+ resolve: "Resolver"
+ accept: "Aceitar"
+ reject: "Rejeitar"
+ resolveTutorial: "Se a denúncia for legítima em conteúdo, selecione \"Aceitar\" para marcar o caso como resolvido afirmativamente.\nSe a denúncia for ilegítima em conteúdo, selecione \"Rejeitar\" para marcar o caso como resolvido negativamente."
_delivery:
status: "Estado de entrega"
stop: "Suspenso"
@@ -1393,8 +1454,12 @@ _serverSettings:
fanoutTimelineDescription: "Melhora significativamente a performance do retorno da linha do tempo e reduz o impacto no banco de dados quando habilitado. Em contrapartida, o uso de memória do Redis aumentará. Considere desabilitar em casos de baixa disponibilidade de memória ou instabilidade do servidor."
fanoutTimelineDbFallback: "\"Fallback\" ao banco de dados"
fanoutTimelineDbFallbackDescription: "Quando habilitado, a linha do tempo irá recuar ao banco de dados caso consultas adicionais sejam feitas e ela não estiver em cache. Quando desabilitado, o impacto no servidor será reduzido ao eliminar o recuo, mas limita a quantidade de linhas do tempo que podem ser recebidas."
+ reactionsBufferingDescription: "Quando ativado, o desempenho durante a criação de uma reação será melhorado substancialmente, reduzindo a carga do banco de dados. Porém, a o uso de memória do Redis irá aumentar."
inquiryUrl: "URL de inquérito"
inquiryUrlDescription: "Especifique um URL para um formulário de inquérito para a administração ou uma página web com informações de contato."
+ openRegistration: "Abrir a criação de contas"
+ openRegistrationWarning: "Abrir cadastros contém riscos. É recomendado apenas habilitá-los se houver um sistema de monitoramento contínuo e resolução imediata de problemas."
+ thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Se nenhuma atividade da moderação for detectada por um tempo, essa configuração será desativada para prevenir spam."
_accountMigration:
moveFrom: "Migrar outra conta para essa"
moveFromSub: "Criar um 'alias' a outra conta"
@@ -1726,6 +1791,11 @@ _role:
canSearchNotes: "Permitir a busca de notas"
canUseTranslator: "Uso do tradutor"
avatarDecorationLimit: "Número máximo de decorações de avatar que podem ser aplicadas"
+ canImportAntennas: "Permitir importação de antenas"
+ canImportBlocking: "Permitir importação de bloqueios"
+ canImportFollowing: "Permitir importação de usuários seguidos"
+ canImportMuting: "Permitir importação de silenciamentos"
+ canImportUserLists: "Permitir importação de listas"
_condition:
roleAssignedTo: "Atribuído a cargos manuais"
isLocal: "Usuário local"
@@ -2109,8 +2179,11 @@ _auth:
permissionAsk: "O aplicativo solicita as seguintes permissões"
pleaseGoBack: "Por favor, volte ao aplicativo"
callback: "Retornando ao aplicativo"
+ accepted: "Acesso permitido"
denied: "Acesso negado"
+ scopeUser: "Operar como o usuário a seguir"
pleaseLogin: "Por favor, entre para autorizar aplicativos."
+ byClickingYouWillBeRedirectedToThisUrl: "Quando o acesso for permitido, você será redirecionado para o seguinte endereço"
_antennaSources:
all: "Todas as notas"
homeTimeline: "Notas de usuários seguidos"
@@ -2219,6 +2292,9 @@ _profile:
changeBanner: "Mudar banner"
verifiedLinkDescription: "Ao inserir um URL que contém um link para essa conta, um ícone de verificação será exibido ao lado do campo"
avatarDecorationMax: "Você pode adicionar até {max} decorações."
+ followedMessage: "Mensagem exibida quando alguém segue você"
+ followedMessageDescription: "Você pode definir uma curta mensagem que será exibida aos usuários que seguirem você."
+ followedMessageDescriptionForLockedAccount: "Se você aceita pedidos de seguidor manualmente, isso será exibido quando você aceitá-los."
_exportOrImport:
allNotes: "Todas as notas"
favoritedNotes: "Notas nos favoritos"
@@ -2357,6 +2433,8 @@ _notification:
renotedBySomeUsers: "{n} usuários repostaram a nota"
followedBySomeUsers: "{n} usuários te seguiram"
flushNotification: "Limpar notificações"
+ exportOfXCompleted: "Exportação de {x} foi concluída"
+ login: "Alguém entrou na conta"
_types:
all: "Todas"
note: "Novas notas"
@@ -2371,7 +2449,9 @@ _notification:
followRequestAccepted: "Aceitou pedidos de seguidor"
roleAssigned: "Cargo dado"
achievementEarned: "Conquista desbloqueada"
+ exportCompleted: "A exportação foi concluída"
login: "Iniciar sessão"
+ test: "Notificação teste"
app: "Notificações de aplicativos conectados"
_actions:
followBack: "te seguiu de volta"
@@ -2437,7 +2517,10 @@ _webhookSettings:
abuseReport: "Quando receber um relatório de abuso"
abuseReportResolved: "Quando relatórios de abuso forem resolvidos "
userCreated: "Quando um usuário é criado"
+ inactiveModeratorsWarning: "Quando moderadores estiverem inativos por um tempo"
+ inactiveModeratorsInvitationOnlyChanged: "Quando um moderador está inativo por um tempo e os cadastros passam a exigir convites"
deleteConfirm: "Você tem certeza de que deseja excluir o Webhook?"
+ testRemarks: "Clique no botão à direita do interruptor para enviar um Webhook de teste com dados fictícios."
_abuseReport:
_notificationRecipient:
createRecipient: "Adicionar destinatário para relatórios de abuso"
@@ -2481,6 +2564,8 @@ _moderationLogTypes:
markSensitiveDriveFile: "Arquivo marcado como sensível"
unmarkSensitiveDriveFile: "Arquivo desmarcado como sensível"
resolveAbuseReport: "Relatório resolvido"
+ forwardAbuseReport: "Denúncia encaminhada"
+ updateAbuseReportNote: "Nota de moderação da denúncia atualizada"
createInvitation: "Convite gerado"
createAd: "Propaganda criada"
deleteAd: "Propaganda excluída"
@@ -2636,3 +2721,44 @@ _contextMenu:
app: "Aplicativo"
appWithShift: "Aplicativo com a tecla shift"
native: "Nativo"
+_embedCodeGen:
+ title: "Personalizar código do embed"
+ header: "Exibir cabeçalho"
+ autoload: "Carregar mais automaticamente (obsoleto)"
+ maxHeight: "Altura máxima"
+ maxHeightDescription: "Colocar em 0 desabilita a altura máxima. Especifique um valor para prevenir uma expansão vertical contínua."
+ maxHeightWarn: "O limite de altura máxima está desabilitado (0). Se isso não for intencional, insira um valor para a altura máxima."
+ previewIsNotActual: "A exibição difere do embed original porque ela excede o tamanho da tela de prévia."
+ rounded: "Tornar arredondado"
+ border: "Adicionar uma borda ao quadro externo"
+ applyToPreview: "Aplicar para a prévia"
+ generateCode: "Gerar código de embed"
+ codeGenerated: "O código foi gerado"
+ codeGeneratedDescription: "Coloque o código no seu website para incorporar o conteúdo."
+_selfXssPrevention:
+ warning: "AVISO"
+ title: "\"Cole algo nessa tela\" é uma fraude"
+ description1: "Se você colar algo aqui, um usuário malicioso pode sabotar a sua conta ou roubar informações pessoais."
+ description2: "Se você não entender exatamente o que está colando, %cpare agora e feche essa janela."
+ description3: "Para mais informação, clique no link. {link}"
+_followRequest:
+ recieved: "Aplicação recebida"
+ sent: "Aplicação enviada"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "Não foi possível se comunicar com o servidor"
+ description: "Comunicação com esse servidor pode ter sido desabilitada ou o servidor pode ter sido bloqueado.\nPor favor, entre em contato com o administrador do servidor."
+ _uriInvalid:
+ title: "Endereço inválido"
+ description: "Há um problema com o endereço inserido. Por favor, confira se você não inseriu caracteres inválidos."
+ _requestFailed:
+ title: "Solicitação falhou"
+ description: "Comunicação com esse servidor falhou. O servidor pode estar inativo. Além disso, confira se você não inseriu um endereço inválido ou inexistente."
+ _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."
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 71dc1dc94c..07f4c98d96 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -733,3 +733,6 @@ _moderationLogTypes:
resetPassword: "Resetează parola"
_reversi:
total: "Total"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Nu a fost găsit"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 537e99036c..bc1b12895c 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -18,7 +18,7 @@ gotIt: "ЯÑно!"
cancel: "Отмена"
noThankYou: "Ðет, ÑпаÑибо"
enterUsername: "Введите Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ"
-renotedBy: "{user} репоÑтнул(а)"
+renotedBy: "{user} делает репоÑÑ‚"
noNotes: "Ðет ни одной заметки"
noNotifications: "Ðет уведомлений"
instance: "ЭкземплÑÑ€"
@@ -1063,7 +1063,7 @@ hiddenTags: "Скрытые хештеги"
notesSearchNotAvailable: "ПоиÑк заметок недоÑтупен"
license: "ЛицензиÑ"
unfavoriteConfirm: "Удалить избранное?"
-myClips: "Мои клипы"
+myClips: "Мои подборки"
drivecleaner: "ОчиÑтитель диÑков"
retryAllQueuesNow: "Повторить вÑе очереди ÑейчаÑ"
retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?"
@@ -1105,16 +1105,18 @@ preservedUsernames: "Зарезервированные имена пользоÐ
preservedUsernamesDescription: "ПеречиÑлите зарезервированные имена пользователей, отделÑÑ Ð¸Ñ… Ñтроками. Они Ñтанут недоÑтупны при Ñоздании учётной запиÑи. Это ограничение не применÑетÑÑ Ð¿Ñ€Ð¸ Ñоздании учётной запиÑи админиÑтраторами. Также, уже ÑущеÑтвующие учётные запиÑи оÑтанутÑÑ Ð±ÐµÐ· изменений."
createNoteFromTheFile: "Создать заметку из Ñтого файла"
archive: "Ðрхив"
+unarchive: "Разархивировать"
channelArchiveConfirmTitle: "ПеремеÑтить {name} в архив?"
channelArchiveConfirmDescription: "Ðрхивированные каналы переÑтанут отображатьÑÑ Ð² ÑпиÑке каналов или результатах поиÑка. Ð’ них также Ð½ÐµÐ»ÑŒÐ·Ñ Ð±ÑƒÐ´ÐµÑ‚ добавлÑть новые запиÑи."
thisChannelArchived: "Этот канал находитÑÑ Ð² архиве."
displayOfNote: "Отображение заметок"
initialAccountSetting: "ÐаÑтройка профилÑ"
-youFollowing: "ПодпиÑки"
+youFollowing: "Ð’Ñ‹ подпиÑаны"
preventAiLearning: "ОтказатьÑÑ Ð¾Ñ‚ иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² машинном обучении (Генеративный ИИ)"
preventAiLearningDescription: "ЗапроÑить краулеров не иÑпользовать опубликованный текÑÑ‚ или Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸ Ñ‚.д. Ð´Ð»Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð½Ð¾Ð³Ð¾ Ð¾Ð±ÑƒÑ‡ÐµÐ½Ð¸Ñ (Прогнозирующий / Генеративный ИИ) датаÑетов. Это доÑтигаетÑÑ Ð¿ÑƒÑ‚Ñ‘Ð¼ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ \"noai\" HTTP-заголовка в ответ на ÑоответÑтвующий контент. Полного Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñ‡ÐµÑ€ÐµÐ· Ñтот заголовок не избежать, так как он может быть проÑто проигнорирован."
options: "ÐаÑтройки ролей"
specifyUser: "Указанный пользователь"
+lookupConfirm: "Хотите узнать?"
openTagPageConfirm: "Открыть Ñтраницу Ñтого хештега?"
specifyHost: "Указать Ñайт"
failedToPreviewUrl: "Предварительный проÑмотр недоÑтупен"
@@ -1178,6 +1180,7 @@ keepOriginalFilename: "СохранÑть иÑходное Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°"
keepOriginalFilenameDescription: "ЕÑли вы выключите данную наÑтройку, имена файлов будут автоматичеÑки заменены Ñлучайной Ñтрокой при загрузке."
alwaysConfirmFollow: "Ð’Ñегда подтверждать подпиÑку"
inquiry: "СвÑзатьÑÑ"
+messageToFollower: "Сообщение подпиÑчикам"
_delivery:
stop: "Заморожено"
_type:
@@ -1504,6 +1507,7 @@ _role:
rateLimitFactor: "Ограничение активноÑти"
descriptionOfRateLimitFactor: "Меньшее значение — Ñлабые ограничениÑ, большее — Ñильные"
canHideAds: "Может Ñкрыть рекламу"
+ canImportFollowing: "Можно импортировать подпиÑчиков"
_condition:
isLocal: "МеÑтный"
isRemote: "ÐемеÑтный"
@@ -2143,3 +2147,6 @@ _hemisphere:
caption: "ИÑпользуетÑÑ Ð´Ð»Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… наÑтроек клиента Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ñезона."
_reversi:
total: "Ð’Ñего"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Ðе найдено"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index f3f43ee6a6..715ff4c847 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -1449,3 +1449,6 @@ _moderationLogTypes:
resetPassword: "Resetovať heslo"
_reversi:
total: "Celkom"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Nenájdené"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index c725282d50..8e68d6cf49 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -2709,3 +2709,6 @@ _embedCodeGen:
generateCode: "สร้างโค้ดสำหรับà¸à¸²à¸£à¸à¸±à¸‡"
codeGenerated: "รหัสถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‚ึ้นà¹à¸¥à¹‰à¸§"
codeGeneratedDescription: "นำโค้ดที่สร้างà¹à¸¥à¹‰à¸§à¹„ปวางในเว็บไซต์ของคุณเพื่อà¸à¸±à¸‡à¹€à¸™à¸·à¹‰à¸­à¸«à¸²"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "ไม่พบหน้าที่ต้องà¸à¸²à¸£"
diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml
index 69892fedc8..2c63f15aa2 100644
--- a/locales/tr-TR.yml
+++ b/locales/tr-TR.yml
@@ -8,6 +8,7 @@ search: "Arama"
notifications: "Bildirim"
username: "Kullanıcı Adı"
password: "Åžifre"
+initialPasswordForSetup: ""
forgotPassword: "ÅŸifremi unuttum"
fetchingAsApObject: "從è¯é‚¦å®‡å®™å–得中..."
ok: "TAMAM"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 1b21854650..6e3e0bb9da 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -1624,3 +1624,6 @@ _moderationLogTypes:
resetPassword: "Скинути пароль"
_reversi:
total: "Ð’Ñього"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Ðе знайдено"
diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml
index 051a4ae6c5..2116d2b86f 100644
--- a/locales/uz-UZ.yml
+++ b/locales/uz-UZ.yml
@@ -1094,3 +1094,6 @@ _moderationLogTypes:
resetPassword: "Parolni tiklash"
_reversi:
total: "Jami"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Topilmadi"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index 24faa4b94c..cded29fdba 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -1930,3 +1930,6 @@ _moderationLogTypes:
createInvitation: "Tạo lá»i má»i"
_reversi:
total: "Tổng cộng"
+_remoteLookupErrors:
+ _noSuchObject:
+ title: "Không tìm thấy"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index e6232070d7..1a14f0bf76 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.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: "删除并编辑"
@@ -142,15 +144,15 @@ markAsSensitive: "æ ‡è®°ä¸ºæ•æ„Ÿå†…容"
unmarkAsSensitive: "å–æ¶ˆæ ‡è®°ä¸ºæ•感内容"
enterFileName: "输入文件å"
mute: "å±è”½"
-unmute: "解除é™éŸ³"
+unmute: "å–æ¶ˆéšè—"
renoteMute: "éšè—转帖"
renoteUnmute: "解除éšè—转帖"
-block: "拉黑"
-unblock: "å–æ¶ˆæ‹‰é»‘"
+block: "å±è”½"
+unblock: "å–æ¶ˆå±è”½"
suspend: "冻结"
unsuspend: "解除冻结"
-blockConfirm: "ç¡®å®šè¦æ‹‰é»‘å—?"
-unblockConfirm: "确定è¦è§£é™¤æ‹‰é»‘å—?"
+blockConfirm: "确定è¦å±è”½å—?"
+unblockConfirm: "确定è¦å–消å±è”½å—?"
suspendConfirm: "è¦å†»ç»“å—?"
unsuspendConfirm: "è¦è§£é™¤å†»ç»“å—?"
selectList: "选择列表"
@@ -195,7 +197,7 @@ setWallpaper: "设置å£çº¸"
removeWallpaper: "移除å£çº¸"
searchWith: "æœç´¢:{q}"
youHaveNoLists: "列表为空"
-followConfirm: "你确定è¦å…³æ³¨ {name} å—?"
+followConfirm: "确定è¦å…³æ³¨ {name} å—?"
proxyAccount: "代ç†è´¦æˆ·"
proxyAccountDescription: "代ç†è´¦æˆ·æ˜¯åœ¨æŸäº›æƒ…况下替代用户进行远程关注用的账户。 例如说,当用户将一ä½è¿œç¨‹ç”¨æˆ·æ”¾å…¥ä¸€ä¸ªåˆ—表中时,如果本地æœåŠ¡å™¨ä¸Šæ²¡æœ‰ä»»ä½•äººå…³æ³¨è¿™ä½è¿œç¨‹ç”¨æˆ·ï¼Œåˆ™è¿™ä½è¿œç¨‹ç”¨æˆ·çš„账户活动将ä¸ä¼šè¢«é€åˆ°æœ¬åœ°æœåŠ¡å™¨ä¸Šã€‚ä½œä¸ºæ›¿ä»£ï¼Œæ­¤æ—¶å°†ä½¿ç”¨ä»£ç†è´¦æˆ·è¿›è¡Œå…³æ³¨ã€‚"
host: "主机å"
@@ -229,10 +231,10 @@ disk: "存储"
instanceInfo: "æœåŠ¡å™¨ä¿¡æ¯"
statistics: "统计"
clearQueue: "清除队列"
-clearQueueConfirmTitle: "确定清除队列?"
+clearQueueConfirmTitle: "ç¡®å®šè¦æ¸…除队列å—?"
clearQueueConfirmText: "未é€è¾¾çš„帖å­å°†ä¸ä¼šè¢«æŠ•递。 通常无需执行此æ“作。"
clearCachedFiles: "清除缓存"
-clearCachedFilesConfirm: "ç¡®å®šè¦æ¸…除所有缓存的远程文件?"
+clearCachedFilesConfirm: "ç¡®å®šè¦æ¸…除所有缓存的远程文件å—?"
blockedInstances: "被å±è”½çš„æœåŠ¡å™¨"
blockedInstancesDescription: "设定è¦å±è”½çš„æœåŠ¡å™¨ï¼Œä»¥æ¢è¡Œåˆ†éš”。被å±è”½çš„æœåŠ¡å™¨å°†æ— æ³•ä¸Žæœ¬æœåŠ¡å™¨è¿›è¡Œäº¤æ¢é€šè®¯ã€‚å­åŸŸåä¹ŸåŒæ ·ä¼šè¢«å±è”½ã€‚"
silencedInstances: "被é™éŸ³çš„æœåŠ¡å™¨"
@@ -246,7 +248,7 @@ mutedUsers: "å·²éšè—用户"
blockedUsers: "å·²å±è”½çš„用户"
noUsers: "无用户"
editProfile: "编辑资料"
-noteDeleteConfirm: "è¦åˆ é™¤è¯¥å¸–å­å—?"
+noteDeleteConfirm: "确定è¦åˆ é™¤è¯¥å¸–å­å—?"
pinLimitExceeded: "无法置顶更多了"
intro: "Misskey 的部署结æŸå•¦ï¼åˆ›å»ºç®¡ç†å‘˜è´¦å·å§ï¼"
done: "完æˆ"
@@ -257,7 +259,7 @@ defaultValueIs: "默认值: {value}"
noCustomEmojis: "没有自定义表情符å·"
noJobs: "没有任务"
federating: "è”åˆä¸­"
-blocked: "已拉黑"
+blocked: "å·²å±è”½"
suspended: "åœæ­¢æŠ•递"
all: "全部"
subscribing: "已订阅"
@@ -566,7 +568,7 @@ objectStorageRegionDesc: "指定一个å¯ç”¨åŒºï¼Œä¾‹å¦‚“xx-east-1â€ã€‚ 如æž
objectStorageUseSSL: "使用 SSL"
objectStorageUseSSLDesc: "如果ä¸ä½¿ç”¨ https 进行 API 连接,请关闭。"
objectStorageUseProxy: "使用代ç†"
-objectStorageUseProxyDesc: "如果您ä¸ä½¿ç”¨ä»£ç†è¿›è¡Œ API 连接,请将其关闭。"
+objectStorageUseProxyDesc: "如果ä¸ä½¿ç”¨ä»£ç†è¿›è¡Œ API 连接,请关闭。"
objectStorageSetPublicRead: "上传时设置为 public-read"
s3ForcePathStyleDesc: "å¯ç”¨ s3ForcePathStyle 会强制将存储桶å称指定为 URL ä¸­è·¯å¾„çš„ä¸€éƒ¨åˆ†ï¼Œè€Œä¸æ˜¯ä¸»æœºå。使用自托管 Minio 等时å¯èƒ½éœ€è¦å¯ç”¨ã€‚"
serverLogs: "æœåŠ¡å™¨æ—¥å¿—"
@@ -683,12 +685,16 @@ emptyToDisableSmtpAuth: "用户å和密ç ç•™ç©ºå¯ä»¥ç¦ç”¨ SMTP 验è¯"
smtpSecure: "在 SMTP 连接中使用éšå¼ SSL / TLS"
smtpSecureInfo: "使用 STARTTLS 时关闭。"
testEmail: "邮件å‘逿µ‹è¯•"
-wordMute: "éšè—文字"
-hardWordMute: "å±è”½å…³é”®è¯"
+wordMute: "éšè—关键è¯"
+wordMuteDescription: "折å åŒ…嫿Œ‡å®šå…³é”®è¯çš„帖å­ã€‚被折å çš„帖å­å¯å•击展开。"
+hardWordMute: "éšè—硬关键è¯"
+showMutedWord: "显示已éšè—的关键è¯"
+hardWordMuteDescription: "éšè—åŒ…å«æŒ‡å®šå…³é”®è¯çš„帖å­ã€‚与éšè—关键è¯ä¸åŒï¼Œå¸–å­å°†å®Œå…¨ä¸ä¼šæ˜¾ç¤ºã€‚"
regexpError: "正则表达å¼é”™è¯¯"
-regexpErrorDescription: "{tab} å±è”½æ–‡å­—的第 {line} è¡Œçš„æ­£åˆ™è¡¨è¾¾å¼æœ‰é”™è¯¯ï¼š"
+regexpErrorDescription: "{tab} éšè—文字的第 {line} è¡Œçš„æ­£åˆ™è¡¨è¾¾å¼æœ‰é”™è¯¯ï¼š"
instanceMute: "å·²éšè—çš„æœåС噍"
userSaysSomething: "{name} 说了什么,但是被å±è”½è¯è¿‡æ»¤äº†"
+userSaysSomethingAbout: "{name} 说了关于「{word}ã€çš„什么"
makeActive: "å¯ç”¨"
display: "显示"
copy: "å¤åˆ¶"
@@ -759,7 +765,7 @@ driveFilesCount: "网盘的文件数"
driveUsage: "网盘的空间用é‡"
noCrawle: "è¦æ±‚æœç´¢å¼•擎ä¸ç´¢å¼•该用户"
noCrawleDescription: "è¦æ±‚æœç´¢å¼•擎ä¸è¦æ”¶å½•(索引)您的用户页é¢ï¼Œå¸–å­ï¼Œé¡µé¢ç­‰ã€‚"
-lockedAccountInfo: "å³ä½¿å¯ç”¨è¯¥åŠŸèƒ½ï¼Œåªè¦æ‚¨ä¸å°†å¸–å­å¯è§èŒƒå›´è®¾ç½®ä¸ºâ€œä»…关注者â€ï¼Œä»»ä½•人都还是å¯ä»¥çœ‹åˆ°æ‚¨çš„帖å­ã€‚"
+lockedAccountInfo: "å³ä½¿å¯ç”¨è¯¥åŠŸèƒ½ï¼Œåªè¦å¸–å­å¯è§èŒƒå›´ä¸æ˜¯ã€Œä»…关注者ã€ï¼Œä»»ä½•人都å¯ä»¥çœ‹åˆ°æ‚¨çš„帖å­ã€‚"
alwaysMarkSensitive: "é»˜è®¤å°†åª’ä½“æ–‡ä»¶æ ‡è®°ä¸ºæ•æ„Ÿå†…容"
loadRawImages: "添加附件图åƒçš„缩略图时使用原始图åƒè´¨é‡"
disableShowingAnimatedImages: "䏿’­æ”¾åŠ¨ç”»"
@@ -846,7 +852,7 @@ active: "活动"
offline: "离线"
notRecommended: "䏿ލè"
botProtection: "Bot防御"
-instanceBlocking: "被阻拦的æœåС噍"
+instanceBlocking: "å±è”½/é™éŸ³çš„æœåŠ¡å™¨"
selectAccount: "选择账户"
switchAccount: "切æ¢è´¦æˆ·"
enabled: "å·²å¯ç”¨"
@@ -1301,6 +1307,8 @@ lockdown: "é”定"
pleaseSelectAccount: "è¯·é€‰æ‹©å¸æˆ·"
availableRoles: "å¯ç”¨è§’色"
acknowledgeNotesAndEnable: "ç†è§£æ³¨æ„事项åŽå†å¼€å¯ã€‚"
+federationSpecified: "æ­¤æœåС噍已开å¯è”åˆç™½åå•。åªèƒ½ä¸Žç®¡ç†å‘˜æŒ‡å®šçš„æœåŠ¡å™¨é€šä¿¡ã€‚"
+federationDisabled: "æ­¤æœåС噍已ç¦ç”¨è”åˆã€‚无法与其它æœåŠ¡å™¨ä¸Šçš„ç”¨æˆ·é€šä¿¡ã€‚"
_accountSettings:
requireSigninToViewContents: "需è¦ç™»å½•æ‰èƒ½æ˜¾ç¤ºå†…容"
requireSigninToViewContentsDescription1: "您å‘布的所有帖å­å°†å˜æˆéœ€è¦ç™»å…¥åŽæ‰ä¼šæ˜¾ç¤ºã€‚有望防止爬虫收集å„ç§ä¿¡æ¯ã€‚"
@@ -1319,7 +1327,7 @@ _abuseUserReport:
resolve: "解决"
accept: "确认"
reject: "æ‹’ç»"
- resolveTutorial: "如果举报内容有ç†ä¸”已解决,选择「确认ã€å°†æ¡ˆä»¶ä»¥è‚¯å®šçš„æ€åº¦æ ‡è®°ä¸ºå·²è§£å†³ã€‚\n如果举报内容站ä¸ä½è„šï¼Œé€‰æ‹©ã€Œæ‹’ç»ã€å°†æ¡ˆä»¶ä»¥å¦å®šçš„æ€åº¦æ ‡è®°ä¸ºå·²è§£å†³ã€‚"
+ resolveTutorial: "如果认å¯ä¸¾æŠ¥å¹¶å·²è§£å†³ï¼Œé€‰æ‹©ã€Œç¡®è®¤ã€å°†æ¡ˆä»¶ä»¥è‚¯å®šçš„æ€åº¦æ ‡è®°ä¸ºå·²è§£å†³ã€‚\n如果ä¸è®¤å¯ä¸¾æŠ¥ï¼Œé€‰æ‹©ã€Œæ‹’ç»ã€å°†æ¡ˆä»¶ä»¥å¦å®šçš„æ€åº¦æ ‡è®°ä¸ºå·²è§£å†³ã€‚"
_delivery:
status: "投递状æ€"
stop: "åœæ­¢æŠ•递"
@@ -1468,7 +1476,7 @@ _accountMigration:
moveTo: "把这个账户è¿ç§»åˆ°æ–°çš„账户"
moveToLabel: "è¿ç§»åŽçš„账户"
moveCannotBeUndone: "一旦è¿ç§»è´¦æˆ·ï¼Œå°±æ— æ³•撤销。"
- moveAccountDescription: "\nè¿ç§»åˆ°æ–°å¸æˆ·ã€‚\nã€€ãƒ»çŽ°æœ‰çš„å…³æ³¨è€…è‡ªåŠ¨å…³æ³¨æ–°å¸æˆ·\nã€€ãƒ»æ­¤å¸æˆ·çš„æ‰€æœ‰å…³æ³¨è€…都将被删除\n ・您将无法å†ä½¿ç”¨æ­¤å¸æˆ·å‘帖。\n关注者è¿ç§»æ˜¯è‡ªåŠ¨çš„ï¼Œä½†å…³æ³¨ä¸­è¿ç§»å¿…须手动完æˆã€‚请在è¿ç§»å‰åœ¨æ­¤å¸æˆ·ä¸Šå¯¼å‡ºå…³æ³¨åˆ—表,并在è¿ç§»åŽç«‹å³åœ¨ç›®æ ‡å¸æˆ·ä¸Šæ‰§è¡Œå¯¼å…¥ã€‚\nå±è”½åˆ—表也是如此,因此您必须手动è¿ç§»å®ƒã€‚\n(此æè¿°é€‚用于该æœåŠ¡å™¨ï¼ˆMisskey v13.12.0 或更高版本)。其他 ActivityPub 软件(例如 Mastodon)的行为å¯èƒ½æœ‰æ‰€ä¸åŒã€‚)"
+ moveAccountDescription: "\nè¿ç§»åˆ°æ–°å¸æˆ·ã€‚\nã€€ãƒ»çŽ°æœ‰çš„å…³æ³¨è€…è‡ªåŠ¨å…³æ³¨æ–°å¸æˆ·\nã€€ãƒ»æ­¤å¸æˆ·çš„æ‰€æœ‰å…³æ³¨è€…都将被删除\n ・您将无法å†ä½¿ç”¨æ­¤å¸æˆ·å‘帖。\n关注者è¿ç§»æ˜¯è‡ªåŠ¨çš„ï¼Œä½†å…³æ³¨ä¸­è¿ç§»å¿…须手动完æˆã€‚请在è¿ç§»å‰åœ¨æ­¤å¸æˆ·ä¸Šå¯¼å‡ºå…³æ³¨åˆ—表,并在è¿ç§»åŽç«‹å³åœ¨ç›®æ ‡å¸æˆ·ä¸Šæ‰§è¡Œå¯¼å…¥ã€‚\n列表ã€éšè—ã€å±è”½ä¹Ÿæ˜¯å¦‚此,因此您必须手动è¿ç§»å®ƒã€‚\n(此æè¿°é€‚用于该æœåŠ¡å™¨ï¼ˆMisskey v13.12.0 或更高版本)。其他 ActivityPub 软件(例如 Mastodon)的行为å¯èƒ½æœ‰æ‰€ä¸åŒã€‚)"
moveAccountHowTo: "è¦è¿›è¡Œè´¦æˆ·è¿ç§»ï¼Œè¯·çŽ°åœ¨ç›®æ ‡è´¦æˆ·ä¸­ä¸ºæ­¤è´¦æˆ·å»ºç«‹ä¸€ä¸ªåˆ«å。\n建立别ååŽï¼Œè¯·åƒè¿™æ ·è¾“入目标账户:@username@server.example.com"
startMigration: "è¿ç§»"
migrationConfirm: "ç¡®å®šè¦æŠŠæ­¤è´¦æˆ·è¿ç§»åˆ° {account} å—?一旦确定åŽï¼Œæ­¤æ“ä½œæ— æ³•å–æ¶ˆï¼Œæ­¤è´¦æˆ·ä¹Ÿæ— æ³•以原æ¥çš„状æ€ä½¿ç”¨ã€‚\nåŒæ—¶ï¼Œè¯·ç¡®è®¤è¿ç§»åŽçš„账户,已创造别å。"
@@ -1688,7 +1696,7 @@ _achievements:
title: "超高校级的幸è¿"
description: "æ¯ 10 秒有 0.005% 的概率自动获得"
_setNameToSyuilo:
- title: "åƒç¥žä¸€æ ·å‘"
+ title: "ä¸Šå¸æƒ…结"
description: "å°†å称设定为 syuilo"
_passedSinceAccountCreated1:
title: "一周年"
@@ -1794,7 +1802,7 @@ _role:
canImportAntennas: "å…许导入天线"
canImportBlocking: "å…许导入å±è”½åˆ—表"
canImportFollowing: "å…许导入关注列表"
- canImportMuting: "å…许导入å±è”½åˆ—表"
+ canImportMuting: "å…许导入éšè—列表"
canImportUserLists: "å…许导入用户列表"
_condition:
roleAssignedTo: "已分é…给手动角色"
@@ -1948,7 +1956,7 @@ _wordMute:
_instanceMute:
instanceMuteDescription: "éšè—æœåŠ¡å™¨ä¸­çš„æ‰€æœ‰å¸–å­å’Œè½¬å¸–,包括这些æœåŠ¡å™¨ä¸Šçš„ç”¨æˆ·å›žå¤ã€‚"
instanceMuteDescription2: "一行一个"
- title: "éšè—æœåŠ¡å™¨å·²è®¾ç½®çš„å¸–å­ã€‚"
+ title: "下é¢å®žä¾‹ä¸­çš„帖å­å°†è¢«éšè—。"
heading: "å·²éšè—çš„æœåС噍"
_theme:
explore: "寻找主题"
@@ -2069,12 +2077,12 @@ _2fa:
step4: "从现在开始,任何登录æ“ä½œéƒ½å°†è¦æ±‚您æä¾›åЍæ€å£ä»¤ã€‚"
securityKeyNotSupported: "您的æµè§ˆå™¨ä¸æ”¯æŒå®‰å…¨å¯†é’¥ã€‚"
registerTOTPBeforeKey: "è¦æ³¨å†Œå®‰å…¨å¯†é’¥æˆ– Passkey,请先设置验è¯å™¨ã€‚"
- securityKeyInfo: "注册兼容 WebAuthn çš„å¯†é’¥ï¼Œä¾‹å¦‚æ”¯æŒ FIDO2 的硬件安全密钥ã€è®¾å¤‡ä¸Šçš„生物识别功能ã€PIN ç ä»¥åŠ Passkey 等。"
+ securityKeyInfo: "注册兼容 WebAuthn çš„å¯†é’¥ï¼Œä¾‹å¦‚æ”¯æŒ FIDO2 的硬件安全密钥ã€è®¾å¤‡ä¸Šçš„生物识别功能ã€PIN ä»¥åŠ Passkey 等。"
registerSecurityKey: "注册安全密钥或 Passkey"
securityKeyName: "输入密钥åç§°"
tapSecurityKey: "请按照æµè§ˆå™¨è¯´æ˜Žæ“ä½œæ¥æ³¨å†Œå®‰å…¨å¯†é’¥æˆ– Passkey。"
removeKey: "删除安全密钥"
- removeKeyConfirm: "您确定è¦åˆ é™¤ {name} å—?"
+ removeKeyConfirm: "确定è¦åˆ é™¤ {name} å—?"
whyTOTPOnlyRenew: "å½“æ³¨å†Œäº†å®‰å…¨å¯†é’¥æ—¶ï¼Œæ— æ³•å–æ¶ˆä½¿ç”¨éªŒè¯å™¨ã€‚"
renewTOTP: "é‡ç½®éªŒè¯å™¨"
renewTOTPConfirm: "当å‰éªŒè¯å™¨çš„验è¯ç åŠå¤‡ç”¨ä»£ç å·²å¤±æ•ˆ"
@@ -2282,7 +2290,7 @@ _profile:
name: "昵称"
username: "用户å"
description: "个人简介"
- youCanIncludeHashtags: "ä½ å¯ä»¥åœ¨ä¸ªäººç®€ä»‹ä¸­åŒ…å«ä¸€äº›#标签。"
+ youCanIncludeHashtags: "å¯ä»¥åœ¨ä¸ªäººç®€ä»‹ä¸­åŒ…å« #标签。"
metadata: "附加信æ¯"
metadataEdit: "附加信æ¯ç¼–辑"
metadataDescription: "最多å¯ä»¥åœ¨ä¸ªäººèµ„æ–™ä¸­ä»¥è¡¨æ ¼å½¢å¼æ˜¾ç¤ºå››æ¡å…¶ä»–ä¿¡æ¯ã€‚"
@@ -2386,7 +2394,7 @@ _pages:
fontSansSerif: "无衬线字体"
eyeCatchingImageSet: "设置å°é¢å›¾ç‰‡"
eyeCatchingImageRemove: "删除å°é¢å›¾ç‰‡"
- chooseBlock: "添加å—"
+ chooseBlock: "添加内容å—"
enterSectionTitle: "è¾“å…¥ä¼šè¯æ ‡é¢˜"
selectType: "选择类型"
contentBlocks: "内容"
@@ -2398,8 +2406,8 @@ _pages:
section: "章节"
image: "图片"
button: "按钮"
- dynamic: "动æ€åŒºå—"
- dynamicDescription: "这个区å—å·²ç»åºŸå¼ƒã€‚以åŽè¯·ä½¿ç”¨{play}。"
+ dynamic: "动æ€å†…容å—"
+ dynamicDescription: "这个内容å—å·²ç»åºŸå¼ƒã€‚以åŽè¯·ä½¿ç”¨{play}。"
note: "嵌入的帖å­"
_note:
id: "å¸–å­ ID"
@@ -2433,7 +2441,7 @@ _notification:
renotedBySomeUsers: "{n} 人转å‘了"
followedBySomeUsers: "被 {n} 人关注"
flushNotification: "é‡ç½®é€šçŸ¥åކå²"
- exportOfXCompleted: "å·²å®Œæˆ {x} 个导出"
+ exportOfXCompleted: "å·²å®Œæˆ {x} 的导出"
login: "有新的登录"
_types:
all: "全部"
@@ -2721,6 +2729,66 @@ _contextMenu:
app: "应用"
appWithShift: "Shift 键应用"
native: "æµè§ˆå™¨çš„用户界é¢"
+_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: "å•击此链接以从电脑中选择"
+ emojiInputAreaList3: "å•击此链接以从网盘中选择"
+ confirmRegisterEmojisDescription: "è¦å°†åˆ—è¡¨å†…æ˜¾ç¤ºçš„è¡¨æƒ…ç¬¦å·æ›¿æ¢ä¸ºæ–°çš„自定义表情符å·å—?(为é™ä½ŽæœåŠ¡å™¨è´Ÿè½½ï¼Œä¸€æ¬¡æ“作最多åªèƒ½æ³¨å†Œ {count} 个表情符å·ï¼‰"
+ confirmClearEmojisDescription: "è¦æ”¾å¼ƒç¼–è¾‘å¹¶å°†åˆ—è¡¨å†…è¡¨ç¤ºçš„è¡¨æƒ…ç¬¦å·æ¸…空å—?"
+ confirmUploadEmojisDescription: "è¦å°†æ‹–放的 {count} 个文件上传到网盘上å—?"
_embedCodeGen:
title: "自定义嵌入代ç "
header: "显示标题"
@@ -2744,3 +2812,34 @@ _selfXssPrevention:
_followRequest:
recieved: "已收到申请"
sent: "å·²å‘é€ç”³è¯·"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "无法与此æœåŠ¡å™¨é€šä¿¡"
+ description: "与此æœåŠ¡å™¨çš„é€šä¿¡å¯èƒ½è¢«ç¦ç”¨ï¼Œåˆæˆ–者是å±è”½äº†æ­¤æœåŠ¡å™¨æˆ–è¢«æ­¤æœåС噍å±è”½äº†ã€‚\n请è”ç³»æœåŠ¡å™¨çš„ç®¡ç†è€…。"
+ _uriInvalid:
+ title: "URI 有误"
+ description: "输入的 URI 有问题。请确认是å¦è¾“入了 URI 中无法使用的字符。"
+ _requestFailed:
+ title: "请求失败"
+ description: "与该æœåŠ¡å™¨çš„é€šä¿¡å¤±è´¥ã€‚å¯¹é¢æœåС噍å¯èƒ½ä¸å¯ç”¨ã€‚å¦å¤–,请确认是å¦è¾“入了无效或ä¸å­˜åœ¨çš„ URI。"
+ _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: "å‘生æ„外错误。"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index d4ffb28c76..159ede1356 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.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: "刪除並編輯"
@@ -230,7 +232,7 @@ instanceInfo: "伺æœå™¨è³‡è¨Š"
statistics: "統計"
clearQueue: "清除佇列"
clearQueueConfirmTitle: "ç¢ºå®šè¦æ¸…除佇列嗎?"
-clearQueueConfirmText: "æœªç™¼ä½ˆçš„è²¼æ–‡å°‡ä¸æœƒç™¼ä½ˆã€‚您通常ä¸éœ€è¦ç¢ºèªã€‚"
+clearQueueConfirmText: "未æˆåŠŸç™¼ä½ˆçš„è²¼æ–‡å°‡ä¸æœƒå†å˜—試發佈。通常ä¸éœ€è¦é€²è¡Œé€™é …æ“作。"
clearCachedFiles: "清除快å–資料"
clearCachedFilesConfirm: "ç¢ºå®šè¦æ¸…除所有é ç«¯æš«å­˜è³‡æ–™å—Žï¼Ÿ"
blockedInstances: "å·²å°éŽ–çš„ä¼ºæœå™¨"
@@ -291,8 +293,8 @@ messaging: "èŠå¤©"
upload: "上傳"
keepOriginalUploading: "ä¿ç•™åŽŸåœ–"
keepOriginalUploadingDescription: "上傳圖片時ä¿ç•™åŽŸå§‹åœ–ç‰‡ã€‚é—œé–‰æ™‚ï¼Œç€è¦½å™¨æœƒåœ¨ä¸Šå‚³æ™‚生æˆé©ç”¨æ–¼ç¶²è·¯å‚³é€çš„版本。"
-fromDrive: "從雲端空間"
-fromUrl: "從 URL"
+fromDrive: "å¾žé›²ç«¯ç©ºé–“ä¸­é¸æ“‡"
+fromUrl: "從 URL 上傳"
uploadFromUrl: "從網å€ä¸Šå‚³"
uploadFromUrlDescription: "您è¦ä¸Šå‚³çš„æª”案網å€"
uploadFromUrlRequested: "已請求上傳"
@@ -324,7 +326,7 @@ light: "淺色"
dark: "深色"
lightThemes: "淺色佈景主題"
darkThemes: "深色佈景主題"
-syncDeviceDarkMode: "與設備的深色模å¼åŒæ­¥"
+syncDeviceDarkMode: "與è£ç½®çš„æ·±è‰²æ¨¡å¼åŒæ­¥"
drive: "雲端硬碟"
fileName: "檔案å稱"
selectFile: "鏿“‡æª”案"
@@ -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: "複製"
@@ -764,7 +770,7 @@ alwaysMarkSensitive: "é è¨­æ¨™è¨˜æª”æ¡ˆç‚ºæ•æ„Ÿå…§å®¹"
loadRawImages: "以原始圖檔顯示附件圖檔的縮圖"
disableShowingAnimatedImages: "䏿’­æ”¾å‹•態圖檔"
highlightSensitiveMedia: "å¼·èª¿æ•æ„Ÿæ¨™è¨˜"
-verificationEmailSent: "已發é€é©—證電å­éƒµä»¶ã€‚請點擊進入電å­éƒµä»¶ä¸­çš„éˆæŽ¥å®Œæˆé©—證。"
+verificationEmailSent: "已發é€é©—證電å­éƒµä»¶ã€‚請點擊進入電å­éƒµä»¶ä¸­çš„連çµä»¥å®Œæˆé©—證。"
notSet: "未設定"
emailVerified: "å·²æˆåŠŸé©—è­‰æ‚¨çš„é›»å­éƒµä»¶åœ°å€"
noteFavoritesCount: "我的最愛貼文的數目"
@@ -821,7 +827,7 @@ apply: "套用"
receiveAnnouncementFromInstance: "接收來自伺æœå™¨çš„通知"
emailNotification: "郵件通知"
publish: "發布"
-inChannelSearch: "é »é“内æœå°‹"
+inChannelSearch: "é »é“å…§æœå°‹"
useReactionPickerForContextMenu: "點擊å³éµé–‹å•Ÿåæ‡‰é¸æ“‡å™¨"
typingUsers: "{users}輸入中"
jumpToSpecifiedDate: "跳轉到特定日期"
@@ -925,7 +931,7 @@ incorrectPassword: "密碼錯誤。"
incorrectTotp: "ä¸€æ¬¡æ€§å¯†ç¢¼éŒ¯èª¤ï¼Œæˆ–è€…å·²éŽæœŸã€‚"
voteConfirm: "確定投給「{choice}ã€ï¼Ÿ"
hide: "éš±è—"
-useDrawerReactionPickerForMobile: "在移動設備上使用抽屜顯示"
+useDrawerReactionPickerForMobile: "在行動è£ç½®ä¸Šä½¿ç”¨æŠ½å±œé¡¯ç¤º"
welcomeBackWithName: "歡迎回來,{name}"
clickToFinishEmailVerification: "點擊 [{ok}] 完æˆé›»å­éƒµä»¶åœ°å€èªè­‰ã€‚"
overridedDeviceKind: "è£ç½®é¡žåž‹"
@@ -1006,7 +1012,7 @@ unsubscribePushNotification: "åœç”¨æŽ¨æ’­é€šçŸ¥"
pushNotificationAlreadySubscribed: "推播通知啟用中"
pushNotificationNotSupported: "ç€è¦½å™¨æˆ–伺æœå™¨ä¸æ”¯æ´æŽ¨æ’­é€šçŸ¥"
sendPushNotificationReadMessage: "如果已閱讀通知與訊æ¯ï¼Œå°±åˆªé™¤æŽ¨æ’­é€šçŸ¥"
-sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}ã€é€šçŸ¥å°‡ç«‹åˆ»é¡¯ç¤ºã€‚å¯èƒ½æœƒæ›´æ¶ˆè€—è£ç½®é›»æ± ã€‚"
+sendPushNotificationReadMessageCaption: "å¯èƒ½æœƒå°Žè‡´è£ç½®çš„電池消耗é‡å¢žåŠ ã€‚"
windowMaximize: "最大化"
windowMinimize: "最å°åŒ–"
windowRestore: "復原"
@@ -1175,20 +1181,20 @@ used: "已使用"
expired: "éŽæœŸ"
doYouAgree: "ä½ åŒæ„嗎?"
beSureToReadThisAsItIsImportant: "é‡è¦ï¼Œè«‹å‹™å¿…閱讀。"
-iHaveReadXCarefullyAndAgree: "æˆ‘å·²ä»”ç´°é–±è®€ä¸¦åŒæ„「{x}ã€çš„内容。"
+iHaveReadXCarefullyAndAgree: "æˆ‘å·²ä»”ç´°é–±è®€ä¸¦åŒæ„「{x}ã€çš„內容。"
dialog: "å°è©±æ–¹å¡Š"
icon: "圖示"
forYou: "給您"
currentAnnouncements: "最新公告"
pastAnnouncements: "æ­·å²å…¬å‘Š"
youHaveUnreadAnnouncements: "有未讀的公告。"
-useSecurityKey: "請按照ç€è¦½å™¨æˆ–設備上的說明使用安全金鑰或 Passkey。"
+useSecurityKey: "請按照ç€è¦½å™¨æˆ–è£ç½®ä¸Šçš„說明來使用安全金鑰或 Passkey。"
replies: "回覆"
renotes: "轉發"
loadReplies: "閱覽回覆"
loadConversation: "閱覽å°è©±"
pinnedList: "已置頂的清單"
-keepScreenOn: "ä¿æŒè¨­å‚™èž¢å¹•開啟"
+keepScreenOn: "ä¿æŒè£ç½®èž¢å¹•開啟"
verifiedLink: "已驗證連çµ"
notifyNotes: "開啟貼文通知"
unnotifyNotes: "關閉貼文通知"
@@ -1243,7 +1249,7 @@ overwriteContentConfirm: "確定è¦è¦†è“‹ç›®å‰çš„內容嗎?"
seasonalScreenEffect: "隨季節變æ›ç•«é¢çš„呈ç¾"
decorate: "設置頭åƒè£é£¾"
addMfmFunction: "æ’å…¥ MFM 功能語法"
-enableQuickAddMfmFunction: "顯示高級 MFM 鏿“‡å™¨"
+enableQuickAddMfmFunction: "顯示進階 MFM 鏿“‡å™¨"
bubbleGame: "æ°£æ³¡éŠæˆ²"
sfx: "音效"
soundWillBePlayed: "將播放音效"
@@ -1270,7 +1276,7 @@ useNativeUIForVideoAudioPlayer: "使用ç€è¦½å™¨çš„ UI 播放影片與音訊"
keepOriginalFilename: "ä¿ç•™åŽŸå§‹æª”å"
keepOriginalFilenameDescription: "如果關閉此設置,上傳時檔案å稱會自動替æ›ç‚ºéš¨æ©Ÿå­—串。"
noDescription: "沒有說明文字"
-alwaysConfirmFollow: "跟隨時總是確èª"
+alwaysConfirmFollow: "追隨時總是確èª"
inquiry: "è¯çµ¡æˆ‘們"
tryAgain: "è«‹å†è©¦ä¸€æ¬¡ã€‚"
confirmWhenRevealingSensitiveMedia: "è¦é¡¯ç¤ºæ•感媒體時需確èª"
@@ -1301,6 +1307,8 @@ lockdown: "鎖定"
pleaseSelectAccount: "è«‹é¸æ“‡å¸³æˆ¶"
availableRoles: "å¯ç”¨è§’色"
acknowledgeNotesAndEnable: "了解注æ„事項後å†é–‹å•Ÿã€‚"
+federationSpecified: "此伺æœå™¨ä»¥ç™½åå–®è¯é‚¦çš„æ–¹å¼é‹ä½œã€‚除了管ç†å“¡æŒ‡å®šçš„伺æœå™¨å¤–,它無法與其他伺æœå™¨äº’動。"
+federationDisabled: "此伺æœå™¨æœªé–‹å•Ÿç«™å°è¯é‚¦ã€‚無法與其他伺æœå™¨ä¸Šçš„使用者互動。"
_accountSettings:
requireSigninToViewContents: "須登入以顯示內容"
requireSigninToViewContentsDescription1: "å¿…é ˆç™»å…¥æ‰æœƒé¡¯ç¤ºæ‚¨å»ºç«‹çš„è²¼æ–‡ç­‰å…§å®¹ã€‚å¯æœ›æœ‰æ•ˆé˜²æ­¢è³‡è¨Šè¢«çˆ¬èŸ²è’集。"
@@ -1366,7 +1374,7 @@ _initialAccountSetting:
theseSettingsCanEditLater: "這裡的設定å¯ä»¥åœ¨ä¹‹å¾Œè®Šæ›´ã€‚"
youCanEditMoreSettingsInSettingsPageLater: "除此之外,還å¯ä»¥åœ¨ã€Œè¨­å®šã€é é¢é€²è¡Œå„種設定。之後請確èªçœ‹çœ‹ã€‚"
followUsers: "為了構築時間軸,試著追隨您感興趣的使用者å§ã€‚"
- pushNotificationDescription: "啟用推é€é€šçŸ¥ï¼Œå°±å¯ä»¥åœ¨è¨­å‚™ä¸ŠæŽ¥æ”¶{name}的通知。"
+ pushNotificationDescription: "啟用推é€é€šçŸ¥å¾Œï¼Œå°±å¯ä»¥åœ¨è£ç½®ä¸ŠæŽ¥æ”¶ä¾†è‡ª{name}的通知了。"
initialAccountSettingCompleted: "åˆå§‹è¨­å®šå®Œæˆäº†ï¼"
haveFun: "盡情享å—{name}å§ï¼"
youCanContinueTutorial: "您å¯ä»¥ç¹¼çºŒå­¸ç¿’如何使用{name}(Misskey),也å¯ä»¥å°±æ­¤æ‰“ä½ï¼Œç«‹å³é–‹å§‹ä½¿ç”¨ã€‚"
@@ -1386,12 +1394,12 @@ _initialTutorial:
description: "在Misskey上發布的內容稱為「貼文ã€ã€‚è²¼æ–‡åœ¨æ™‚é–“è»¸ä¸ŠæŒ‰æ™‚é–“é †åºæŽ’åˆ—ï¼Œä¸¦å³æ™‚更新。"
reply: "您å¯ä»¥å›žè¦†è²¼æ–‡ï¼Œä¸¦åƒè¨Žè«–串一樣繼續å°è©±ã€‚"
renote: "您å¯ä»¥å°‡æ­¤è²¼æ–‡åˆ†äº«åˆ°è‡ªå·±çš„æ™‚間軸。您也å¯ä»¥åœ¨å¼•用時添加文字。"
- reaction: "您å¯ä»¥æ·»åР忇‰ã€‚詳細資訊將在下一é é€²è¡Œèªªæ˜Žã€‚"
+ reaction: "您å¯ä»¥åР入忇‰ã€‚詳細資訊將在下一é é€²è¡Œèªªæ˜Žã€‚"
menu: "å¯åŸ·è¡Œå„種æ“作,如查看貼文詳細資訊和複製連çµã€‚"
_reaction:
title: "ä»€éº¼æ˜¯åæ‡‰ï¼Ÿ"
- description: "您å¯ä»¥åœ¨è²¼æ–‡ä¸­æ·»åŠ ã€Œåæ‡‰ã€ã€‚您å¯ä»¥ä½¿ç”¨å應輕鬆隨æ„地表é”「最愛/å¤§å¿ƒã€æ‰€ç„¡æ³•傳é”的細微差別。"
- letsTryReacting: "å¯ä»¥é€éŽé»žæ“Šè²¼æ–‡ä¸Šçš„「+ã€æŒ‰éˆ•ä¾†æ·»åŠ åæ‡‰ã€‚è«‹å˜—è©¦åœ¨æ­¤ç¯„ä¾‹è²¼æ–‡æ·»åŠ åæ‡‰ï¼"
+ description: "您å¯ä»¥åœ¨è²¼æ–‡ä¸­åŠ ä¸Šã€Œåæ‡‰ã€ã€‚有些用「最愛/大心ã€ç„¡æ³•傳é”的感想,å¯ä»¥ç”¨å應輕鬆地表é”出來。"
+ letsTryReacting: "按一下貼文上的「+ã€æŒ‰éˆ•å³å¯åР入忇‰ã€‚è©¦è‘—å°æ­¤ç¯„ä¾‹è²¼æ–‡åŠ ä¸Šåæ‡‰ï¼"
reactToContinue: "æ·»åŠ åæ‡‰ä»¥ç¹¼çºŒæ•™å­¸èª²ç¨‹ã€‚"
reactNotification: "ç•¶æœ‰äººå°æ‚¨çš„貼文åšå‡ºåæ‡‰æ™‚æœƒå³æ™‚接收到通知。"
reactDone: "按下「-ã€æŒ‰éˆ•å¯ä»¥å–æ¶ˆåæ‡‰ã€‚"
@@ -1473,7 +1481,7 @@ _accountMigration:
startMigration: "é·ç§»"
migrationConfirm: "確定è¦å°‡é€™å€‹å¸³æˆ¶é·ç§»è‡³ {account} 嗎?一旦é·ç§»å°±ç„¡æ³•撤銷,也就無法以原來的狀態使用這個帳戶。\nå¦å¤–,請確èªåœ¨è¦é·ç§»åˆ°çš„帳戶已經建立了一個別å。"
movedAndCannotBeUndone: "帳戶已é·ç§»ã€‚\né·ç§»ç„¡æ³•撤消。"
- postMigrationNote: "å–æ¶ˆè¿½è¹¤æ­¤å¸³æˆ¶å°‡åœ¨é·ç§»æ“作後 24 å°æ™‚執行。\n 此帳戶有 0 個關注者/關注者。 您的關注者ä»ç„¶å¯ä»¥çœ‹åˆ°æ­¤å¸³æˆ¶çš„關注者帖å­ï¼Œå› ç‚ºæ‚¨ä¸æœƒè¢«å–消關注。"
+ postMigrationNote: "將在完æˆé·ç§»å¾Œçš„ 24 å°æ™‚å–æ¶ˆè¿½éš¨æ‰€æœ‰å¸³è™Ÿã€‚\n此帳戶的追隨中/è¿½éš¨è€…äººæ•¸å°‡æ­¸é›¶ã€‚ç”±æ–¼ä¸æœƒè§£é™¤ç²‰çµ²å°æ‚¨çš„追隨,因此他們ä»ç„¶å¯ä»¥ç¹¼çºŒé–±è¦½æ­¤å¸³æˆ¶åƒ…å°è¿½éš¨è€…公開的貼文。"
movedTo: "è¦é·ç§»åˆ°çš„帳戶:"
_achievements:
earnedAt: "ç²å¾—日期"
@@ -1793,7 +1801,7 @@ _role:
avatarDecorationLimit: "é ­åƒè£é£¾çš„æœ€å¤§è¨­ç½®é‡"
canImportAntennas: "å…許匯入天線"
canImportBlocking: "å…許匯入å°éŽ–åå–®"
- canImportFollowing: "å…許匯入跟隨åå–®"
+ canImportFollowing: "å…許匯入追隨åå–®"
canImportMuting: "å…許匯入éœéŸ³åå–®"
canImportUserLists: "å…許匯入清單"
_condition:
@@ -2069,7 +2077,7 @@ _2fa:
step4: "從ç¾åœ¨é–‹å§‹ï¼Œä»»ä½•登入æ“ä½œéƒ½å°‡è¦æ±‚您æä¾›æ¬Šæ–。"
securityKeyNotSupported: "您的ç€è¦½å™¨ä¸æ”¯æ´å®‰å…¨é‡‘鑰。"
registerTOTPBeforeKey: "如è¦è¨»å†Šå®‰å…¨é‡‘鑰或 Passkey,請先設定驗證應用程å¼ã€‚"
- securityKeyInfo: "您å¯ä»¥è¨­å®šä½¿ç”¨æ”¯æ´ FIDO2 的硬體安全鎖ã€çµ‚端設備的指紋èªè­‰ï¼Œæˆ–者 PIN 碼來登入。"
+ securityKeyInfo: "您å¯ä»¥è¨­å®šä½¿ç”¨æ”¯æ´ FIDO2 的硬體安全金鑰,以åŠè£ç½®ä¸Šçš„生物辨識ã€PIN 碼和密碼等來登入。"
registerSecurityKey: "註冊安全金鑰或 Passkey"
securityKeyName: "輸入金鑰å稱"
tapSecurityKey: "按照ç€è¦½å™¨çš„說明註冊安全金鑰或 Passkey。"
@@ -2287,7 +2295,7 @@ _profile:
metadataEdit: "編輯附加資訊"
metadataDescription: "å¯ä»¥åœ¨å€‹äººè³‡æ–™ä¸­ä»¥è¡¨æ ¼å½¢å¼é¡¯ç¤ºå…¶ä»–資訊。"
metadataLabel: "標籤"
- metadataContent: "内容"
+ metadataContent: "內容"
changeAvatar: "æ›´æ›å¤§é ­è²¼"
changeBanner: "變更橫幅圖åƒ"
verifiedLinkDescription: "å¦‚æžœè¼¸å…¥åŒ…å«æ‚¨å€‹äººè³‡æ–™çš„網站 URLï¼Œæ¬„ä½æ—邊將出ç¾é©—證圖示。"
@@ -2627,7 +2635,7 @@ _externalResourceInstaller:
description: "å·²å–å¾—è³‡æ–™ä½†è§£æž AiScript 時發生錯誤,導致無法載入。請è¯çµ¡å¤–掛作者。請檢查 Javascript 控制å°ä»¥å–得錯誤詳細資訊。"
_pluginInstallFailed:
title: "外掛安è£å¤±æ•—"
- description: "å®‰è£æ’件時出ç¾å•題。請å†è©¦ä¸€æ¬¡ã€‚è«‹åƒé–± Javascript 控制å°ä»¥å–得錯誤詳細資訊。"
+ description: "安è£å¤–掛時出ç¾å•題。請å†è©¦ä¸€æ¬¡ã€‚å¯åƒé–± Javascript 控制å°ä»¥å–得錯誤詳細資訊。"
_themeParseFailed:
title: "佈景主題解æžéŒ¯èª¤"
description: "å·²å–得資料但解æžä½ˆæ™¯ä¸»é¡Œæ™‚發生錯誤,導致無法載入。請è¯çµ¡ä½ˆæ™¯ä¸»é¡Œçš„作者。請檢查 Javascript 控制å°ä»¥å–得錯誤詳細資訊。"
@@ -2646,7 +2654,7 @@ _dataSaver:
description: "å°‡ä¸å†è‡ªå‹•載入網å€é è¦½ç¸®åœ–。"
_code:
title: "程å¼ç¢¼çªå‡ºé¡¯ç¤º"
- description: "如果使用了 MFM 的程å¼ç¢¼çªé¡¯æ¨™è¨˜ï¼Œå‰‡åœ¨é»žæ“Šä¹‹å‰ä¸æœƒè¼‰å…¥ã€‚程å¼ç¢¼çªé¡¯è¦æ±‚加載æ¯ç¨®ç¨‹å¼èªžè¨€çš„çªé¡¯å®šç¾©æª”案,但由於這些檔案ä¸å†è‡ªå‹•載入,因此有望減少資料æµé‡ã€‚"
+ description: "如果使用了程å¼ç¢¼çªé¡¯èªžæ³•(如 MFM),則在點擊之å‰ä¸æœƒè¢«è¼‰å…¥ã€‚由於需è¦ç‚ºå°æ‡‰çš„程å¼èªžè¨€ä¸‹è¼‰çªé¡¯å®šç¾©æª”案,因此關閉自動載入有助於減少資料æµé‡ã€‚"
_hemisphere:
N: "北åŠçƒ"
S: "å—åŠçƒ"
@@ -2721,6 +2729,66 @@ _contextMenu:
app: "應用程å¼"
appWithShift: "Shift 鵿‡‰ç”¨ç¨‹å¼"
native: "ç€è¦½å™¨çš„使用者介é¢"
+_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: "å°‡é¸å–範åœçš„行設為刪除å°è±¡\n"
+ alertUpdateEmojisNothingDescription: "沒有é¸å–需è¦è®Šæ›´çš„表情符號。"
+ alertDeleteEmojisNothingDescription: "沒有é¸å–需è¦åˆªé™¤çš„表情符號。"
+ confirmMovePage: "è¦ç§»å‹•到其他é é¢å—Žï¼Ÿ"
+ confirmChangeView: "è¦æ›´æ”¹é¡¯ç¤ºæ–¹å¼å—Žï¼Ÿ"
+ confirmUpdateEmojisDescription: "將更新{count}個表情符號。是å¦åŸ·è¡Œæ­¤æ“作?"
+ confirmDeleteEmojisDescription: "將刪除勾é¸çš„{count}個表情符號。是å¦åŸ·è¡Œæ­¤æ“作?"
+ confirmResetDescription: "ç›®å‰æ‰€åšçš„æ‰€æœ‰è®Šæ›´éƒ½æœƒé‡è¨­ã€‚"
+ confirmMovePageDesciption: "æ­¤é é¢çš„表情符號已被更改。 \n若未儲存就直接離開此é é¢ï¼Œå‰‡åœ¨æ­¤é é¢é€²è¡Œçš„æ‰€æœ‰æ›´æ”¹å°‡æœƒè¢«æ¨æ£„。"
+ dialogSelectRoleTitle: "根據表情符號設定的角色進行æœå°‹"
+ _register:
+ uploadSettingTitle: "上傳設定"
+ uploadSettingDescription: "您å¯ä»¥åœ¨æ­¤ç•«é¢è¨­å®šè¡¨æƒ…符號上傳時的æ“作。"
+ directoryToCategoryLabel: "åœ¨ã€Œé¡žåˆ¥ã€æ¬„ä½ä¸­è¼¸å…¥ç›®éŒ„å稱"
+ directoryToCategoryCaption: "æ‹–æ”¾ç›®éŒ„æ™‚ï¼Œè«‹åœ¨ã€Œé¡žåˆ¥ã€æ¬„ä½ä¸­è¼¸å…¥ç›®éŒ„å稱。"
+ emojiInputAreaCaption: "以下列其中一種方å¼é¸æ“‡æ‚¨æƒ³è¦è¨»å†Šçš„表情符號"
+ emojiInputAreaList1: "將圖片檔案或目錄拖放到此框中"
+ emojiInputAreaList2: "點擊此連çµå¾žé›»è…¦ä¸­é¸æ“‡"
+ emojiInputAreaList3: "點擊此連çµå¾žé›²ç«¯ç¡¬ç¢Ÿä¸­é¸æ“‡"
+ confirmRegisterEmojisDescription: "將列表中顯示的表情符號登錄為新的自定表情符號。是å¦ç¢ºå®šï¼Ÿï¼ˆç‚ºé¿å…éŽé«˜è² è·ï¼Œæ¯æ¬¡æ“作最多å¯ç™»éŒ„{count}個表情符號)"
+ confirmClearEmojisDescription: "放棄編輯內容並清除列表中顯示的表情符號。是å¦ç¢ºå®šï¼Ÿ"
+ confirmUploadEmojisDescription: "將拖放的{count}個檔案上傳到雲端硬碟。是å¦åŸ·è¡Œæ­¤æ“作?"
_embedCodeGen:
title: "自訂嵌入程å¼ç¢¼"
header: "檢視標頭 "
@@ -2744,3 +2812,34 @@ _selfXssPrevention:
_followRequest:
recieved: "收到的請求"
sent: "é€å‡ºçš„請求"
+_remoteLookupErrors:
+ _federationNotAllowed:
+ title: "無法與這個伺æœå™¨é€šè¨Š"
+ description: "與此伺æœå™¨çš„通訊å¯èƒ½è¢«åœç”¨ã€æˆ–å°éŽ–äº†è©²ä¼ºæœå™¨ï¼Œæˆ–被該伺æœå™¨å°éŽ–ã€‚\nè«‹è¯ç¹«æ‚¨çš„伺æœå™¨ç®¡ç†å“¡ã€‚"
+ _uriInvalid:
+ title: "URI 䏿­£ç¢º"
+ description: "輸入的 URI 有å•題。請檢查是å¦è¼¸å…¥äº† URI 中ä¸èƒ½ä½¿ç”¨çš„字元。"
+ _requestFailed:
+ title: "請求失敗"
+ description: "與此伺æœå™¨çš„通訊失敗。å¯èƒ½æ˜¯å°æ–¹ä¼ºæœå™¨æ–·ç·šã€‚ 此外,請檢查是å¦è¼¸å…¥äº†ä¸æ­£ç¢ºæˆ–ä¸å­˜åœ¨çš„ URI。"
+ _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: "發生了æ„外的錯誤。"
diff --git a/package.json b/package.json
index 0f39078491..68ca6fd876 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "sharkey",
- "version": "2025.1.0-dev",
+ "version": "2025.2.0-dev",
"codename": "shonk",
"repository": {
"type": "git",
diff --git a/packages/backend/migration/1709126576000-optimize-emoji-index.js b/packages/backend/migration/1709126576000-optimize-emoji-index.js
new file mode 100644
index 0000000000..e4184895d0
--- /dev/null
+++ b/packages/backend/migration/1709126576000-optimize-emoji-index.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class OptimizeEmojiIndex1709126576000 {
+ name = 'OptimizeEmojiIndex1709126576000'
+
+ async up(queryRunner) {
+ await queryRunner.query(`CREATE INDEX "IDX_EMOJI_ROLE_IDS" ON "emoji" using gin ("roleIdsThatCanBeUsedThisEmojiAsReaction")`)
+ await queryRunner.query(`CREATE INDEX "IDX_EMOJI_CATEGORY" ON "emoji" ("category")`)
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_EMOJI_CATEGORY"`)
+ await queryRunner.query(`DROP INDEX "IDX_EMOJI_ROLE_IDS"`)
+ }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index bace8c1f96..a8566e30d9 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -164,7 +164,6 @@
"proxy-addr": "^2.0.7",
"psl": "^1.13.0",
"pug": "3.0.3",
- "punycode": "2.3.1",
"qrcode": "1.5.4",
"random-seed": "0.3.0",
"ratelimiter": "3.4.1",
@@ -223,7 +222,6 @@
"@types/pg": "8.11.10",
"@types/proxy-addr": "^2.0.3",
"@types/pug": "2.0.10",
- "@types/punycode": "2.1.4",
"@types/qrcode": "1.5.5",
"@types/random-seed": "0.3.5",
"@types/ratelimiter": "3.4.6",
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 6ae8ccfbb3..ace7f7841c 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -7,14 +7,14 @@ import { Global, Inject, Module } from '@nestjs/common';
import * as Redis from 'ioredis';
import { DataSource } from 'typeorm';
import { MeiliSearch } from 'meilisearch';
+import { MiMeta } from '@/models/Meta.js';
import { DI } from './di-symbols.js';
import { Config, loadConfig } from './config.js';
import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js';
import { allSettled } from './misc/promise-tracker.js';
-import type { Provider, OnApplicationShutdown } from '@nestjs/common';
-import { MiMeta } from '@/models/Meta.js';
import { GlobalEvents } from './core/GlobalEventService.js';
+import type { Provider, OnApplicationShutdown } from '@nestjs/common';
const $config: Provider = {
provide: DI.config,
@@ -33,7 +33,11 @@ const $db: Provider = {
const $meilisearch: Provider = {
provide: DI.meilisearch,
useFactory: (config: Config) => {
- if (config.meilisearch) {
+ if (config.fulltextSearch?.provider === 'meilisearch') {
+ if (!config.meilisearch) {
+ throw new Error('MeiliSearch is enabled but no configuration is provided');
+ }
+
return new MeiliSearch({
host: `${config.meilisearch.ssl ? 'https' : 'http'}://${config.meilisearch.host}:${config.meilisearch.port}`,
apiKey: config.meilisearch.apiKey,
diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts
index 56128a7ab9..735a0f4666 100644
--- a/packages/backend/src/boot/entry.ts
+++ b/packages/backend/src/boot/entry.ts
@@ -70,14 +70,22 @@ async function main() {
});
//#endregion
- if (cluster.isPrimary || envOption.disableClustering) {
- await masterMain();
+ if (!envOption.disableClustering) {
if (cluster.isPrimary) {
+ logger.info(`Start main process... pid: ${process.pid}`);
+ await masterMain();
ev.mount();
+ } else if (cluster.isWorker) {
+ logger.info(`Start worker process... pid: ${process.pid}`);
+ await workerMain();
+ } else {
+ throw new Error('Unknown process type');
}
- }
- if (cluster.isWorker) {
- await workerMain();
+ } else {
+ // éžclusterã®å ´åˆã¯Masterã®ã¿ãŒèµ·å‹•ã™ã‚‹ãŸã‚ã€Workerã®å‡¦ç†ã¯è¡Œã‚ãªã„(cluster.isWorker === trueã®çŠ¶æ…‹ã§ã“ã®ãƒ–ãƒ­ãƒƒã‚¯ã«æ¥ã‚‹ã“ã¨ã¯ãªã„)
+ logger.info(`Start main process... pid: ${process.pid}`);
+ await masterMain();
+ ev.mount();
}
readyRef.value = true;
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
index 355e095c12..7b3f3395e6 100644
--- a/packages/backend/src/boot/master.ts
+++ b/packages/backend/src/boot/master.ts
@@ -97,22 +97,22 @@ export async function masterMain() {
});
}
- if (envOption.disableClustering) {
+ bootLogger.info(
+ `mode: [disableClustering: ${envOption.disableClustering}, onlyServer: ${envOption.onlyServer}, onlyQueue: ${envOption.onlyQueue}]`,
+ );
+
+ if (!envOption.disableClustering) {
+ // clusterモジュール有効時
+
if (envOption.onlyServer) {
- await server();
+ // onlyServer ã‹ã¤ enableCluster ãªå ´åˆã€ãƒ¡ã‚¤ãƒ³ãƒ—ロセスã¯forkã®ã¿ã«åˆ¶é™ã™ã‚‹(listenã—ãªã„)。
+ // ワーカープロセスå´ã§listenã™ã‚‹ã¨ã€ãƒ¡ã‚¤ãƒ³ãƒ—ロセスã§ãƒãƒ¼ãƒˆã¸ã®ç€ä¿¡ã‚’å—ã‘入れã¦ãƒ¯ãƒ¼ã‚«ãƒ¼ãƒ—ロセスã¸ã®åˆ†é…を行ã†å‹•作をã™ã‚‹ã€‚
+ // ãã®ãŸã‚ã€ãƒ¡ã‚¤ãƒ³ãƒ—ロセスã§ã‚‚直接listenã™ã‚‹ã¨ãƒãƒ¼ãƒˆã®ç«¶åˆãŒç™ºç”Ÿã—ã¦èµ·å‹•ã«å¤±æ•—ã—ã¦ã—ã¾ã†ã€‚
+ // see: https://nodejs.org/api/cluster.html#cluster
} else if (envOption.onlyQueue) {
await jobQueue();
} else {
await server();
- await jobQueue();
- }
- } else {
- if (envOption.onlyServer) {
- // nop
- } else if (envOption.onlyQueue) {
- // nop
- } else {
- await server();
}
if (config.clusterLimit === 0) {
@@ -121,6 +121,17 @@ export async function masterMain() {
}
await spawnWorkers(config.clusterLimit);
+ } else {
+ // clusterモジュール無効時
+
+ if (envOption.onlyServer) {
+ await server();
+ } else if (envOption.onlyQueue) {
+ await jobQueue();
+ } else {
+ await server();
+ await jobQueue();
+ }
}
if (envOption.onlyQueue) {
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index 4af1140f36..e980b40224 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -52,6 +52,9 @@ type Source = {
redisForJobQueue?: RedisOptionsSource;
redisForTimelines?: RedisOptionsSource;
redisForReactions?: RedisOptionsSource;
+ fulltextSearch?: {
+ provider?: FulltextSearchProvider;
+ };
meilisearch?: {
host: string;
port: string;
@@ -118,6 +121,13 @@ type Source = {
pidFile: string;
filePermissionBits?: string;
+
+ logging?: {
+ sql?: {
+ disableQueryTruncation? : boolean,
+ enableQueryParamLogging? : boolean,
+ }
+ }
};
export type Config = {
@@ -143,6 +153,9 @@ export type Config = {
user: string;
pass: string;
}[] | undefined;
+ fulltextSearch?: {
+ provider?: FulltextSearchProvider;
+ };
meilisearch: {
host: string;
port: string;
@@ -179,6 +192,12 @@ export type Config = {
signToActivityPubGet: boolean;
attachLdSignatureForRelays: boolean;
checkActivityPubGetSignature: boolean | undefined;
+ logging?: {
+ sql?: {
+ disableQueryTruncation? : boolean,
+ enableQueryParamLogging? : boolean,
+ }
+ }
version: string;
publishTarballInsteadOfProvideRepositoryUrl: boolean;
@@ -219,6 +238,8 @@ export type Config = {
filePermissionBits?: string;
};
+export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch';
+
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@@ -302,6 +323,7 @@ export function loadConfig(): Config {
db: { ...config.db, db: dbDb, user: dbUser, pass: dbPass },
dbReplications: config.dbReplications,
dbSlaves: config.dbSlaves,
+ fulltextSearch: config.fulltextSearch,
meilisearch: config.meilisearch,
redis,
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
@@ -354,6 +376,7 @@ export function loadConfig(): Config {
import: config.import,
pidFile: config.pidFile,
filePermissionBits: config.filePermissionBits,
+ logging: config.logging,
};
}
diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts
index adb0a63ad7..e2c492ff80 100644
--- a/packages/backend/src/const.ts
+++ b/packages/backend/src/const.ts
@@ -8,6 +8,18 @@ export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
export const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
+export const FILE_TYPE_IMAGE = [
+ 'image/png',
+ 'image/gif',
+ 'image/jpeg',
+ 'image/webp',
+ 'image/avif',
+ 'image/apng',
+ 'image/bmp',
+ 'image/tiff',
+ 'image/x-icon',
+];
+
// ブラウザã§ç›´æŽ¥è¡¨ç¤ºã™ã‚‹ã“ã¨ã‚’許å¯ã™ã‚‹ãƒ•ァイルã®ç¨®é¡žã®ãƒªã‚¹ãƒˆ
// ã“ã“ã«å«ã¾ã‚Œãªã„ã‚‚ã®ã¯ application/octet-stream ã¨ã—ã¦ãƒ¬ã‚¹ãƒãƒ³ã‚¹ã•れる
// SVGã¯XSSを生むã®ã§è¨±å¯ã—ãªã„
diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts
index 742e2621fd..9bca795479 100644
--- a/packages/backend/src/core/AbuseReportNotificationService.ts
+++ b/packages/backend/src/core/AbuseReportNotificationService.ts
@@ -160,22 +160,22 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
};
});
- const recipientWebhookIds = await this.fetchWebhookRecipients()
- .then(it => it
- .filter(it => it.isActive && it.systemWebhookId && it.method === 'webhook')
- .map(it => it.systemWebhookId)
- .filter(x => x != null));
- for (const webhookId of recipientWebhookIds) {
- await Promise.all(
- convertedReports.map(it => {
- return this.systemWebhookService.enqueueSystemWebhook(
- webhookId,
- type,
- it,
- );
- }),
- );
- }
+ const inactiveRecipients = await this.fetchWebhookRecipients()
+ .then(it => it.filter(it => !it.isActive));
+ const withoutWebhookIds = inactiveRecipients
+ .map(it => it.systemWebhookId)
+ .filter(x => x != null);
+ return Promise.all(
+ convertedReports.map(it => {
+ return this.systemWebhookService.enqueueSystemWebhook(
+ type,
+ it,
+ {
+ excludes: withoutWebhookIds,
+ },
+ );
+ }),
+ );
}
/**
diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/packages/backend/src/core/AiService.ts
diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts
index 5b1ab00cfe..79aa722fe5 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -6,6 +6,65 @@
import { Injectable } from '@nestjs/common';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
+import { MetaService } from '@/core/MetaService.js';
+import { MiMeta } from '@/models/Meta.js';
+import Logger from '@/logger.js';
+import { LoggerService } from './LoggerService.js';
+
+export const supportedCaptchaProviders = ['none', 'hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile', 'testcaptcha'] as const;
+export type CaptchaProvider = typeof supportedCaptchaProviders[number];
+
+export const captchaErrorCodes = {
+ invalidProvider: Symbol('invalidProvider'),
+ invalidParameters: Symbol('invalidParameters'),
+ noResponseProvided: Symbol('noResponseProvided'),
+ requestFailed: Symbol('requestFailed'),
+ verificationFailed: Symbol('verificationFailed'),
+ unknown: Symbol('unknown'),
+} as const;
+export type CaptchaErrorCode = typeof captchaErrorCodes[keyof typeof captchaErrorCodes];
+
+export type CaptchaSetting = {
+ provider: CaptchaProvider;
+ hcaptcha: {
+ siteKey: string | null;
+ secretKey: string | null;
+ }
+ mcaptcha: {
+ siteKey: string | null;
+ secretKey: string | null;
+ instanceUrl: string | null;
+ }
+ recaptcha: {
+ siteKey: string | null;
+ secretKey: string | null;
+ }
+ turnstile: {
+ siteKey: string | null;
+ secretKey: string | null;
+ }
+}
+
+export class CaptchaError extends Error {
+ public readonly code: CaptchaErrorCode;
+ public readonly cause?: unknown;
+
+ constructor(code: CaptchaErrorCode, message: string, cause?: unknown) {
+ super(message);
+ this.code = code;
+ this.cause = cause;
+ this.name = 'CaptchaError';
+ }
+}
+
+export type CaptchaSaveSuccess = {
+ success: true;
+}
+export type CaptchaSaveFailure = {
+ success: false;
+ error: CaptchaError;
+}
+export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure;
type CaptchaResponse = {
success: boolean;
@@ -15,9 +74,14 @@ type CaptchaResponse = {
@Injectable()
export class CaptchaService {
+ private readonly logger: Logger;
+
constructor(
private httpRequestService: HttpRequestService,
+ private metaService: MetaService,
+ loggerService: LoggerService,
) {
+ this.logger = loggerService.getLogger('captcha');
}
@bindThis
@@ -45,32 +109,32 @@ export class CaptchaService {
@bindThis
public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
- throw new Error('recaptcha-failed: no response provided');
+ throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'recaptcha-failed: no response provided');
}
const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => {
- throw new Error(`recaptcha-request-failed: ${err}`);
+ throw new CaptchaError(captchaErrorCodes.requestFailed, `recaptcha-request-failed: ${err}`);
});
if (result.success !== true) {
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
- throw new Error(`recaptcha-failed: ${errorCodes}`);
+ throw new CaptchaError(captchaErrorCodes.verificationFailed, `recaptcha-failed: ${errorCodes}`);
}
}
@bindThis
public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
- throw new Error('hcaptcha-failed: no response provided');
+ throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'hcaptcha-failed: no response provided');
}
const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => {
- throw new Error(`hcaptcha-request-failed: ${err}`);
+ throw new CaptchaError(captchaErrorCodes.requestFailed, `hcaptcha-request-failed: ${err}`);
});
if (result.success !== true) {
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
- throw new Error(`hcaptcha-failed: ${errorCodes}`);
+ throw new CaptchaError(captchaErrorCodes.verificationFailed, `hcaptcha-failed: ${errorCodes}`);
}
}
@@ -107,7 +171,7 @@ export class CaptchaService {
@bindThis
public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise<void> {
if (response == null) {
- throw new Error('mcaptcha-failed: no response provided');
+ throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'mcaptcha-failed: no response provided');
}
const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost);
@@ -121,46 +185,251 @@ export class CaptchaService {
headers: {
'Content-Type': 'application/json',
},
- });
+ }, { throwErrorWhenResponseNotOk: false });
if (result.status !== 200) {
- throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK');
+ throw new CaptchaError(captchaErrorCodes.requestFailed, 'mcaptcha-failed: mcaptcha didn\'t return 200 OK');
}
const resp = (await result.json()) as { valid: boolean };
if (!resp.valid) {
- throw new Error('mcaptcha-request-failed');
+ throw new CaptchaError(captchaErrorCodes.verificationFailed, 'mcaptcha-request-failed');
}
}
@bindThis
public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
if (response == null) {
- throw new Error('turnstile-failed: no response provided');
+ throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'turnstile-failed: no response provided');
}
const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => {
- throw new Error(`turnstile-request-failed: ${err}`);
+ throw new CaptchaError(captchaErrorCodes.requestFailed, `turnstile-request-failed: ${err}`);
});
if (result.success !== true) {
const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
- throw new Error(`turnstile-failed: ${errorCodes}`);
+ throw new CaptchaError(captchaErrorCodes.verificationFailed, `turnstile-failed: ${errorCodes}`);
}
}
@bindThis
public async verifyTestcaptcha(response: string | null | undefined): Promise<void> {
if (response == null) {
- throw new Error('testcaptcha-failed: no response provided');
+ throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'testcaptcha-failed: no response provided');
}
const success = response === 'testcaptcha-passed';
if (!success) {
- throw new Error('testcaptcha-failed');
+ throw new CaptchaError(captchaErrorCodes.verificationFailed, 'testcaptcha-failed');
+ }
+ }
+
+ @bindThis
+ public async get(): Promise<CaptchaSetting> {
+ const meta = await this.metaService.fetch(true);
+
+ let provider: CaptchaProvider;
+ switch (true) {
+ case meta.enableHcaptcha: {
+ provider = 'hcaptcha';
+ break;
+ }
+ case meta.enableMcaptcha: {
+ provider = 'mcaptcha';
+ break;
+ }
+ case meta.enableRecaptcha: {
+ provider = 'recaptcha';
+ break;
+ }
+ case meta.enableTurnstile: {
+ provider = 'turnstile';
+ break;
+ }
+ case meta.enableTestcaptcha: {
+ provider = 'testcaptcha';
+ break;
+ }
+ default: {
+ provider = 'none';
+ break;
+ }
+ }
+
+ return {
+ provider: provider,
+ hcaptcha: {
+ siteKey: meta.hcaptchaSiteKey,
+ secretKey: meta.hcaptchaSecretKey,
+ },
+ mcaptcha: {
+ siteKey: meta.mcaptchaSitekey,
+ secretKey: meta.mcaptchaSecretKey,
+ instanceUrl: meta.mcaptchaInstanceUrl,
+ },
+ recaptcha: {
+ siteKey: meta.recaptchaSiteKey,
+ secretKey: meta.recaptchaSecretKey,
+ },
+ turnstile: {
+ siteKey: meta.turnstileSiteKey,
+ secretKey: meta.turnstileSecretKey,
+ },
+ };
+ }
+
+ /**
+ * captchaã®è¨­å®šã‚’æ›´æ–°ã—ã¾ã™. ãã®éš›ã€ãƒ•ロントエンドå´ã§å—ã‘å–ã£ãŸcaptchaã‹ã‚‰ã®æˆ»ã‚Šå€¤ã‚’検証ã—ã€passã—ãŸå ´åˆã®ã¿è¨­å®šã‚’æ›´æ–°ã—ã¾ã™.
+ * å®Ÿéš›ã®æ¤œè¨¼å‡¦ç†ã¯ã‚µãƒ¼ãƒ“ス内ã§å®šç¾©ã•れã¦ã„ã‚‹å„captchaプロãƒã‚¤ãƒ€ã®æ¤œè¨¼é–¢æ•°ã«å§”è­²ã—ã¾ã™.
+ *
+ * @param provider 検証ã™ã‚‹captchaã®ãƒ—ロãƒã‚¤ãƒ€
+ * @param params
+ * @param params.sitekey hcaptcha, recaptcha, turnstile, mcaptchaã®å ´åˆã«æŒ‡å®šã™ã‚‹sitekey. ãれ以外ã®ãƒ—ロãƒã‚¤ãƒ€ã§ã¯ç„¡è¦–ã•れã¾ã™
+ * @param params.secret hcaptcha, recaptcha, turnstile, mcaptchaã®å ´åˆã«æŒ‡å®šã™ã‚‹secret. ãれ以外ã®ãƒ—ロãƒã‚¤ãƒ€ã§ã¯ç„¡è¦–ã•れã¾ã™
+ * @param params.instanceUrl mcaptchaã®å ´åˆã«æŒ‡å®šã™ã‚‹ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã®URL. ãれ以外ã®ãƒ—ロãƒã‚¤ãƒ€ã§ã¯ç„¡è¦–ã•れã¾ã™
+ * @param params.captchaResult フロントエンドå´ã§å—ã‘å–ã£ãŸcaptchaプロãƒã‚¤ãƒ€ã‹ã‚‰ã®æˆ»ã‚Šå€¤. ã“ã®å€¤ã‚’使ã£ã¦ã‚µãƒ¼ãƒã‚µã‚¤ãƒ‰ã§ã®æ¤œè¨¼ã‚’行ã„ã¾ã™
+ * @see verifyHcaptcha
+ * @see verifyMcaptcha
+ * @see verifyRecaptcha
+ * @see verifyTurnstile
+ * @see verifyTestcaptcha
+ */
+ @bindThis
+ public async save(
+ provider: CaptchaProvider,
+ params?: {
+ sitekey?: string | null;
+ secret?: string | null;
+ instanceUrl?: string | null;
+ captchaResult?: string | null;
+ },
+ ): Promise<CaptchaSaveResult> {
+ if (!supportedCaptchaProviders.includes(provider)) {
+ return {
+ success: false,
+ error: new CaptchaError(captchaErrorCodes.invalidProvider, `Invalid captcha provider: ${provider}`),
+ };
+ }
+
+ const operation = {
+ none: async () => {
+ await this.updateMeta(provider, params);
+ },
+ hcaptcha: async () => {
+ if (!params?.secret || !params.captchaResult) {
+ throw new CaptchaError(captchaErrorCodes.invalidParameters, 'hcaptcha-failed: secret and captureResult are required');
+ }
+
+ await this.verifyHcaptcha(params.secret, params.captchaResult);
+ await this.updateMeta(provider, params);
+ },
+ mcaptcha: async () => {
+ if (!params?.secret || !params.sitekey || !params.instanceUrl || !params.captchaResult) {
+ throw new CaptchaError(captchaErrorCodes.invalidParameters, 'mcaptcha-failed: secret, sitekey, instanceUrl and captureResult are required');
+ }
+
+ await this.verifyMcaptcha(params.secret, params.sitekey, params.instanceUrl, params.captchaResult);
+ await this.updateMeta(provider, params);
+ },
+ recaptcha: async () => {
+ if (!params?.secret || !params.captchaResult) {
+ throw new CaptchaError(captchaErrorCodes.invalidParameters, 'recaptcha-failed: secret and captureResult are required');
+ }
+
+ await this.verifyRecaptcha(params.secret, params.captchaResult);
+ await this.updateMeta(provider, params);
+ },
+ turnstile: async () => {
+ if (!params?.secret || !params.captchaResult) {
+ throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: secret and captureResult are required');
+ }
+
+ await this.verifyTurnstile(params.secret, params.captchaResult);
+ await this.updateMeta(provider, params);
+ },
+ testcaptcha: async () => {
+ if (!params?.captchaResult) {
+ throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: captureResult are required');
+ }
+
+ await this.verifyTestcaptcha(params.captchaResult);
+ await this.updateMeta(provider, params);
+ },
+ }[provider];
+
+ return operation()
+ .then(() => ({ success: true }) as CaptchaSaveSuccess)
+ .catch(err => {
+ this.logger.info(err);
+ const error = err instanceof CaptchaError
+ ? err
+ : new CaptchaError(captchaErrorCodes.unknown, `unknown error: ${err}`);
+ return {
+ success: false,
+ error,
+ };
+ });
+ }
+
+ @bindThis
+ private async updateMeta(
+ provider: CaptchaProvider,
+ params?: {
+ sitekey?: string | null;
+ secret?: string | null;
+ instanceUrl?: string | null;
+ },
+ ) {
+ const metaPartial: Partial<
+ Pick<
+ MiMeta,
+ ('enableHcaptcha' | 'hcaptchaSiteKey' | 'hcaptchaSecretKey') |
+ ('enableMcaptcha' | 'mcaptchaSitekey' | 'mcaptchaSecretKey' | 'mcaptchaInstanceUrl') |
+ ('enableRecaptcha' | 'recaptchaSiteKey' | 'recaptchaSecretKey') |
+ ('enableTurnstile' | 'turnstileSiteKey' | 'turnstileSecretKey') |
+ ('enableTestcaptcha')
+ >
+ > = {
+ enableHcaptcha: provider === 'hcaptcha',
+ enableMcaptcha: provider === 'mcaptcha',
+ enableRecaptcha: provider === 'recaptcha',
+ enableTurnstile: provider === 'turnstile',
+ enableTestcaptcha: provider === 'testcaptcha',
+ };
+
+ const updateIfNotUndefined = <K extends keyof typeof metaPartial>(key: K, value: typeof metaPartial[K]) => {
+ if (value !== undefined) {
+ metaPartial[key] = value;
+ }
+ };
+ switch (provider) {
+ case 'hcaptcha': {
+ updateIfNotUndefined('hcaptchaSiteKey', params?.sitekey);
+ updateIfNotUndefined('hcaptchaSecretKey', params?.secret);
+ break;
+ }
+ case 'mcaptcha': {
+ updateIfNotUndefined('mcaptchaSitekey', params?.sitekey);
+ updateIfNotUndefined('mcaptchaSecretKey', params?.secret);
+ updateIfNotUndefined('mcaptchaInstanceUrl', params?.instanceUrl);
+ break;
+ }
+ case 'recaptcha': {
+ updateIfNotUndefined('recaptchaSiteKey', params?.sitekey);
+ updateIfNotUndefined('recaptchaSecretKey', params?.secret);
+ break;
+ }
+ case 'turnstile': {
+ updateIfNotUndefined('turnstileSiteKey', params?.sitekey);
+ updateIfNotUndefined('turnstileSecretKey', params?.secret);
+ break;
+ }
}
+
+ await this.metaService.update(metaPartial);
}
}
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index cc33fb5c0b..3f7ad5b947 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -4,19 +4,18 @@
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
-import { In, IsNull } from 'typeorm';
import * as Redis from 'ioredis';
-import { DI } from '@/di-symbols.js';
-import { IdService } from '@/core/IdService.js';
+import { In, IsNull } from 'typeorm';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
-import type { MiDriveFile } from '@/models/DriveFile.js';
-import type { MiEmoji } from '@/models/Emoji.js';
-import type { DriveFilesRepository, EmojisRepository, MiRole, MiUser } from '@/models/_.js';
+import { IdService } from '@/core/IdService.js';
+import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
+import { DI } from '@/di-symbols.js';
import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
-import { UtilityService } from '@/core/UtilityService.js';
-import { query } from '@/misc/prelude/url.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
+import type { DriveFilesRepository, EmojisRepository, MiRole, MiUser } from '@/models/_.js';
+import type { MiEmoji } from '@/models/Emoji.js';
import type { Serialized } from '@/types.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Config } from '@/config.js';
@@ -24,6 +23,42 @@ import { DriveService } from './DriveService.js';
const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
+export const fetchEmojisHostTypes = [
+ 'local',
+ 'remote',
+ 'all',
+] as const;
+export type FetchEmojisHostTypes = typeof fetchEmojisHostTypes[number];
+export const fetchEmojisSortKeys = [
+ '+id',
+ '-id',
+ '+updatedAt',
+ '-updatedAt',
+ '+name',
+ '-name',
+ '+host',
+ '-host',
+ '+uri',
+ '-uri',
+ '+publicUrl',
+ '-publicUrl',
+ '+type',
+ '-type',
+ '+aliases',
+ '-aliases',
+ '+category',
+ '-category',
+ '+license',
+ '-license',
+ '+isSensitive',
+ '-isSensitive',
+ '+localOnly',
+ '-localOnly',
+ '+roleIdsThatCanBeUsedThisEmojiAsReaction',
+ '-roleIdsThatCanBeUsedThisEmojiAsReaction',
+] as const;
+export type FetchEmojisSortKeys = typeof fetchEmojisSortKeys[number];
+
@Injectable()
export class CustomEmojiService implements OnApplicationShutdown {
private emojisCache: MemoryKVCache<MiEmoji | null>;
@@ -32,16 +67,12 @@ export class CustomEmojiService implements OnApplicationShutdown {
constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
-
@Inject(DI.config)
private config: Config,
-
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
-
private utilityService: UtilityService,
private idService: IdService,
private emojiEntityService: EmojiEntityService,
@@ -67,7 +98,9 @@ export class CustomEmojiService implements OnApplicationShutdown {
@bindThis
public async add(data: {
- driveFile: MiDriveFile;
+ originalUrl: string;
+ publicUrl: string;
+ fileType: string;
name: string;
category: string | null;
aliases: string[];
@@ -84,9 +117,9 @@ export class CustomEmojiService implements OnApplicationShutdown {
category: data.category,
host: data.host,
aliases: data.aliases,
- originalUrl: data.driveFile.url,
- publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
- type: data.driveFile.webpublicType ?? data.driveFile.type,
+ originalUrl: data.originalUrl,
+ publicUrl: data.publicUrl,
+ type: data.fileType,
license: data.license,
isSensitive: data.isSensitive,
localOnly: data.localOnly,
@@ -114,8 +147,10 @@ export class CustomEmojiService implements OnApplicationShutdown {
@bindThis
public async update(data: (
{ id: MiEmoji['id'], name?: string; } | { name: string; id?: MiEmoji['id'], }
- ) & {
- driveFile?: MiDriveFile;
+ ) & {
+ originalUrl?: string;
+ publicUrl?: string;
+ fileType?: string;
category?: string | null;
aliases?: string[];
license?: string | null;
@@ -140,6 +175,17 @@ export class CustomEmojiService implements OnApplicationShutdown {
if (isDuplicate) return 'SAME_NAME_EMOJI_EXISTS';
}
+ // If we're changing the file, then we need to delete the old one
+ if (data.originalUrl != null && data.originalUrl !== emoji.originalUrl) {
+ const oldFile = await this.driveFilesRepository.findOneBy({ url: emoji.originalUrl, userHost: emoji.host ? emoji.host : IsNull() });
+ const newFile = await this.driveFilesRepository.findOneBy({ url: data.originalUrl, userHost: emoji.host ? emoji.host : IsNull() });
+
+ // But DON'T delete if this is the same file reference, otherwise we'll break the emoji!
+ if (oldFile && newFile && oldFile.id !== newFile.id) {
+ await this.driveService.deleteFile(oldFile, false, moderator ? moderator : undefined);
+ }
+ }
+
await this.emojisRepository.update(emoji.id, {
updatedAt: new Date(),
name: data.name,
@@ -148,21 +194,14 @@ export class CustomEmojiService implements OnApplicationShutdown {
license: data.license,
isSensitive: data.isSensitive,
localOnly: data.localOnly,
- originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
- publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
- type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
+ originalUrl: data.originalUrl,
+ publicUrl: data.publicUrl,
+ type: data.fileType,
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
});
this.localEmojisCache.refresh();
- if (data.driveFile != null) {
- const file = await this.driveFilesRepository.findOneBy({ url: emoji.originalUrl, userHost: emoji.host ? emoji.host : IsNull() });
- if (file && file.id !== data.driveFile.id) {
- await this.driveService.deleteFile(file, false, moderator ? moderator : undefined);
- }
- }
-
const packed = await this.emojiEntityService.packDetailed(emoji.id);
if (!doNameUpdate) {
@@ -336,7 +375,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
@bindThis
private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null {
- // クエリã«ä½¿ã†ãƒ›ã‚¹ãƒˆ
+ // クエリã«ä½¿ã†ãƒ›ã‚¹ãƒˆ
let host = src === '.' ? null // .ã¯ãƒ­ãƒ¼ã‚«ãƒ«ãƒ›ã‚¹ãƒˆ (ã“ã“ãŒãƒžãƒƒãƒã™ã‚‹ã®ã¯ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã®ã¿)
: src === undefined ? noteUserHost // ノートãªã©ã§ãƒ›ã‚¹ãƒˆçœç•¥è¡¨è¨˜ã®å ´åˆã¯ãƒ­ãƒ¼ã‚«ãƒ«ãƒ›ã‚¹ãƒˆ (ã“ã“ãŒãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã«ãƒžãƒƒãƒã™ã‚‹ã“ã¨ã¯ãªã„)
: this.utilityService.isSelfHost(src) ? null // 自ホスト指定
@@ -445,6 +484,151 @@ export class CustomEmojiService implements OnApplicationShutdown {
}
@bindThis
+ public async fetchEmojis(
+ params?: {
+ query?: {
+ updatedAtFrom?: string;
+ updatedAtTo?: string;
+ name?: string;
+ host?: string;
+ uri?: string;
+ publicUrl?: string;
+ type?: string;
+ aliases?: string;
+ category?: string;
+ license?: string;
+ isSensitive?: boolean;
+ localOnly?: boolean;
+ hostType?: FetchEmojisHostTypes;
+ roleIds?: string[];
+ },
+ sinceId?: string;
+ untilId?: string;
+ },
+ opts?: {
+ limit?: number;
+ page?: number;
+ sortKeys?: FetchEmojisSortKeys[]
+ },
+ ) {
+ function multipleWordsToQuery(words: string) {
+ return words.split(/\s/).filter(x => x.length > 0).map(x => `%${sqlLikeEscape(x)}%`);
+ }
+
+ const builder = this.emojisRepository.createQueryBuilder('emoji');
+ if (params?.query) {
+ const q = params.query;
+ if (q.updatedAtFrom) {
+ // noIndexScan
+ builder.andWhere('CAST(emoji.updatedAt AS DATE) >= :updateAtFrom', { updateAtFrom: q.updatedAtFrom });
+ }
+ if (q.updatedAtTo) {
+ // noIndexScan
+ builder.andWhere('CAST(emoji.updatedAt AS DATE) <= :updateAtTo', { updateAtTo: q.updatedAtTo });
+ }
+ if (q.name) {
+ builder.andWhere('emoji.name ~~ ANY(ARRAY[:...name])', { name: multipleWordsToQuery(q.name) });
+ }
+
+ switch (true) {
+ case q.hostType === 'local': {
+ builder.andWhere('emoji.host IS NULL');
+ break;
+ }
+ case q.hostType === 'remote': {
+ if (q.host) {
+ // noIndexScan
+ builder.andWhere('emoji.host ~~ ANY(ARRAY[:...host])', { host: multipleWordsToQuery(q.host) });
+ } else {
+ builder.andWhere('emoji.host IS NOT NULL');
+ }
+ break;
+ }
+ }
+
+ if (q.uri) {
+ // noIndexScan
+ builder.andWhere('emoji.uri ~~ ANY(ARRAY[:...uri])', { uri: multipleWordsToQuery(q.uri) });
+ }
+ if (q.publicUrl) {
+ // noIndexScan
+ builder.andWhere('emoji.publicUrl ~~ ANY(ARRAY[:...publicUrl])', { publicUrl: multipleWordsToQuery(q.publicUrl) });
+ }
+ if (q.type) {
+ // noIndexScan
+ builder.andWhere('emoji.type ~~ ANY(ARRAY[:...type])', { type: multipleWordsToQuery(q.type) });
+ }
+ if (q.aliases) {
+ // noIndexScan
+ const subQueryBuilder = builder.subQuery()
+ .select('COUNT(0)', 'count')
+ .from(
+ sq2 => sq2
+ .select('unnest(subEmoji.aliases)', 'alias')
+ .addSelect('subEmoji.id', 'id')
+ .from('emoji', 'subEmoji'),
+ 'aliasTable',
+ )
+ .where('"emoji"."id" = "aliasTable"."id"')
+ .andWhere('"aliasTable"."alias" ~~ ANY(ARRAY[:...aliases])', { aliases: multipleWordsToQuery(q.aliases) });
+
+ builder.andWhere(`(${subQueryBuilder.getQuery()}) > 0`);
+ }
+ if (q.category) {
+ builder.andWhere('emoji.category ~~ ANY(ARRAY[:...category])', { category: multipleWordsToQuery(q.category) });
+ }
+ if (q.license) {
+ // noIndexScan
+ builder.andWhere('emoji.license ~~ ANY(ARRAY[:...license])', { license: multipleWordsToQuery(q.license) });
+ }
+ if (q.isSensitive != null) {
+ // noIndexScan
+ builder.andWhere('emoji.isSensitive = :isSensitive', { isSensitive: q.isSensitive });
+ }
+ if (q.localOnly != null) {
+ // noIndexScan
+ builder.andWhere('emoji.localOnly = :localOnly', { localOnly: q.localOnly });
+ }
+ if (q.roleIds && q.roleIds.length > 0) {
+ builder.andWhere('emoji.roleIdsThatCanBeUsedThisEmojiAsReaction && ARRAY[:...roleIds]::VARCHAR[]', { roleIds: q.roleIds });
+ }
+ }
+
+ if (params?.sinceId) {
+ builder.andWhere('emoji.id > :sinceId', { sinceId: params.sinceId });
+ }
+ if (params?.untilId) {
+ builder.andWhere('emoji.id < :untilId', { untilId: params.untilId });
+ }
+
+ if (opts?.sortKeys && opts.sortKeys.length > 0) {
+ for (const sortKey of opts.sortKeys) {
+ const direction = sortKey.startsWith('-') ? 'DESC' : 'ASC';
+ const key = sortKey.replace(/^[+-]/, '');
+ builder.addOrderBy(`emoji.${key}`, direction);
+ }
+ } else {
+ builder.addOrderBy('emoji.id', 'DESC');
+ }
+
+ const limit = opts?.limit ?? 10;
+ if (opts?.page) {
+ builder.skip((opts.page - 1) * limit);
+ }
+
+ builder.take(limit);
+
+ const [emojis, count] = await builder.getManyAndCount();
+
+ return {
+ emojis,
+ count: (count > limit ? emojis.length : count),
+ allCount: count,
+ allPages: Math.ceil(count / limit),
+ };
+ }
+
+ @bindThis
public dispose(): void {
this.emojisCache.dispose();
}
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index 987999bce7..ce3af7c774 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -181,7 +181,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async fetchDom(instance: MiInstance): Promise<DOMWindow['document']> {
+ private async fetchDom(instance: MiInstance): Promise<Document> {
this.logger.info(`Fetching HTML of ${instance.host} ...`);
const url = 'https://' + instance.host;
@@ -206,7 +206,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async fetchFaviconUrl(instance: MiInstance, doc: DOMWindow['document'] | null): Promise<string | null> {
+ private async fetchFaviconUrl(instance: MiInstance, doc: Document | null): Promise<string | null> {
const url = 'https://' + instance.host;
if (doc) {
@@ -232,7 +232,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async fetchIconUrl(instance: MiInstance, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
+ private async fetchIconUrl(instance: MiInstance, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
const url = 'https://' + instance.host;
return (new URL(manifest.icons[0].src, url)).href;
@@ -261,7 +261,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async getThemeColor(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
+ private async getThemeColor(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
const themeColor = info?.metadata?.themeColor ?? doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') ?? manifest?.theme_color;
if (themeColor) {
@@ -273,7 +273,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
+ private async getSiteName(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
if (info && info.metadata) {
if (typeof info.metadata.nodeName === 'string') {
return info.metadata.nodeName;
@@ -298,7 +298,7 @@ export class FetchInstanceMetadataService {
}
@bindThis
- private async getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
+ private async getDescription(info: NodeInfo | null, doc: Document | null, manifest: Record<string, any> | null): Promise<string | null> {
if (info && info.metadata) {
if (typeof info.metadata.nodeDescription === 'string') {
return info.metadata.nodeDescription;
diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts
index cc66e9fe3a..dc4483ad3f 100644
--- a/packages/backend/src/core/FileInfoService.ts
+++ b/packages/backend/src/core/FileInfoService.ts
@@ -16,6 +16,7 @@ import * as blurhash from 'blurhash';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
+import type { PredictionType } from 'nsfwjs';
export type FileInfo = {
size: number;
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 42676d6f98..1aca3737b3 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -179,6 +179,39 @@ export class MfmService {
break;
}
+ case 'ruby': {
+ let ruby: [string, string][] = [];
+ for (const child of node.childNodes) {
+ if (child.nodeName === 'rp') {
+ continue;
+ }
+ if (treeAdapter.isTextNode(child) && !/\s|\[|\]/.test(child.value)) {
+ ruby.push([child.value, '']);
+ continue;
+ }
+ if (child.nodeName === 'rt' && ruby.length > 0) {
+ const rt = getText(child);
+ if (/\s|\[|\]/.test(rt)) {
+ // If any space is included in rt, it is treated as a normal text
+ ruby = [];
+ appendChildren(node.childNodes);
+ break;
+ } else {
+ ruby.at(-1)![1] = rt;
+ continue;
+ }
+ }
+ // If any other element is included in ruby, it is treated as a normal text
+ ruby = [];
+ appendChildren(node.childNodes);
+ break;
+ }
+ for (const [base, rt] of ruby) {
+ text += `$[ruby ${base} ${rt}]`;
+ }
+ break;
+ }
+
// block code (<pre><code>)
case 'pre': {
if (node.childNodes.length === 1 && node.childNodes[0].nodeName === 'code') {
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 96bb30a0d6..3bfced1d80 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -678,14 +678,7 @@ export class NoteCreateService implements OnApplicationShutdown {
this.roleService.addNoteToRoleTimeline(noteObj);
- this.webhookService.getActiveWebhooks().then(webhooks => {
- webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'note', {
- note: noteObj,
- });
- }
- });
+ this.webhookService.enqueueUserWebhook(user.id, 'note', { note: noteObj });
const nm = new NotificationManager(this.mutingsRepository, this.notificationService, user, note);
@@ -717,13 +710,7 @@ export class NoteCreateService implements OnApplicationShutdown {
if (!isThreadMuted && !muted) {
nm.push(data.reply.userId, 'reply');
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
-
- const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'reply', {
- note: noteObj,
- });
- }
+ this.webhookService.enqueueUserWebhook(data.reply.userId, 'reply', { note: noteObj });
}
}
}
@@ -757,20 +744,14 @@ export class NoteCreateService implements OnApplicationShutdown {
// Publish event
if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
this.globalEventService.publishMainStream(data.renote.userId, 'renote', noteObj);
-
- const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'renote', {
- note: noteObj,
- });
- }
+ this.webhookService.enqueueUserWebhook(data.renote.userId, 'renote', { note: noteObj });
}
}
nm.notify();
//#region AP deliver
- if (this.userEntityService.isLocalUser(user)) {
+ if (!data.localOnly && this.userEntityService.isLocalUser(user)) {
(async () => {
const noteActivity = await this.renderNoteOrRenoteActivity(data, note);
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
@@ -905,13 +886,7 @@ export class NoteCreateService implements OnApplicationShutdown {
});
this.globalEventService.publishMainStream(u.id, 'mention', detailPackedNote);
-
- const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'mention', {
- note: detailPackedNote,
- });
- }
+ this.webhookService.enqueueUserWebhook(u.id, 'mention', { note: detailPackedNote });
// Create notification
nm.push(u.id, 'mention');
diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts
index bb2a463354..37721d2bf1 100644
--- a/packages/backend/src/core/S3Service.ts
+++ b/packages/backend/src/core/S3Service.ts
@@ -28,7 +28,7 @@ export class S3Service {
? `${meta.objectStorageUseSSL ? 'https' : 'http'}://${meta.objectStorageEndpoint}`
: `${meta.objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent
- const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy);
+ const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy, true);
const handlerOption: NodeHttpHandlerOptions = {};
if (meta.objectStorageUseSSL) {
handlerOption.httpsAgent = agent as https.Agent;
diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts
index 6dc3e85fc8..431cc0234e 100644
--- a/packages/backend/src/core/SearchService.ts
+++ b/packages/backend/src/core/SearchService.ts
@@ -6,16 +6,17 @@
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
+import { type Config, FulltextSearchProvider } from '@/config.js';
import { bindThis } from '@/decorators.js';
import { MiNote } from '@/models/Note.js';
-import { MiUser } from '@/models/_.js';
import type { NotesRepository } from '@/models/_.js';
+import { MiUser } from '@/models/_.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { CacheService } from '@/core/CacheService.js';
import { QueryService } from '@/core/QueryService.js';
import { IdService } from '@/core/IdService.js';
+import { LoggerService } from '@/core/LoggerService.js';
import type { Index, MeiliSearch } from 'meilisearch';
type K = string;
@@ -27,12 +28,27 @@ type Q =
{ op: '<', k: K, v: number } |
{ op: '>=', k: K, v: number } |
{ op: '<=', k: K, v: number } |
- { op: 'is null', k: K} |
- { op: 'is not null', k: K} |
+ { op: 'is null', k: K } |
+ { op: 'is not null', k: K } |
{ op: 'and', qs: Q[] } |
{ op: 'or', qs: Q[] } |
{ op: 'not', q: Q };
+export type SearchOpts = {
+ userId?: MiNote['userId'] | null;
+ channelId?: MiNote['channelId'] | null;
+ host?: string | null;
+ filetype?: string | null;
+ order?: string | null;
+ disableMeili?: boolean | null;
+};
+
+export type SearchPagination = {
+ untilId?: MiNote['id'];
+ sinceId?: MiNote['id'];
+ limit: number;
+};
+
function compileValue(value: V): string {
if (typeof value === 'string') {
return `'${value}'`; // TODO: escape
@@ -64,7 +80,8 @@ function compileQuery(q: Q): string {
@Injectable()
export class SearchService {
private readonly meilisearchIndexScope: 'local' | 'global' | string[] = 'local';
- private meilisearchNoteIndex: Index | null = null;
+ private readonly meilisearchNoteIndex: Index | null = null;
+ private readonly provider: FulltextSearchProvider;
constructor(
@Inject(DI.config)
@@ -79,6 +96,7 @@ export class SearchService {
private cacheService: CacheService,
private queryService: QueryService,
private idService: IdService,
+ private loggerService: LoggerService,
) {
if (meilisearch) {
this.meilisearchNoteIndex = meilisearch.index(`${this.config.meilisearch?.index}---notes`);
@@ -110,189 +128,202 @@ export class SearchService {
if (this.config.meilisearch?.scope) {
this.meilisearchIndexScope = this.config.meilisearch.scope;
}
+
+ this.provider = config.fulltextSearch?.provider ?? 'sqlLike';
+ this.loggerService.getLogger('SearchService').info(`-- Provider: ${this.provider}`);
}
@bindThis
public async indexNote(note: MiNote): Promise<void> {
+ if (!this.meilisearch) return;
if (note.text == null && note.cw == null) return;
if (!['home', 'public'].includes(note.visibility)) return;
- if (this.meilisearch) {
- switch (this.meilisearchIndexScope) {
- case 'global':
- break;
+ switch (this.meilisearchIndexScope) {
+ case 'global':
+ break;
- case 'local':
- if (note.userHost == null) break;
- return;
+ case 'local':
+ if (note.userHost == null) break;
+ return;
- default: {
- if (note.userHost == null) break;
- if (this.meilisearchIndexScope.includes(note.userHost)) break;
- return;
- }
+ default: {
+ if (note.userHost == null) break;
+ if (this.meilisearchIndexScope.includes(note.userHost)) break;
+ return;
}
-
- await this.meilisearchNoteIndex?.addDocuments([{
- id: note.id,
- createdAt: this.idService.parse(note.id).date.getTime(),
- userId: note.userId,
- userHost: note.userHost,
- channelId: note.channelId,
- cw: note.cw,
- text: note.text,
- tags: note.tags,
- attachedFileTypes: note.attachedFileTypes,
- }], {
- primaryKey: 'id',
- });
}
+
+ await this.meilisearchNoteIndex?.addDocuments([{
+ id: note.id,
+ createdAt: this.idService.parse(note.id).date.getTime(),
+ userId: note.userId,
+ userHost: note.userHost,
+ channelId: note.channelId,
+ cw: note.cw,
+ text: note.text,
+ tags: note.tags,
+ attachedFileTypes: note.attachedFileTypes,
+ }], {
+ primaryKey: 'id',
+ });
}
@bindThis
public async unindexNote(note: MiNote): Promise<void> {
+ if (!this.meilisearch) return;
if (!['home', 'public'].includes(note.visibility)) return;
- if (this.meilisearch) {
- this.meilisearchNoteIndex!.deleteDocument(note.id);
- }
+ await this.meilisearchNoteIndex?.deleteDocument(note.id);
+ await this.meilisearchNoteIndex?.deleteDocument(note.id);
}
@bindThis
- public async searchNote(q: string, me: MiUser | null, opts: {
- userId?: MiNote['userId'] | null;
- channelId?: MiNote['channelId'] | null;
- host?: string | null;
- filetype?: string | null;
- order?: string | null;
- disableMeili?: boolean | null;
- }, pagination: {
- untilId?: MiNote['id'];
- sinceId?: MiNote['id'];
- limit?: number;
- }): Promise<MiNote[]> {
- if (this.meilisearch && !opts.disableMeili) {
- const filter: Q = {
- op: 'and',
- qs: [],
- };
- if (pagination.untilId) filter.qs.push({ op: '<', k: 'createdAt', v: this.idService.parse(pagination.untilId).date.getTime() });
- if (pagination.sinceId) filter.qs.push({ op: '>', k: 'createdAt', v: this.idService.parse(pagination.sinceId).date.getTime() });
- if (opts.userId) filter.qs.push({ op: '=', k: 'userId', v: opts.userId });
- if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId });
- if (opts.host) {
- if (opts.host === '.') {
- filter.qs.push({ op: 'is null', k: 'userHost' });
- } else {
- filter.qs.push({ op: '=', k: 'userHost', v: opts.host });
- }
+ public async searchNote(
+ q: string,
+ me: MiUser | null,
+ opts: SearchOpts,
+ pagination: SearchPagination,
+ ): Promise<MiNote[]> {
+ switch (this.provider) {
+ case 'sqlLike':
+ case 'sqlPgroonga': {
+ // ã»ã¨ã‚“ã©å†…容ã«å·®ãŒãªã„ã®ã§sqlLikeã¨sqlPgroongaã‚’åŒã˜å‡¦ç†ã«ã—ã¦ã„ã‚‹.
+ // ä»Šå¾Œã®æ‹¡å¼µã§å·®ãŒå‡ºã‚‹ç”¨ã§ã‚れã°é–¢æ•°ã‚’分ã‘ã‚‹.
+ return this.searchNoteByLike(q, me, opts, pagination);
}
- if (opts.filetype) {
- if (opts.filetype === 'image') {
- filter.qs.push({ op: 'or', qs: [
- { op: '=', k: 'attachedFileTypes', v: 'image/webp' },
- { op: '=', k: 'attachedFileTypes', v: 'image/png' },
- { op: '=', k: 'attachedFileTypes', v: 'image/jpeg' },
- { op: '=', k: 'attachedFileTypes', v: 'image/avif' },
- { op: '=', k: 'attachedFileTypes', v: 'image/apng' },
- { op: '=', k: 'attachedFileTypes', v: 'image/gif' },
- ] });
- } else if (opts.filetype === 'video') {
- filter.qs.push({ op: 'or', qs: [
- { op: '=', k: 'attachedFileTypes', v: 'video/mp4' },
- { op: '=', k: 'attachedFileTypes', v: 'video/webm' },
- { op: '=', k: 'attachedFileTypes', v: 'video/mpeg' },
- { op: '=', k: 'attachedFileTypes', v: 'video/x-m4v' },
- ] });
- } else if (opts.filetype === 'audio') {
- filter.qs.push({ op: 'or', qs: [
- { op: '=', k: 'attachedFileTypes', v: 'audio/mpeg' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/flac' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/wav' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/aac' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/webm' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/opus' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/ogg' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/x-m4a' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/mod' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/s3m' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/xm' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/it' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/x-mod' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/x-s3m' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/x-xm' },
- { op: '=', k: 'attachedFileTypes', v: 'audio/x-it' },
- ] });
- }
+ case 'meilisearch': {
+ return this.searchNoteByMeiliSearch(q, me, opts, pagination);
}
- const res = await this.meilisearchNoteIndex!.search(q, {
- sort: [`createdAt:${opts.order ? opts.order : 'desc'}`],
- matchingStrategy: 'all',
- attributesToRetrieve: ['id', 'createdAt'],
- filter: compileQuery(filter),
- limit: pagination.limit,
- });
- if (res.hits.length === 0) return [];
- const [
- userIdsWhoMeMuting,
- userIdsWhoBlockingMe,
- ] = me ? await Promise.all([
- this.cacheService.userMutingsCache.fetch(me.id),
- this.cacheService.userBlockedCache.fetch(me.id),
- ]) : [new Set<string>(), new Set<string>()];
- const notes = (await this.notesRepository.findBy({
- id: In(res.hits.map(x => x.id)),
- })).filter(note => {
- if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
- if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
- return true;
- });
- return notes.sort((a, b) => a.id > b.id ? -1 : 1);
+ default: {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const typeCheck: never = this.provider;
+ return [];
+ }
+ }
+ }
+
+ @bindThis
+ private async searchNoteByLike(
+ q: string,
+ me: MiUser | null,
+ opts: SearchOpts,
+ pagination: SearchPagination,
+ ): Promise<MiNote[]> {
+ const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
+
+ if (opts.userId) {
+ query.andWhere('note.userId = :userId', { userId: opts.userId });
+ } else if (opts.channelId) {
+ query.andWhere('note.channelId = :channelId', { channelId: opts.channelId });
+ }
+
+ query
+ .innerJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .leftJoinAndSelect('renote.user', 'renoteUser');
+
+ if (this.config.fulltextSearch?.provider === 'sqlPgroonga') {
+ query.andWhere('note.text &@ :q', { q });
} else {
- const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
+ query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` });
+ }
- if (opts.userId) {
- query.andWhere('note.userId = :userId', { userId: opts.userId });
- } else if (opts.channelId) {
- query.andWhere('note.channelId = :channelId', { channelId: opts.channelId });
+ if (opts.host) {
+ if (opts.host === '.') {
+ query.andWhere('user.host IS NULL');
+ } else {
+ query.andWhere('user.host = :host', { host: opts.host });
}
+ }
- query
- .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` })
- .innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('note.reply', 'reply')
- .leftJoinAndSelect('note.renote', 'renote')
- .leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('renote.user', 'renoteUser');
+ if (opts.filetype) {
+ /* this is very ugly, but the "correct" solution would
+ be `and exists (select 1 from
+ unnest(note."attachedFileTypes") x(t) where t like
+ :type)` and I can't find a way to get TypeORM to
+ generate that; this hack works because `~*` is
+ "regexp match, ignoring case" and the stringified
+ version of an array of varchars (which is what
+ `attachedFileTypes` is) looks like `{foo,bar}`, so
+ we're looking for opts.filetype as the first half of
+ a MIME type, either at start of the array (after the
+ `{`) or later (after a `,`) */
+ query.andWhere('note."attachedFileTypes"::varchar ~* :type', { type: `[{,]${opts.filetype}/` });
+ }
- if (opts.host) {
- if (opts.host === '.') {
- query.andWhere('user.host IS NULL');
- } else {
- query.andWhere('user.host = :host', { host: opts.host });
- }
- }
+ this.queryService.generateVisibilityQuery(query, me);
+ if (me) this.queryService.generateMutedUserQuery(query, me);
+ if (me) this.queryService.generateBlockedUserQuery(query, me);
- if (opts.filetype) {
- /* this is very ugly, but the "correct" solution would
- be `and exists (select 1 from
- unnest(note."attachedFileTypes") x(t) where t like
- :type)` and I can't find a way to get TypeORM to
- generate that; this hack works because `~*` is
- "regexp match, ignoring case" and the stringified
- version of an array of varchars (which is what
- `attachedFileTypes` is) looks like `{foo,bar}`, so
- we're looking for opts.filetype as the first half of
- a MIME type, either at start of the array (after the
- `{`) or later (after a `,`) */
- query.andWhere(`note."attachedFileTypes"::varchar ~* :type`, { type: `[{,]${opts.filetype}/` });
- }
+ return await query.limit(pagination.limit).getMany();
+ }
- this.queryService.generateVisibilityQuery(query, me);
- if (me) this.queryService.generateMutedUserQuery(query, me);
- if (me) this.queryService.generateBlockedUserQuery(query, me);
+ @bindThis
+ private async searchNoteByMeiliSearch(
+ q: string,
+ me: MiUser | null,
+ opts: SearchOpts,
+ pagination: SearchPagination,
+ ): Promise<MiNote[]> {
+ if (!this.meilisearch || !this.meilisearchNoteIndex) {
+ throw new Error('MeiliSearch is not available');
+ }
+
+ const filter: Q = {
+ op: 'and',
+ qs: [],
+ };
+ if (pagination.untilId) filter.qs.push({
+ op: '<',
+ k: 'createdAt',
+ v: this.idService.parse(pagination.untilId).date.getTime(),
+ });
+ if (pagination.sinceId) filter.qs.push({
+ op: '>',
+ k: 'createdAt',
+ v: this.idService.parse(pagination.sinceId).date.getTime(),
+ });
+ if (opts.userId) filter.qs.push({ op: '=', k: 'userId', v: opts.userId });
+ if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId });
+ if (opts.host) {
+ if (opts.host === '.') {
+ filter.qs.push({ op: 'is null', k: 'userHost' });
+ } else {
+ filter.qs.push({ op: '=', k: 'userHost', v: opts.host });
+ }
+ }
- return await query.limit(pagination.limit).getMany();
+ const res = await this.meilisearchNoteIndex.search(q, {
+ sort: ['createdAt:desc'],
+ matchingStrategy: 'all',
+ attributesToRetrieve: ['id', 'createdAt'],
+ filter: compileQuery(filter),
+ limit: pagination.limit,
+ });
+ if (res.hits.length === 0) {
+ return [];
}
+
+ const [
+ userIdsWhoMeMuting,
+ userIdsWhoBlockingMe,
+ ] = me
+ ? await Promise.all([
+ this.cacheService.userMutingsCache.fetch(me.id),
+ this.cacheService.userBlockedCache.fetch(me.id),
+ ])
+ : [new Set<string>(), new Set<string>()];
+ const notes = (await this.notesRepository.findBy({
+ id: In(res.hits.map(x => x.id)),
+ })).filter(note => {
+ if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
+ if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
+ return true;
+ });
+
+ return notes.sort((a, b) => a.id > b.id ? -1 : 1);
}
}
diff --git a/packages/backend/src/core/SystemWebhookService.ts b/packages/backend/src/core/SystemWebhookService.ts
index de00169612..8239490adc 100644
--- a/packages/backend/src/core/SystemWebhookService.ts
+++ b/packages/backend/src/core/SystemWebhookService.ts
@@ -50,7 +50,6 @@ export type SystemWebhookPayload<T extends SystemWebhookEventType> =
@Injectable()
export class SystemWebhookService implements OnApplicationShutdown {
- private logger: Logger;
private activeSystemWebhooksFetched = false;
private activeSystemWebhooks: MiSystemWebhook[] = [];
@@ -62,11 +61,9 @@ export class SystemWebhookService implements OnApplicationShutdown {
private idService: IdService,
private queueService: QueueService,
private moderationLogService: ModerationLogService,
- private loggerService: LoggerService,
private globalEventService: GlobalEventService,
) {
this.redisForSub.on('message', this.onMessage);
- this.logger = this.loggerService.getLogger('webhook');
}
@bindThis
@@ -193,28 +190,24 @@ export class SystemWebhookService implements OnApplicationShutdown {
/**
* SystemWebhook ã‚’Webhooké…é€ã‚­ãƒ¥ãƒ¼ã«è¿½åŠ ã™ã‚‹
* @see QueueService.systemWebhookDeliver
- * // TODO: contentã®åž‹ã‚’厳格化ã™ã‚‹
*/
@bindThis
public async enqueueSystemWebhook<T extends SystemWebhookEventType>(
- webhook: MiSystemWebhook | MiSystemWebhook['id'],
type: T,
content: SystemWebhookPayload<T>,
+ opts?: {
+ excludes?: MiSystemWebhook['id'][];
+ },
) {
- const webhookEntity = typeof webhook === 'string'
- ? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)
- : webhook;
- if (!webhookEntity || !webhookEntity.isActive) {
- this.logger.info(`SystemWebhook is not active or not found : ${webhook}`);
- return;
- }
-
- if (!webhookEntity.on.includes(type)) {
- this.logger.info(`SystemWebhook ${webhookEntity.id} is not listening to ${type}`);
- return;
- }
-
- return this.queueService.systemWebhookDeliver(webhookEntity, type, content);
+ const webhooks = await this.fetchActiveSystemWebhooks()
+ .then(webhooks => {
+ return webhooks.filter(webhook => !opts?.excludes?.includes(webhook.id) && webhook.on.includes(type));
+ });
+ return Promise.all(
+ webhooks.map(webhook => {
+ return this.queueService.systemWebhookDeliver(webhook, type, content);
+ }),
+ );
}
@bindThis
diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts
index 2f1310b8ef..8da1bb2092 100644
--- a/packages/backend/src/core/UserBlockingService.ts
+++ b/packages/backend/src/core/UserBlockingService.ts
@@ -118,13 +118,7 @@ export class UserBlockingService implements OnModuleInit {
schema: 'UserDetailedNotMe',
}).then(async packed => {
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
-
- const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'unfollow', {
- user: packed,
- });
- }
+ this.webhookService.enqueueUserWebhook(follower.id, 'unfollow', { user: packed });
});
}
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 8963003057..b98ca97ec9 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -333,13 +333,7 @@ export class UserFollowingService implements OnModuleInit {
schema: 'UserDetailedNotMe',
}).then(async packed => {
this.globalEventService.publishMainStream(follower.id, 'follow', packed);
-
- const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'follow', {
- user: packed,
- });
- }
+ this.webhookService.enqueueUserWebhook(follower.id, 'follow', { user: packed });
});
}
@@ -347,13 +341,7 @@ export class UserFollowingService implements OnModuleInit {
if (this.userEntityService.isLocalUser(followee)) {
this.userEntityService.pack(follower.id, followee).then(async packed => {
this.globalEventService.publishMainStream(followee.id, 'followed', packed);
-
- const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'followed', {
- user: packed,
- });
- }
+ this.webhookService.enqueueUserWebhook(followee.id, 'followed', { user: packed });
});
// 通知を作æˆ
@@ -400,13 +388,7 @@ export class UserFollowingService implements OnModuleInit {
schema: 'UserDetailedNotMe',
}).then(async packed => {
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
-
- const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'unfollow', {
- user: packed,
- });
- }
+ this.webhookService.enqueueUserWebhook(follower.id, 'unfollow', { user: packed });
});
}
@@ -744,13 +726,7 @@ export class UserFollowingService implements OnModuleInit {
});
this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee);
-
- const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
- for (const webhook of webhooks) {
- this.queueService.userWebhookDeliver(webhook, 'unfollow', {
- user: packedFollowee,
- });
- }
+ this.webhookService.enqueueUserWebhook(follower.id, 'unfollow', { user: packedFollowee });
}
@bindThis
diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts
index 9b1961c631..1f471513f3 100644
--- a/packages/backend/src/core/UserService.ts
+++ b/packages/backend/src/core/UserService.ts
@@ -63,13 +63,6 @@ export class UserService {
@bindThis
public async notifySystemWebhook(user: MiUser, type: 'userCreated') {
const packedUser = await this.userEntityService.pack(user, null, { schema: 'UserLite' });
- const recipientWebhookIds = await this.systemWebhookService.fetchSystemWebhooks({ isActive: true, on: [type] });
- for (const webhookId of recipientWebhookIds) {
- await this.systemWebhookService.enqueueSystemWebhook(
- webhookId,
- type,
- packedUser,
- );
- }
+ return this.systemWebhookService.enqueueSystemWebhook(type, packedUser);
}
}
diff --git a/packages/backend/src/core/UserWebhookService.ts b/packages/backend/src/core/UserWebhookService.ts
index 911efdf768..08db4c9afc 100644
--- a/packages/backend/src/core/UserWebhookService.ts
+++ b/packages/backend/src/core/UserWebhookService.ts
@@ -5,13 +5,14 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
-import { type WebhooksRepository } from '@/models/_.js';
+import { MiUser, type WebhooksRepository } from '@/models/_.js';
import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { GlobalEvents } from '@/core/GlobalEventService.js';
-import type { OnApplicationShutdown } from '@nestjs/common';
import type { Packed } from '@/misc/json-schema.js';
+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' ? {
@@ -34,6 +35,7 @@ export class UserWebhookService implements OnApplicationShutdown {
private redisForSub: Redis.Redis,
@Inject(DI.webhooksRepository)
private webhooksRepository: WebhooksRepository,
+ private queueService: QueueService,
) {
this.redisForSub.on('message', this.onMessage);
}
@@ -75,6 +77,25 @@ export class UserWebhookService implements OnApplicationShutdown {
return query.getMany();
}
+ /**
+ * UserWebhook ã‚’Webhooké…é€ã‚­ãƒ¥ãƒ¼ã«è¿½åŠ ã™ã‚‹
+ * @see QueueService.userWebhookDeliver
+ */
+ @bindThis
+ public async enqueueUserWebhook<T extends WebhookEventTypes>(
+ userId: MiUser['id'],
+ type: T,
+ content: UserWebhookPayload<T>,
+ ) {
+ const webhooks = await this.getActiveWebhooks()
+ .then(webhooks => webhooks.filter(webhook => webhook.userId === userId && webhook.on.includes(type)));
+ return Promise.all(
+ webhooks.map(webhook => {
+ return this.queueService.userWebhookDeliver(webhook, type, content);
+ }),
+ );
+ }
+
@bindThis
private async onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index f905914022..81eaa5f95d 100644
--- a/packages/backend/src/core/UtilityService.ts
+++ b/packages/backend/src/core/UtilityService.ts
@@ -3,8 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { URL } from 'node:url';
-import punycode from 'punycode/punycode.js';
+import { URL, domainToASCII } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import RE2 from 're2';
import psl from 'psl';
@@ -107,13 +106,13 @@ export class UtilityService {
@bindThis
public toPuny(host: string): string {
- return punycode.toASCII(host.toLowerCase());
+ return domainToASCII(host.toLowerCase());
}
@bindThis
public toPunyNullable(host: string | null | undefined): string | null {
if (host == null) return null;
- return punycode.toASCII(host.toLowerCase());
+ return domainToASCII(host.toLowerCase());
}
@bindThis
diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts
index ad53192f18..ed75e4f467 100644
--- a/packages/backend/src/core/WebAuthnService.ts
+++ b/packages/backend/src/core/WebAuthnService.ts
@@ -189,14 +189,12 @@ export class WebAuthnService {
*/
@bindThis
public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise<MiUser['id'] | null> {
- const challenge = await this.redisClient.get(`webauthn:challenge:${context}`);
+ const challenge = await this.redisClient.getdel(`webauthn:challenge:${context}`);
if (!challenge) {
throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`);
}
- await this.redisClient.del(`webauthn:challenge:${context}`);
-
const key = await this.userSecurityKeysRepository.findOneBy({
id: response.id,
});
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index fb706a775f..a71c0edcf5 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -192,6 +192,9 @@ export class ApRendererService {
// || emoji.originalUrl ã—ã¦ã‚‹ã®ã¯å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚(publicUrlã¯stringãªã®ã§??ã¯ã ã‚)
url: emoji.publicUrl || emoji.originalUrl,
},
+ _misskey_license: {
+ freeText: emoji.license
+ },
};
}
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index c82a9be3b1..a0c3a4846c 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -5,7 +5,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull, Not } from 'typeorm';
-import { UnrecoverableError } from 'bullmq';
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
@@ -17,6 +16,7 @@ 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 { isCollectionOrOrderedCollection } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
@@ -68,7 +68,7 @@ export class Resolver {
if (isCollectionOrOrderedCollection(collection)) {
return collection;
} else {
- throw new UnrecoverableError(`unrecognized collection type: ${collection.type}`);
+ throw new IdentifiableError('f100eccf-f347-43fb-9b45-96a0831fb635', `unrecognized collection type: ${collection.type}`);
}
}
@@ -85,15 +85,15 @@ export class Resolver {
// URLs with fragment parts cannot be resolved correctly because
// the fragment part does not get transmitted over HTTP(S).
// Avoid strange behaviour by not trying to resolve these at all.
- throw new UnrecoverableError(`cannot resolve URL with fragment: ${value}`);
+ throw new IdentifiableError('b94fd5b1-0e3b-4678-9df2-dad4cd515ab2', `cannot resolve URL with fragment: ${value}`);
}
if (this.history.has(value)) {
- throw new Error(`cannot resolve already resolved URL: ${value}`);
+ throw new IdentifiableError('0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5', `cannot resolve already resolved URL: ${value}`);
}
if (this.history.size > this.recursionLimit) {
- throw new Error(`hit recursion limit: ${value}`);
+ throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `hit recursion limit: ${value}`);
}
this.history.add(value);
@@ -104,7 +104,7 @@ export class Resolver {
}
if (!this.utilityService.isFederationAllowedHost(host)) {
- throw new UnrecoverableError(`cannot fetch AP object ${value}: blocked instance ${host}`);
+ throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', `cannot fetch AP object ${value}: blocked instance ${host}`);
}
if (this.config.signToActivityPubGet && !this.user) {
@@ -120,13 +120,13 @@ export class Resolver {
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
) {
- throw new UnrecoverableError(`invalid AP object ${value}: does not have ActivityStreams context`);
+ throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', `invalid AP object ${value}: does not have ActivityStreams context`);
}
// Since redirects are allowed, we cannot safely validate an anonymous object.
// Reject any responses without an ID, as all other checks depend on that value.
if (object.id == null) {
- throw new UnrecoverableError(`invalid AP object ${value}: missing id`);
+ throw new IdentifiableError('ad2dc287-75c1-44c4-839d-3d2e64576675', `invalid AP object ${value}: missing id`);
}
// We allow some limited cross-domain redirects, which means the host may have changed during fetch.
@@ -135,12 +135,12 @@ export class Resolver {
if (finalHost !== host) {
// Make sure the redirect stayed within the same authority.
if (this.utilityService.punyHostPSLDomain(object.id) !== this.utilityService.punyHostPSLDomain(value)) {
- throw new UnrecoverableError(`invalid AP object ${value}: id ${object.id} has different host`);
+ throw new IdentifiableError('fd93c2fa-69a8-440f-880b-bf178e0ec877', `invalid AP object ${value}: id ${object.id} has different host`);
}
// Check if the redirect bounce from [allowed domain] to [blocked domain].
if (!this.utilityService.isFederationAllowedHost(finalHost)) {
- throw new UnrecoverableError(`cannot fetch AP object ${value}: redirected to blocked instance ${finalHost}`);
+ throw new IdentifiableError('0a72bf24-2d9b-4f1d-886b-15aaa31adeda', `cannot fetch AP object ${value}: redirected to blocked instance ${finalHost}`);
}
}
@@ -150,7 +150,7 @@ export class Resolver {
@bindThis
private resolveLocal(url: string): Promise<IObject> {
const parsed = this.apDbResolverService.parseUri(url);
- if (!parsed.local) throw new UnrecoverableError(`resolveLocal - not a local URL: ${url}`);
+ if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', `resolveLocal - not a local URL: ${url}`);
switch (parsed.type) {
case 'notes':
@@ -179,7 +179,7 @@ export class Resolver {
case 'follows':
return this.followRequestsRepository.findOneBy({ id: parsed.id })
.then(async followRequest => {
- if (followRequest == null) throw new UnrecoverableError(`resolveLocal - invalid follow request ID ${parsed.id}: ${url}`);
+ if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', `resolveLocal - invalid follow request ID ${parsed.id}: ${url}`);
const [follower, followee] = await Promise.all([
this.usersRepository.findOneBy({
id: followRequest.followerId,
@@ -191,12 +191,12 @@ export class Resolver {
}),
]);
if (follower == null || followee == null) {
- throw new Error(`resolveLocal - follower or followee does not exist: ${url}`);
+ throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', `resolveLocal - follower or followee does not exist: ${url}`);
}
return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url));
});
default:
- throw new UnrecoverableError(`resolveLocal: type ${parsed.type} unhandled: ${url}`);
+ throw new IdentifiableError('7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0', `resolveLocal: type ${parsed.type} unhandled: ${url}`);
}
}
}
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index d7b6fc6589..5c0b8ffcbb 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -561,6 +561,11 @@ const extension_context_definition = {
'_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents',
'_misskey_makeNotesFollowersOnlyBefore': 'misskey:_misskey_makeNotesFollowersOnlyBefore',
'_misskey_makeNotesHiddenBefore': 'misskey:_misskey_makeNotesHiddenBefore',
+ '_misskey_license': 'misskey:_misskey_license',
+ 'freeText': {
+ '@id': 'misskey:freeText',
+ '@type': 'schema:text',
+ },
'isCat': 'misskey:isCat',
// Firefish
firefish: 'https://joinfirefish.org/ns#',
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index e4c4fe54b5..9fc6945edb 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -690,6 +690,8 @@ export class ApNoteService {
originalUrl: tag.icon.url,
publicUrl: tag.icon.url,
updatedAt: new Date(),
+ // _misskey_license ãŒå­˜åœ¨ã—ãªã‘れ㰠`null`
+ license: (tag._misskey_license?.freeText ?? null),
});
const emoji = await this.emojisRepository.findOneBy({ host, name });
@@ -711,6 +713,8 @@ export class ApNoteService {
publicUrl: tag.icon.url,
updatedAt: new Date(),
aliases: [],
+ // _misskey_license ãŒå­˜åœ¨ã—ãªã‘れ㰠`null`
+ license: (tag._misskey_license?.freeText ?? null)
});
}));
}
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 119a9d8ccb..21eff4ddb7 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -270,6 +270,11 @@ export interface IApEmoji extends IObject {
type: 'Emoji';
name: string;
updated: string;
+ // Misskeyæ‹¡å¼µã€‚å¾Œæ–¹äº’æ›æ€§ã®ãŸã‚ã«optional。
+ // å°†æ¥ã®æ‹¡å¼µæ€§ã‚’考慮ã—ã¦objectã«ã—ã¦ã„ã‚‹
+ _misskey_license?: {
+ freeText: string | null;
+ };
}
export const isEmoji = (object: IObject): object is IApEmoji =>
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 841bd731c0..490d3f2511 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -4,10 +4,10 @@
*/
import { Inject, Injectable } from '@nestjs/common';
+import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
-import type { EmojisRepository } from '@/models/_.js';
+import type { EmojisRepository, MiRole, RolesRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { } from '@/models/Blocking.js';
import type { MiEmoji } from '@/models/Emoji.js';
import { bindThis } from '@/decorators.js';
@@ -16,6 +16,8 @@ export class EmojiEntityService {
constructor(
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
+ @Inject(DI.rolesRepository)
+ private rolesRepository: RolesRepository,
) {
}
@@ -68,8 +70,90 @@ export class EmojiEntityService {
@bindThis
public packDetailedMany(
emojis: any[],
- ) {
+ ): Promise<Packed<'EmojiDetailed'>[]> {
return Promise.all(emojis.map(x => this.packDetailed(x)));
}
+
+ @bindThis
+ public async packDetailedAdmin(
+ src: MiEmoji['id'] | MiEmoji,
+ hint?: {
+ roles?: Map<MiRole['id'], MiRole>
+ },
+ ): Promise<Packed<'EmojiDetailedAdmin'>> {
+ const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
+
+ const roles = Array.of<MiRole>();
+ if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0) {
+ if (hint?.roles) {
+ const hintRoles = hint.roles;
+ roles.push(
+ ...emoji.roleIdsThatCanBeUsedThisEmojiAsReaction
+ .filter(x => hintRoles.has(x))
+ .map(x => hintRoles.get(x)!),
+ );
+ } else {
+ roles.push(
+ ...await this.rolesRepository.findBy({ id: In(emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) }),
+ );
+ }
+
+ roles.sort((a, b) => {
+ if (a.displayOrder !== b.displayOrder) {
+ return b.displayOrder - a.displayOrder;
+ }
+
+ return a.id.localeCompare(b.id);
+ });
+ }
+
+ return {
+ id: emoji.id,
+ updatedAt: emoji.updatedAt?.toISOString() ?? null,
+ name: emoji.name,
+ host: emoji.host,
+ uri: emoji.uri,
+ type: emoji.type,
+ aliases: emoji.aliases,
+ category: emoji.category,
+ publicUrl: emoji.publicUrl,
+ originalUrl: emoji.originalUrl,
+ license: emoji.license,
+ localOnly: emoji.localOnly,
+ isSensitive: emoji.isSensitive,
+ roleIdsThatCanBeUsedThisEmojiAsReaction: roles.map(it => ({ id: it.id, name: it.name })),
+ };
+ }
+
+ @bindThis
+ public async packDetailedAdminMany(
+ emojis: MiEmoji['id'][] | MiEmoji[],
+ hint?: {
+ roles?: Map<MiRole['id'], MiRole>
+ },
+ ): Promise<Packed<'EmojiDetailedAdmin'>[]> {
+ // IDã®ã¿ã®è¦ç´ ã‚’ピックアップã—ã€DBã‹ã‚‰ãƒ¬ã‚³ãƒ¼ãƒ‰ã‚’å–り出ã—ã¦ä»–ã®å€¤ã‚’補完ã™ã‚‹
+ const emojiEntities = emojis.filter(x => typeof x === 'object') as MiEmoji[];
+ const emojiIdOnlyList = emojis.filter(x => typeof x === 'string') as string[];
+ if (emojiIdOnlyList.length > 0) {
+ emojiEntities.push(...await this.emojisRepository.findBy({ id: In(emojiIdOnlyList) }));
+ }
+
+ // 特定ロール専用ã®çµµæ–‡å­—ã§ã‚ã‚‹å ´åˆã€ãã®ãƒ­ãƒ¼ãƒ«æƒ…報をã‚らã‹ã˜ã‚ã¾ã¨ã‚ã¦å–å¾—ã—ã¦ãŠã(packå´ã§éƒ½åº¦å–得も出æ¥ã‚‹ãŒè² è·ãŒé«˜ã„ã®ã§ï¼‰
+ let hintRoles: Map<MiRole['id'], MiRole>;
+ if (hint?.roles) {
+ hintRoles = hint.roles;
+ } else {
+ const roles = Array.of<MiRole>();
+ const roleIds = [...new Set(emojiEntities.flatMap(x => x.roleIdsThatCanBeUsedThisEmojiAsReaction))];
+ if (roleIds.length > 0) {
+ roles.push(...await this.rolesRepository.findBy({ id: In(roleIds) }));
+ }
+
+ hintRoles = new Map(roles.map(x => [x.id, x]));
+ }
+
+ return Promise.all(emojis.map(x => this.packDetailedAdmin(x, { roles: hintRoles })));
+ }
}
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index 857e8f5a7b..4e127f17ca 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -145,6 +145,7 @@ export class MetaEntityService {
enableUrlPreview: instance.urlPreviewEnabled,
noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local',
maxFileSize: this.config.maxFileSize,
+ federation: this.meta.federation,
};
return packed;
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index be45e75d74..e5b575c219 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -110,8 +110,7 @@ export class NoteEntityService implements OnModuleInit {
}
@bindThis
- private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> {
- // FIXME: ã“ã®visibility変更処ç†ãŒå½“関数ã«ã‚ã‚‹ã®ã¯è‹¥å¹²ä¸è‡ªç„¶ã‹ã‚‚ã—れãªã„(関数åã‚’ treatVisibility ã¨ã‹ã«å¤‰ãˆã‚‹æ‰‹ã‚‚ã‚ã‚‹)
+ private treatVisibility(packedNote: Packed<'Note'>): Packed<'Note'>['visibility'] {
if (packedNote.visibility === 'public' || packedNote.visibility === 'home') {
const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore;
if ((followersOnlyBefore != null)
@@ -123,7 +122,11 @@ export class NoteEntityService implements OnModuleInit {
packedNote.visibility = 'followers';
}
}
+ return packedNote.visibility;
+ }
+ @bindThis
+ private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> {
if (meId === packedNote.userId) return;
// TODO: isVisibleForMe を使ã†ã‚ˆã†ã«ã—ã¦ã‚‚良ã•ãã†(åž‹é•ã†ã‘ã©)
@@ -500,6 +503,8 @@ export class NoteEntityService implements OnModuleInit {
} : {}),
});
+ this.treatVisibility(packed);
+
if (!opts.skipHide) {
await this.hideNote(packed, meId);
}
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index 040e36228c..f612591eda 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -33,7 +33,11 @@ import { packedClipSchema } from '@/models/json-schema/clip.js';
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
-import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
+import {
+ packedEmojiDetailedAdminSchema,
+ packedEmojiDetailedSchema,
+ packedEmojiSimpleSchema,
+} from '@/models/json-schema/emoji.js';
import { packedFlashSchema } from '@/models/json-schema/flash.js';
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
import { packedSigninSchema } from '@/models/json-schema/signin.js';
@@ -95,6 +99,7 @@ export const refs = {
GalleryPost: packedGalleryPostSchema,
EmojiSimple: packedEmojiSimpleSchema,
EmojiDetailed: packedEmojiDetailedSchema,
+ EmojiDetailedAdmin: packedEmojiDetailedAdminSchema,
Flash: packedFlashSchema,
Signin: packedSigninSchema,
RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,
diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts
index 62686ad5ae..3cd263fa37 100644
--- a/packages/backend/src/models/json-schema/emoji.ts
+++ b/packages/backend/src/models/json-schema/emoji.ts
@@ -104,3 +104,86 @@ export const packedEmojiDetailedSchema = {
},
},
} as const;
+
+export const packedEmojiDetailedAdminSchema = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ format: 'id',
+ optional: false, nullable: false,
+ },
+ updatedAt: {
+ type: 'string',
+ format: 'date-time',
+ optional: false, nullable: true,
+ },
+ name: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ host: {
+ type: 'string',
+ optional: false, nullable: true,
+ description: 'The local host is represented with `null`.',
+ },
+ publicUrl: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ originalUrl: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ uri: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ type: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ aliases: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'string',
+ format: 'id',
+ optional: false, nullable: false,
+ },
+ },
+ category: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ license: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ localOnly: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ isSensitive: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ roleIdsThatCanBeUsedThisEmojiAsReaction: {
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ format: 'misskey:id',
+ optional: false, nullable: false,
+ },
+ name: {
+ type: 'string',
+ optional: false, 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 29fdb4f6be..bf68208c37 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -321,6 +321,11 @@ export const packedMetaLiteSchema = {
type: 'number',
optional: false, nullable: false,
},
+ federation: {
+ type: 'string',
+ enum: ['all', 'specified', 'none'],
+ optional: false, nullable: false,
+ },
},
} as const;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index c964c3ffee..98405052c6 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -92,27 +92,65 @@ export const dbLogger = new MisskeyLogger('db');
const sqlLogger = dbLogger.createSubLogger('sql', 'gray');
+export type LoggerProps = {
+ disableQueryTruncation?: boolean;
+ enableQueryParamLogging?: boolean;
+}
+
+function highlightSql(sql: string) {
+ return highlight.highlight(sql, {
+ language: 'sql', ignoreIllegals: true,
+ });
+}
+
+function truncateSql(sql: string) {
+ return sql.length > 100 ? `${sql.substring(0, 100)}...` : sql;
+}
+
+function stringifyParameter(param: any) {
+ if (param instanceof Date) {
+ return param.toISOString();
+ } else {
+ return param;
+ }
+}
+
class MyCustomLogger implements Logger {
+ constructor(private props: LoggerProps = {}) {
+ }
+
+ @bindThis
+ private transformQueryLog(sql: string) {
+ let modded = sql;
+ if (!this.props.disableQueryTruncation) {
+ modded = truncateSql(modded);
+ }
+
+ return highlightSql(modded);
+ }
+
@bindThis
- private highlight(sql: string) {
- return highlight.highlight(sql, {
- language: 'sql', ignoreIllegals: true,
- });
+ private transformParameters(parameters?: any[]) {
+ if (this.props.enableQueryParamLogging && parameters && parameters.length > 0) {
+ return parameters.map(stringifyParameter);
+ }
+
+ return undefined;
}
@bindThis
public logQuery(query: string, parameters?: any[]) {
- sqlLogger.info(this.highlight(query).substring(0, 100));
+ sqlLogger.info(this.transformQueryLog(query), this.transformParameters(parameters));
}
@bindThis
public logQueryError(error: string, query: string, parameters?: any[]) {
- sqlLogger.error(this.highlight(query));
+ sqlLogger.error(this.transformQueryLog(query), this.transformParameters(parameters));
}
@bindThis
public logQuerySlow(time: number, query: string, parameters?: any[]) {
- sqlLogger.warn(this.highlight(query));
+ sqlLogger.warn(this.transformQueryLog(query), this.transformParameters(parameters));
}
@bindThis
@@ -249,7 +287,12 @@ export function createPostgresDataSource(config: Config) {
},
} : false,
logging: log,
- logger: log ? new MyCustomLogger() : undefined,
+ logger: log
+ ? new MyCustomLogger({
+ disableQueryTruncation: config.logging?.sql?.disableQueryTruncation,
+ enableQueryParamLogging: config.logging?.sql?.enableQueryParamLogging,
+ })
+ : undefined,
maxQueryExecutionTime: 300,
entities: entities,
migrations: ['../../migration/*.js'],
diff --git a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
index b81987cc15..ef21b6142e 100644
--- a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
+++ b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -215,15 +215,10 @@ export class CheckModeratorsActivityProcessorService {
// -- SystemWebhook
- const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks()
- .then(it => it.filter(it => it.on.includes('inactiveModeratorsWarning')));
- for (const systemWebhook of systemWebhooks) {
- this.systemWebhookService.enqueueSystemWebhook(
- systemWebhook,
- 'inactiveModeratorsWarning',
- { remainingTime: remainingTime },
- );
- }
+ return this.systemWebhookService.enqueueSystemWebhook(
+ 'inactiveModeratorsWarning',
+ { remainingTime: remainingTime },
+ );
}
@bindThis
@@ -253,15 +248,10 @@ export class CheckModeratorsActivityProcessorService {
// -- SystemWebhook
- const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks()
- .then(it => it.filter(it => it.on.includes('inactiveModeratorsInvitationOnlyChanged')));
- for (const systemWebhook of systemWebhooks) {
- this.systemWebhookService.enqueueSystemWebhook(
- systemWebhook,
- 'inactiveModeratorsInvitationOnlyChanged',
- {},
- );
- }
+ return this.systemWebhookService.enqueueSystemWebhook(
+ 'inactiveModeratorsInvitationOnlyChanged',
+ {},
+ );
}
@bindThis
diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
index 19f98c0d51..8c5faa8d07 100644
--- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
@@ -48,6 +48,7 @@ export class CleanChartsProcessorService {
public async process(): Promise<void> {
this.logger.info('Clean charts...');
+ // DBã¸ã®åŒæ™‚接続をé¿ã‘ã‚‹ãŸã‚ã«Promise.allを使ã‚ãšã²ã¨ã¤ãšã¤å®Ÿè¡Œã™ã‚‹
await this.federationChart.clean();
await this.notesChart.clean();
await this.usersChart.clean();
diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
index 666a709ab9..383fa0c26a 100644
--- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
@@ -14,10 +14,10 @@ import { createTempDir } from '@/misc/create-temp.js';
import { DriveService } from '@/core/DriveService.js';
import { DownloadService } from '@/core/DownloadService.js';
import { bindThis } from '@/decorators.js';
+import type { Config } from '@/config.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbUserImportJobData } from '../types.js';
-import type { Config } from '@/config.js';
// TODO: åå‰è¡çªæ™‚ã®å‹•作をé¸ã¹ã‚‹ã‚ˆã†ã«ã™ã‚‹
@Injectable()
@@ -92,6 +92,7 @@ export class ImportCustomEmojisProcessorService {
await this.emojisRepository.delete({
name: nameNfc,
});
+
try {
const driveFile = await this.driveService.addFile({
user: null,
@@ -100,11 +101,13 @@ export class ImportCustomEmojisProcessorService {
force: true,
});
await this.customEmojiService.add({
+ originalUrl: driveFile.url,
+ publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+ fileType: driveFile.webpublicType ?? driveFile.type,
name: nameNfc,
category: emojiInfo.category?.normalize('NFC'),
host: null,
aliases: emojiInfo.aliases?.map((a: string) => a.normalize('NFC')),
- driveFile,
license: emojiInfo.license,
isSensitive: emojiInfo.isSensitive,
localOnly: emojiInfo.localOnly,
diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
index 46e1adf173..0c47fdedb3 100644
--- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
@@ -29,6 +29,7 @@ export class ResyncChartsProcessorService {
public async process(): Promise<void> {
this.logger.info('Resync charts...');
+ // DBã¸ã®åŒæ™‚接続をé¿ã‘ã‚‹ãŸã‚ã«Promise.allを使ã‚ãšã²ã¨ã¤ãšã¤å®Ÿè¡Œã™ã‚‹
// TODO: ユーザーã”ã¨ã®ãƒãƒ£ãƒ¼ãƒˆã‚‚æ›´æ–°ã™ã‚‹
// TODO: インスタンスã”ã¨ã®ãƒãƒ£ãƒ¼ãƒˆã‚‚æ›´æ–°ã™ã‚‹
await this.driveChart.resync();
diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts
index c09cbccc57..fc8856a271 100644
--- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts
@@ -48,6 +48,7 @@ export class TickChartsProcessorService {
public async process(): Promise<void> {
this.logger.info('Tick charts...');
+ // DBã¸ã®åŒæ™‚接続をé¿ã‘ã‚‹ãŸã‚ã«Promise.allを使ã‚ãšã²ã¨ã¤ãšã¤å®Ÿè¡Œã™ã‚‹
await this.federationChart.tick(false);
await this.notesChart.tick(false);
await this.usersChart.tick(false);
diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts
index 9433392df5..a900675a86 100644
--- a/packages/backend/src/queue/types.ts
+++ b/packages/backend/src/queue/types.ts
@@ -6,9 +6,12 @@
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiNote } from '@/models/Note.js';
+import type { SystemWebhookEventType } from '@/models/SystemWebhook.js';
import type { MiUser } from '@/models/User.js';
-import type { MiWebhook } from '@/models/Webhook.js';
+import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
import type { IActivity } from '@/core/activitypub/type.js';
+import type { SystemWebhookPayload } from '@/core/SystemWebhookService.js';
+import type { UserWebhookPayload } from '@/core/UserWebhookService.js';
import type httpSignature from '@peertube/http-signature';
export type DeliverJobData = {
@@ -131,9 +134,9 @@ export type EndedPollNotificationJobData = {
noteId: MiNote['id'];
};
-export type SystemWebhookDeliverJobData = {
- type: string;
- content: unknown;
+export type SystemWebhookDeliverJobData<T extends SystemWebhookEventType = SystemWebhookEventType> = {
+ type: T;
+ content: SystemWebhookPayload<T>;
webhookId: MiWebhook['id'];
to: string;
secret: string;
@@ -141,9 +144,9 @@ export type SystemWebhookDeliverJobData = {
eventId: string;
};
-export type UserWebhookDeliverJobData = {
- type: string;
- content: unknown;
+export type UserWebhookDeliverJobData<T extends WebhookEventTypes = WebhookEventTypes> = {
+ type: T;
+ content: UserWebhookPayload<T>;
webhookId: MiWebhook['id'];
userId: MiUser['id'];
to: string;
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index 815bf278c7..a4dddbd6c3 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -653,8 +653,8 @@ export class ActivityPubServerService {
},
deriveConstraint(request: IncomingMessage) {
const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]);
- const isAp = typeof accepted === 'string' && !accepted.match(/html/);
- return isAp ? 'ap' : 'html';
+ if (accepted === false) return null;
+ return accepted !== 'html' ? 'ap' : 'html';
},
});
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index e319d6e0a4..9cfb2f0ac0 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -6,816 +6,13 @@
import { Module } from '@nestjs/common';
import { CoreModule } from '@/core/CoreModule.js';
-import * as ep___admin_abuseReport_notificationRecipient_list from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
-import * as ep___admin_abuseReport_notificationRecipient_show from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js';
-import * as ep___admin_abuseReport_notificationRecipient_create from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js';
-import * as ep___admin_abuseReport_notificationRecipient_update from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js';
-import * as ep___admin_abuseReport_notificationRecipient_delete from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js';
-import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
-import * as ep___admin_meta from './endpoints/admin/meta.js';
-import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
-import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
-import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
-import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
-import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
-import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
-import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
-import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
-import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
-import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
-import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
-import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
-import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
-import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
-import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
-import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
-import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
-import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
-import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
-import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
-import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
-import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
-import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
-import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
-import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
-import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
-import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
-import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
-import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
-import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
-import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
-import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
-import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
-import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
-import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
-import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
-import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
-import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
-import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js';
-import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js';
-import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
-import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
-import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
-import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
-import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
-import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
-import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
-import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
-import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
-import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
-import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
-import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
-import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
-import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
-import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
-import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js';
-import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js';
-import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
-import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
-import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
-import * as ep___admin_showUser from './endpoints/admin/show-user.js';
-import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
-import * as ep___admin_nsfwUser from './endpoints/admin/nsfw-user.js';
-import * as ep___admin_unnsfwUser from './endpoints/admin/unnsfw-user.js';
-import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js';
-import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js';
-import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
-import * as ep___admin_approveUser from './endpoints/admin/approve-user.js';
-import * as ep___admin_declineUser from './endpoints/admin/decline-user.js';
-import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
-import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
-import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
-import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
-import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
-import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
-import * as ep___admin_roles_list from './endpoints/admin/roles/list.js';
-import * as ep___admin_roles_show from './endpoints/admin/roles/show.js';
-import * as ep___admin_roles_update from './endpoints/admin/roles/update.js';
-import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
-import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
-import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
-import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
-import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js';
-import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js';
-import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
-import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
-import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
-import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
-import * as ep___announcements from './endpoints/announcements.js';
-import * as ep___announcements_show from './endpoints/announcements/show.js';
-import * as ep___antennas_create from './endpoints/antennas/create.js';
-import * as ep___antennas_delete from './endpoints/antennas/delete.js';
-import * as ep___antennas_list from './endpoints/antennas/list.js';
-import * as ep___antennas_notes from './endpoints/antennas/notes.js';
-import * as ep___antennas_show from './endpoints/antennas/show.js';
-import * as ep___antennas_update from './endpoints/antennas/update.js';
-import * as ep___ap_get from './endpoints/ap/get.js';
-import * as ep___ap_show from './endpoints/ap/show.js';
-import * as ep___app_create from './endpoints/app/create.js';
-import * as ep___app_show from './endpoints/app/show.js';
-import * as ep___auth_accept from './endpoints/auth/accept.js';
-import * as ep___auth_session_generate from './endpoints/auth/session/generate.js';
-import * as ep___auth_session_show from './endpoints/auth/session/show.js';
-import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js';
-import * as ep___blocking_create from './endpoints/blocking/create.js';
-import * as ep___blocking_delete from './endpoints/blocking/delete.js';
-import * as ep___blocking_list from './endpoints/blocking/list.js';
-import * as ep___channels_create from './endpoints/channels/create.js';
-import * as ep___channels_featured from './endpoints/channels/featured.js';
-import * as ep___channels_follow from './endpoints/channels/follow.js';
-import * as ep___channels_followed from './endpoints/channels/followed.js';
-import * as ep___channels_owned from './endpoints/channels/owned.js';
-import * as ep___channels_show from './endpoints/channels/show.js';
-import * as ep___channels_timeline from './endpoints/channels/timeline.js';
-import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
-import * as ep___channels_update from './endpoints/channels/update.js';
-import * as ep___channels_favorite from './endpoints/channels/favorite.js';
-import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
-import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
-import * as ep___channels_search from './endpoints/channels/search.js';
-import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
-import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
-import * as ep___charts_drive from './endpoints/charts/drive.js';
-import * as ep___charts_federation from './endpoints/charts/federation.js';
-import * as ep___charts_instance from './endpoints/charts/instance.js';
-import * as ep___charts_notes from './endpoints/charts/notes.js';
-import * as ep___charts_user_drive from './endpoints/charts/user/drive.js';
-import * as ep___charts_user_following from './endpoints/charts/user/following.js';
-import * as ep___charts_user_notes from './endpoints/charts/user/notes.js';
-import * as ep___charts_user_pv from './endpoints/charts/user/pv.js';
-import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js';
-import * as ep___charts_users from './endpoints/charts/users.js';
-import * as ep___clips_addNote from './endpoints/clips/add-note.js';
-import * as ep___clips_removeNote from './endpoints/clips/remove-note.js';
-import * as ep___clips_create from './endpoints/clips/create.js';
-import * as ep___clips_delete from './endpoints/clips/delete.js';
-import * as ep___clips_list from './endpoints/clips/list.js';
-import * as ep___clips_notes from './endpoints/clips/notes.js';
-import * as ep___clips_show from './endpoints/clips/show.js';
-import * as ep___clips_update from './endpoints/clips/update.js';
-import * as ep___clips_favorite from './endpoints/clips/favorite.js';
-import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js';
-import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js';
-import * as ep___drive from './endpoints/drive.js';
-import * as ep___drive_files from './endpoints/drive/files.js';
-import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js';
-import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js';
-import * as ep___drive_files_create from './endpoints/drive/files/create.js';
-import * as ep___drive_files_delete from './endpoints/drive/files/delete.js';
-import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js';
-import * as ep___drive_files_find from './endpoints/drive/files/find.js';
-import * as ep___drive_files_show from './endpoints/drive/files/show.js';
-import * as ep___drive_files_update from './endpoints/drive/files/update.js';
-import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js';
-import * as ep___drive_folders from './endpoints/drive/folders.js';
-import * as ep___drive_folders_create from './endpoints/drive/folders/create.js';
-import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js';
-import * as ep___drive_folders_find from './endpoints/drive/folders/find.js';
-import * as ep___drive_folders_show from './endpoints/drive/folders/show.js';
-import * as ep___drive_folders_update from './endpoints/drive/folders/update.js';
-import * as ep___drive_stream from './endpoints/drive/stream.js';
-import * as ep___emailAddress_available from './endpoints/email-address/available.js';
-import * as ep___endpoint from './endpoints/endpoint.js';
-import * as ep___endpoints from './endpoints/endpoints.js';
-import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js';
-import * as ep___federation_followers from './endpoints/federation/followers.js';
-import * as ep___federation_following from './endpoints/federation/following.js';
-import * as ep___federation_instances from './endpoints/federation/instances.js';
-import * as ep___federation_showInstance from './endpoints/federation/show-instance.js';
-import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js';
-import * as ep___federation_users from './endpoints/federation/users.js';
-import * as ep___federation_stats from './endpoints/federation/stats.js';
-import * as ep___following_create from './endpoints/following/create.js';
-import * as ep___following_delete from './endpoints/following/delete.js';
-import * as ep___following_update from './endpoints/following/update.js';
-import * as ep___following_update_all from './endpoints/following/update-all.js';
-import * as ep___following_invalidate from './endpoints/following/invalidate.js';
-import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
-import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
-import * as ep___following_requests_list from './endpoints/following/requests/list.js';
-import * as ep___following_requests_sent from './endpoints/following/requests/sent.js';
-import * as ep___following_requests_reject from './endpoints/following/requests/reject.js';
-import * as ep___gallery_featured from './endpoints/gallery/featured.js';
-import * as ep___gallery_popular from './endpoints/gallery/popular.js';
-import * as ep___gallery_posts from './endpoints/gallery/posts.js';
-import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js';
-import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js';
-import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js';
-import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js';
-import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
-import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
-import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
-import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js';
-import * as ep___hashtags_list from './endpoints/hashtags/list.js';
-import * as ep___hashtags_search from './endpoints/hashtags/search.js';
-import * as ep___hashtags_show from './endpoints/hashtags/show.js';
-import * as ep___hashtags_trend from './endpoints/hashtags/trend.js';
-import * as ep___hashtags_users from './endpoints/hashtags/users.js';
-import * as ep___i from './endpoints/i.js';
-import * as ep___i_2fa_done from './endpoints/i/2fa/done.js';
-import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js';
-import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js';
-import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js';
-import * as ep___i_2fa_register from './endpoints/i/2fa/register.js';
-import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js';
-import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js';
-import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js';
-import * as ep___i_apps from './endpoints/i/apps.js';
-import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js';
-import * as ep___i_claimAchievement from './endpoints/i/claim-achievement.js';
-import * as ep___i_changePassword from './endpoints/i/change-password.js';
-import * as ep___i_deleteAccount from './endpoints/i/delete-account.js';
-import * as ep___i_exportData from './endpoints/i/export-data.js';
-import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
-import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
-import * as ep___i_exportMute from './endpoints/i/export-mute.js';
-import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
-import * as ep___i_exportClips from './endpoints/i/export-clips.js';
-import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
-import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
-import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
-import * as ep___i_favorites from './endpoints/i/favorites.js';
-import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
-import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
-import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
-import * as ep___i_importFollowing from './endpoints/i/import-following.js';
-import * as ep___i_importNotes from './endpoints/i/import-notes.js';
-import * as ep___i_importMuting from './endpoints/i/import-muting.js';
-import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
-import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
-import * as ep___i_notifications from './endpoints/i/notifications.js';
-import * as ep___i_notificationsGrouped from './endpoints/i/notifications-grouped.js';
-import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
-import * as ep___i_pages from './endpoints/i/pages.js';
-import * as ep___i_pin from './endpoints/i/pin.js';
-import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js';
-import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js';
-import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js';
-import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js';
-import * as ep___i_registry_getUnsecure from './endpoints/i/registry/get-unsecure.js';
-import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js';
-import * as ep___i_registry_get from './endpoints/i/registry/get.js';
-import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js';
-import * as ep___i_registry_keys from './endpoints/i/registry/keys.js';
-import * as ep___i_registry_remove from './endpoints/i/registry/remove.js';
-import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js';
-import * as ep___i_registry_set from './endpoints/i/registry/set.js';
-import * as ep___i_revokeToken from './endpoints/i/revoke-token.js';
-import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
-import * as ep___i_unpin from './endpoints/i/unpin.js';
-import * as ep___i_updateEmail from './endpoints/i/update-email.js';
-import * as ep___i_update from './endpoints/i/update.js';
-import * as ep___i_move from './endpoints/i/move.js';
-import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
-import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
-import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
-import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
-import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
-import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
-import * as ep___invite_create from './endpoints/invite/create.js';
-import * as ep___invite_delete from './endpoints/invite/delete.js';
-import * as ep___invite_list from './endpoints/invite/list.js';
-import * as ep___invite_limit from './endpoints/invite/limit.js';
-import * as ep___meta from './endpoints/meta.js';
-import * as ep___emojis from './endpoints/emojis.js';
-import * as ep___emoji from './endpoints/emoji.js';
-import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
-import * as ep___mute_create from './endpoints/mute/create.js';
-import * as ep___mute_delete from './endpoints/mute/delete.js';
-import * as ep___mute_list from './endpoints/mute/list.js';
-import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
-import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
-import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
-import * as ep___my_apps from './endpoints/my/apps.js';
-import * as ep___notes from './endpoints/notes.js';
-import * as ep___notes_children from './endpoints/notes/children.js';
-import * as ep___notes_clips from './endpoints/notes/clips.js';
-import * as ep___notes_conversation from './endpoints/notes/conversation.js';
-import * as ep___notes_create from './endpoints/notes/create.js';
-import * as ep___notes_delete from './endpoints/notes/delete.js';
-import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
-import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
-import * as ep___notes_featured from './endpoints/notes/featured.js';
-import * as ep___notes_following from './endpoints/notes/following.js';
-import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
-import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
-import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
-import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
-import * as ep___notes_mentions from './endpoints/notes/mentions.js';
-import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
-import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
-import * as ep___notes_polls_refresh from './endpoints/notes/polls/refresh.js';
-import * as ep___notes_reactions from './endpoints/notes/reactions.js';
-import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
-import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
-import * as ep___notes_like from './endpoints/notes/like.js';
-import * as ep___notes_renotes from './endpoints/notes/renotes.js';
-import * as ep___notes_replies from './endpoints/notes/replies.js';
-import * as ep___notes_edit from './endpoints/notes/edit.js';
-import * as ep___notes_versions from './endpoints/notes/versions.js';
-import * as ep___notes_schedule_create from './endpoints/notes/schedule/create.js';
-import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js';
-import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
-import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js';
-import * as ep___notes_search from './endpoints/notes/search.js';
-import * as ep___notes_show from './endpoints/notes/show.js';
-import * as ep___notes_state from './endpoints/notes/state.js';
-import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js';
-import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js';
-import * as ep___notes_timeline from './endpoints/notes/timeline.js';
-import * as ep___notes_translate from './endpoints/notes/translate.js';
-import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
-import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
-import * as ep___notifications_create from './endpoints/notifications/create.js';
-import * as ep___notifications_flush from './endpoints/notifications/flush.js';
-import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
-import * as ep___pagePush from './endpoints/page-push.js';
-import * as ep___pages_create from './endpoints/pages/create.js';
-import * as ep___pages_delete from './endpoints/pages/delete.js';
-import * as ep___pages_featured from './endpoints/pages/featured.js';
-import * as ep___pages_like from './endpoints/pages/like.js';
-import * as ep___pages_show from './endpoints/pages/show.js';
-import * as ep___pages_unlike from './endpoints/pages/unlike.js';
-import * as ep___pages_update from './endpoints/pages/update.js';
-import * as ep___flash_create from './endpoints/flash/create.js';
-import * as ep___flash_delete from './endpoints/flash/delete.js';
-import * as ep___flash_featured from './endpoints/flash/featured.js';
-import * as ep___flash_like from './endpoints/flash/like.js';
-import * as ep___flash_show from './endpoints/flash/show.js';
-import * as ep___flash_unlike from './endpoints/flash/unlike.js';
-import * as ep___flash_update from './endpoints/flash/update.js';
-import * as ep___flash_my from './endpoints/flash/my.js';
-import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
-import * as ep___ping from './endpoints/ping.js';
-import * as ep___pinnedUsers from './endpoints/pinned-users.js';
-import * as ep___promo_read from './endpoints/promo/read.js';
-import * as ep___roles_list from './endpoints/roles/list.js';
-import * as ep___roles_show from './endpoints/roles/show.js';
-import * as ep___roles_users from './endpoints/roles/users.js';
-import * as ep___roles_notes from './endpoints/roles/notes.js';
-import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
-import * as ep___resetDb from './endpoints/reset-db.js';
-import * as ep___resetPassword from './endpoints/reset-password.js';
-import * as ep___serverInfo from './endpoints/server-info.js';
-import * as ep___stats from './endpoints/stats.js';
-import * as ep___sw_show_registration from './endpoints/sw/show-registration.js';
-import * as ep___sw_update_registration from './endpoints/sw/update-registration.js';
-import * as ep___sw_register from './endpoints/sw/register.js';
-import * as ep___sw_unregister from './endpoints/sw/unregister.js';
-import * as ep___test from './endpoints/test.js';
-import * as ep___username_available from './endpoints/username/available.js';
-import * as ep___users from './endpoints/users.js';
-import * as ep___users_clips from './endpoints/users/clips.js';
-import * as ep___users_followers from './endpoints/users/followers.js';
-import * as ep___users_following from './endpoints/users/following.js';
-import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
-import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
-import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
-import * as ep___users_lists_create from './endpoints/users/lists/create.js';
-import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
-import * as ep___users_lists_list from './endpoints/users/lists/list.js';
-import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
-import * as ep___users_lists_push from './endpoints/users/lists/push.js';
-import * as ep___users_lists_show from './endpoints/users/lists/show.js';
-import * as ep___users_lists_update from './endpoints/users/lists/update.js';
-import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
-import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
-import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
-import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
-import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
-import * as ep___users_notes from './endpoints/users/notes.js';
-import * as ep___users_pages from './endpoints/users/pages.js';
-import * as ep___users_flashs from './endpoints/users/flashs.js';
-import * as ep___users_reactions from './endpoints/users/reactions.js';
-import * as ep___users_recommendation from './endpoints/users/recommendation.js';
-import * as ep___users_relation from './endpoints/users/relation.js';
-import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
-import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
-import * as ep___users_search from './endpoints/users/search.js';
-import * as ep___users_show from './endpoints/users/show.js';
-import * as ep___users_achievements from './endpoints/users/achievements.js';
-import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
-import * as ep___fetchRss from './endpoints/fetch-rss.js';
-import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
-import * as ep___retention from './endpoints/retention.js';
-import * as ep___sponsors from './endpoints/sponsors.js';
-import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
-import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
-import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
-import * as ep___reversi_games from './endpoints/reversi/games.js';
-import * as ep___reversi_match from './endpoints/reversi/match.js';
-import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
-import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
-import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
-import * as ep___reversi_verify from './endpoints/reversi/verify.js';
+import * as endpointsObject from './endpoint-list.js';
import { GetterService } from './GetterService.js';
import { ApiLoggerService } from './ApiLoggerService.js';
import type { Provider } from '@nestjs/common';
-const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_meta.default };
-const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default };
-const $admin_abuseReport_notificationRecipient_list: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/list', useClass: ep___admin_abuseReport_notificationRecipient_list.default };
-const $admin_abuseReport_notificationRecipient_show: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/show', useClass: ep___admin_abuseReport_notificationRecipient_show.default };
-const $admin_abuseReport_notificationRecipient_create: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/create', useClass: ep___admin_abuseReport_notificationRecipient_create.default };
-const $admin_abuseReport_notificationRecipient_update: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/update', useClass: ep___admin_abuseReport_notificationRecipient_update.default };
-const $admin_abuseReport_notificationRecipient_delete: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/delete', useClass: ep___admin_abuseReport_notificationRecipient_delete.default };
-const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default };
-const $admin_accounts_delete: Provider = { provide: 'ep:admin/accounts/delete', useClass: ep___admin_accounts_delete.default };
-const $admin_accounts_findByEmail: Provider = { provide: 'ep:admin/accounts/find-by-email', useClass: ep___admin_accounts_findByEmail.default };
-const $admin_ad_create: Provider = { provide: 'ep:admin/ad/create', useClass: ep___admin_ad_create.default };
-const $admin_ad_delete: Provider = { provide: 'ep:admin/ad/delete', useClass: ep___admin_ad_delete.default };
-const $admin_ad_list: Provider = { provide: 'ep:admin/ad/list', useClass: ep___admin_ad_list.default };
-const $admin_ad_update: Provider = { provide: 'ep:admin/ad/update', useClass: ep___admin_ad_update.default };
-const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements/create', useClass: ep___admin_announcements_create.default };
-const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default };
-const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default };
-const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default };
-const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-decorations/create', useClass: ep___admin_avatarDecorations_create.default };
-const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
-const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
-const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
-const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
-const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
-const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
-const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
-const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
-const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default };
-const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default };
-const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default };
-const $admin_emoji_add: Provider = { provide: 'ep:admin/emoji/add', useClass: ep___admin_emoji_add.default };
-const $admin_emoji_copy: Provider = { provide: 'ep:admin/emoji/copy', useClass: ep___admin_emoji_copy.default };
-const $admin_emoji_deleteBulk: Provider = { provide: 'ep:admin/emoji/delete-bulk', useClass: ep___admin_emoji_deleteBulk.default };
-const $admin_emoji_delete: Provider = { provide: 'ep:admin/emoji/delete', useClass: ep___admin_emoji_delete.default };
-const $admin_emoji_importZip: Provider = { provide: 'ep:admin/emoji/import-zip', useClass: ep___admin_emoji_importZip.default };
-const $admin_emoji_listRemote: Provider = { provide: 'ep:admin/emoji/list-remote', useClass: ep___admin_emoji_listRemote.default };
-const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass: ep___admin_emoji_list.default };
-const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default };
-const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default };
-const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
-const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
-const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
-const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
-const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
-const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
-const $admin_federation_updateInstance: Provider = { provide: 'ep:admin/federation/update-instance', useClass: ep___admin_federation_updateInstance.default };
-const $admin_getIndexStats: Provider = { provide: 'ep:admin/get-index-stats', useClass: ep___admin_getIndexStats.default };
-const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', useClass: ep___admin_getTableStats.default };
-const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default };
-const $admin_invite_create: Provider = { provide: 'ep:admin/invite/create', useClass: ep___admin_invite_create.default };
-const $admin_invite_list: Provider = { provide: 'ep:admin/invite/list', useClass: ep___admin_invite_list.default };
-const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default };
-const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default };
-const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default };
-const $admin_queue_inboxDelayed: Provider = { provide: 'ep:admin/queue/inbox-delayed', useClass: ep___admin_queue_inboxDelayed.default };
-const $admin_queue_promote: Provider = { provide: 'ep:admin/queue/promote', useClass: ep___admin_queue_promote.default };
-const $admin_queue_stats: Provider = { provide: 'ep:admin/queue/stats', useClass: ep___admin_queue_stats.default };
-const $admin_relays_add: Provider = { provide: 'ep:admin/relays/add', useClass: ep___admin_relays_add.default };
-const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass: ep___admin_relays_list.default };
-const $admin_relays_remove: Provider = { provide: 'ep:admin/relays/remove', useClass: ep___admin_relays_remove.default };
-const $admin_resetPassword: Provider = { provide: 'ep:admin/reset-password', useClass: ep___admin_resetPassword.default };
-const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abuse-user-report', useClass: ep___admin_resolveAbuseUserReport.default };
-const $admin_forwardAbuseUserReport: Provider = { provide: 'ep:admin/forward-abuse-user-report', useClass: ep___admin_forwardAbuseUserReport.default };
-const $admin_updateAbuseUserReport: Provider = { provide: 'ep:admin/update-abuse-user-report', useClass: ep___admin_updateAbuseUserReport.default };
-const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default };
-const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default };
-const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation-logs', useClass: ep___admin_showModerationLogs.default };
-const $admin_showUser: Provider = { provide: 'ep:admin/show-user', useClass: ep___admin_showUser.default };
-const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default };
-const $admin_nsfwUser: Provider = { provide: 'ep:admin/nsfw-user', useClass: ep___admin_nsfwUser.default };
-const $admin_unnsfwUser: Provider = { provide: 'ep:admin/unnsfw-user', useClass: ep___admin_unnsfwUser.default };
-const $admin_silenceUser: Provider = { provide: 'ep:admin/silence-user', useClass: ep___admin_silenceUser.default };
-const $admin_unsilenceUser: Provider = { provide: 'ep:admin/unsilence-user', useClass: ep___admin_unsilenceUser.default };
-const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
-const $admin_approveUser: Provider = { provide: 'ep:admin/approve-user', useClass: ep___admin_approveUser.default };
-const $admin_declineUser: Provider = { provide: 'ep:admin/decline-user', useClass: ep___admin_declineUser.default };
-const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
-const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
-const $admin_deleteAccount: Provider = { provide: 'ep:admin/delete-account', useClass: ep___admin_deleteAccount.default };
-const $admin_updateUserNote: Provider = { provide: 'ep:admin/update-user-note', useClass: ep___admin_updateUserNote.default };
-const $admin_roles_create: Provider = { provide: 'ep:admin/roles/create', useClass: ep___admin_roles_create.default };
-const $admin_roles_delete: Provider = { provide: 'ep:admin/roles/delete', useClass: ep___admin_roles_delete.default };
-const $admin_roles_list: Provider = { provide: 'ep:admin/roles/list', useClass: ep___admin_roles_list.default };
-const $admin_roles_show: Provider = { provide: 'ep:admin/roles/show', useClass: ep___admin_roles_show.default };
-const $admin_roles_update: Provider = { provide: 'ep:admin/roles/update', useClass: ep___admin_roles_update.default };
-const $admin_roles_assign: Provider = { provide: 'ep:admin/roles/assign', useClass: ep___admin_roles_assign.default };
-const $admin_roles_unassign: Provider = { provide: 'ep:admin/roles/unassign', useClass: ep___admin_roles_unassign.default };
-const $admin_roles_updateDefaultPolicies: Provider = { provide: 'ep:admin/roles/update-default-policies', useClass: ep___admin_roles_updateDefaultPolicies.default };
-const $admin_roles_users: Provider = { provide: 'ep:admin/roles/users', useClass: ep___admin_roles_users.default };
-const $admin_systemWebhook_create: Provider = { provide: 'ep:admin/system-webhook/create', useClass: ep___admin_systemWebhook_create.default };
-const $admin_systemWebhook_delete: Provider = { provide: 'ep:admin/system-webhook/delete', useClass: ep___admin_systemWebhook_delete.default };
-const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/list', useClass: ep___admin_systemWebhook_list.default };
-const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default };
-const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default };
-const $admin_systemWebhook_test: Provider = { provide: 'ep:admin/system-webhook/test', useClass: ep___admin_systemWebhook_test.default };
-const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default };
-const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default };
-const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default };
-const $antennas_delete: Provider = { provide: 'ep:antennas/delete', useClass: ep___antennas_delete.default };
-const $antennas_list: Provider = { provide: 'ep:antennas/list', useClass: ep___antennas_list.default };
-const $antennas_notes: Provider = { provide: 'ep:antennas/notes', useClass: ep___antennas_notes.default };
-const $antennas_show: Provider = { provide: 'ep:antennas/show', useClass: ep___antennas_show.default };
-const $antennas_update: Provider = { provide: 'ep:antennas/update', useClass: ep___antennas_update.default };
-const $ap_get: Provider = { provide: 'ep:ap/get', useClass: ep___ap_get.default };
-const $ap_show: Provider = { provide: 'ep:ap/show', useClass: ep___ap_show.default };
-const $app_create: Provider = { provide: 'ep:app/create', useClass: ep___app_create.default };
-const $app_show: Provider = { provide: 'ep:app/show', useClass: ep___app_show.default };
-const $auth_accept: Provider = { provide: 'ep:auth/accept', useClass: ep___auth_accept.default };
-const $auth_session_generate: Provider = { provide: 'ep:auth/session/generate', useClass: ep___auth_session_generate.default };
-const $auth_session_show: Provider = { provide: 'ep:auth/session/show', useClass: ep___auth_session_show.default };
-const $auth_session_userkey: Provider = { provide: 'ep:auth/session/userkey', useClass: ep___auth_session_userkey.default };
-const $blocking_create: Provider = { provide: 'ep:blocking/create', useClass: ep___blocking_create.default };
-const $blocking_delete: Provider = { provide: 'ep:blocking/delete', useClass: ep___blocking_delete.default };
-const $blocking_list: Provider = { provide: 'ep:blocking/list', useClass: ep___blocking_list.default };
-const $channels_create: Provider = { provide: 'ep:channels/create', useClass: ep___channels_create.default };
-const $channels_featured: Provider = { provide: 'ep:channels/featured', useClass: ep___channels_featured.default };
-const $channels_follow: Provider = { provide: 'ep:channels/follow', useClass: ep___channels_follow.default };
-const $channels_followed: Provider = { provide: 'ep:channels/followed', useClass: ep___channels_followed.default };
-const $channels_owned: Provider = { provide: 'ep:channels/owned', useClass: ep___channels_owned.default };
-const $channels_show: Provider = { provide: 'ep:channels/show', useClass: ep___channels_show.default };
-const $channels_timeline: Provider = { provide: 'ep:channels/timeline', useClass: ep___channels_timeline.default };
-const $channels_unfollow: Provider = { provide: 'ep:channels/unfollow', useClass: ep___channels_unfollow.default };
-const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep___channels_update.default };
-const $channels_favorite: Provider = { provide: 'ep:channels/favorite', useClass: ep___channels_favorite.default };
-const $channels_unfavorite: Provider = { provide: 'ep:channels/unfavorite', useClass: ep___channels_unfavorite.default };
-const $channels_myFavorites: Provider = { provide: 'ep:channels/my-favorites', useClass: ep___channels_myFavorites.default };
-const $channels_search: Provider = { provide: 'ep:channels/search', useClass: ep___channels_search.default };
-const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default };
-const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default };
-const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default };
-const $charts_federation: Provider = { provide: 'ep:charts/federation', useClass: ep___charts_federation.default };
-const $charts_instance: Provider = { provide: 'ep:charts/instance', useClass: ep___charts_instance.default };
-const $charts_notes: Provider = { provide: 'ep:charts/notes', useClass: ep___charts_notes.default };
-const $charts_user_drive: Provider = { provide: 'ep:charts/user/drive', useClass: ep___charts_user_drive.default };
-const $charts_user_following: Provider = { provide: 'ep:charts/user/following', useClass: ep___charts_user_following.default };
-const $charts_user_notes: Provider = { provide: 'ep:charts/user/notes', useClass: ep___charts_user_notes.default };
-const $charts_user_pv: Provider = { provide: 'ep:charts/user/pv', useClass: ep___charts_user_pv.default };
-const $charts_user_reactions: Provider = { provide: 'ep:charts/user/reactions', useClass: ep___charts_user_reactions.default };
-const $charts_users: Provider = { provide: 'ep:charts/users', useClass: ep___charts_users.default };
-const $clips_addNote: Provider = { provide: 'ep:clips/add-note', useClass: ep___clips_addNote.default };
-const $clips_removeNote: Provider = { provide: 'ep:clips/remove-note', useClass: ep___clips_removeNote.default };
-const $clips_create: Provider = { provide: 'ep:clips/create', useClass: ep___clips_create.default };
-const $clips_delete: Provider = { provide: 'ep:clips/delete', useClass: ep___clips_delete.default };
-const $clips_list: Provider = { provide: 'ep:clips/list', useClass: ep___clips_list.default };
-const $clips_notes: Provider = { provide: 'ep:clips/notes', useClass: ep___clips_notes.default };
-const $clips_show: Provider = { provide: 'ep:clips/show', useClass: ep___clips_show.default };
-const $clips_update: Provider = { provide: 'ep:clips/update', useClass: ep___clips_update.default };
-const $clips_favorite: Provider = { provide: 'ep:clips/favorite', useClass: ep___clips_favorite.default };
-const $clips_unfavorite: Provider = { provide: 'ep:clips/unfavorite', useClass: ep___clips_unfavorite.default };
-const $clips_myFavorites: Provider = { provide: 'ep:clips/my-favorites', useClass: ep___clips_myFavorites.default };
-const $drive: Provider = { provide: 'ep:drive', useClass: ep___drive.default };
-const $drive_files: Provider = { provide: 'ep:drive/files', useClass: ep___drive_files.default };
-const $drive_files_attachedNotes: Provider = { provide: 'ep:drive/files/attached-notes', useClass: ep___drive_files_attachedNotes.default };
-const $drive_files_checkExistence: Provider = { provide: 'ep:drive/files/check-existence', useClass: ep___drive_files_checkExistence.default };
-const $drive_files_create: Provider = { provide: 'ep:drive/files/create', useClass: ep___drive_files_create.default };
-const $drive_files_delete: Provider = { provide: 'ep:drive/files/delete', useClass: ep___drive_files_delete.default };
-const $drive_files_findByHash: Provider = { provide: 'ep:drive/files/find-by-hash', useClass: ep___drive_files_findByHash.default };
-const $drive_files_find: Provider = { provide: 'ep:drive/files/find', useClass: ep___drive_files_find.default };
-const $drive_files_show: Provider = { provide: 'ep:drive/files/show', useClass: ep___drive_files_show.default };
-const $drive_files_update: Provider = { provide: 'ep:drive/files/update', useClass: ep___drive_files_update.default };
-const $drive_files_uploadFromUrl: Provider = { provide: 'ep:drive/files/upload-from-url', useClass: ep___drive_files_uploadFromUrl.default };
-const $drive_folders: Provider = { provide: 'ep:drive/folders', useClass: ep___drive_folders.default };
-const $drive_folders_create: Provider = { provide: 'ep:drive/folders/create', useClass: ep___drive_folders_create.default };
-const $drive_folders_delete: Provider = { provide: 'ep:drive/folders/delete', useClass: ep___drive_folders_delete.default };
-const $drive_folders_find: Provider = { provide: 'ep:drive/folders/find', useClass: ep___drive_folders_find.default };
-const $drive_folders_show: Provider = { provide: 'ep:drive/folders/show', useClass: ep___drive_folders_show.default };
-const $drive_folders_update: Provider = { provide: 'ep:drive/folders/update', useClass: ep___drive_folders_update.default };
-const $drive_stream: Provider = { provide: 'ep:drive/stream', useClass: ep___drive_stream.default };
-const $emailAddress_available: Provider = { provide: 'ep:email-address/available', useClass: ep___emailAddress_available.default };
-const $endpoint: Provider = { provide: 'ep:endpoint', useClass: ep___endpoint.default };
-const $endpoints: Provider = { provide: 'ep:endpoints', useClass: ep___endpoints.default };
-const $exportCustomEmojis: Provider = { provide: 'ep:export-custom-emojis', useClass: ep___exportCustomEmojis.default };
-const $federation_followers: Provider = { provide: 'ep:federation/followers', useClass: ep___federation_followers.default };
-const $federation_following: Provider = { provide: 'ep:federation/following', useClass: ep___federation_following.default };
-const $federation_instances: Provider = { provide: 'ep:federation/instances', useClass: ep___federation_instances.default };
-const $federation_showInstance: Provider = { provide: 'ep:federation/show-instance', useClass: ep___federation_showInstance.default };
-const $federation_updateRemoteUser: Provider = { provide: 'ep:federation/update-remote-user', useClass: ep___federation_updateRemoteUser.default };
-const $federation_users: Provider = { provide: 'ep:federation/users', useClass: ep___federation_users.default };
-const $federation_stats: Provider = { provide: 'ep:federation/stats', useClass: ep___federation_stats.default };
-const $following_create: Provider = { provide: 'ep:following/create', useClass: ep___following_create.default };
-const $following_delete: Provider = { provide: 'ep:following/delete', useClass: ep___following_delete.default };
-const $following_update: Provider = { provide: 'ep:following/update', useClass: ep___following_update.default };
-const $following_update_all: Provider = { provide: 'ep:following/update-all', useClass: ep___following_update_all.default };
-const $following_invalidate: Provider = { provide: 'ep:following/invalidate', useClass: ep___following_invalidate.default };
-const $following_requests_accept: Provider = { provide: 'ep:following/requests/accept', useClass: ep___following_requests_accept.default };
-const $following_requests_cancel: Provider = { provide: 'ep:following/requests/cancel', useClass: ep___following_requests_cancel.default };
-const $following_requests_list: Provider = { provide: 'ep:following/requests/list', useClass: ep___following_requests_list.default };
-const $following_requests_sent: Provider = { provide: 'ep:following/requests/sent', useClass: ep___following_requests_sent.default };
-const $following_requests_reject: Provider = { provide: 'ep:following/requests/reject', useClass: ep___following_requests_reject.default };
-const $gallery_featured: Provider = { provide: 'ep:gallery/featured', useClass: ep___gallery_featured.default };
-const $gallery_popular: Provider = { provide: 'ep:gallery/popular', useClass: ep___gallery_popular.default };
-const $gallery_posts: Provider = { provide: 'ep:gallery/posts', useClass: ep___gallery_posts.default };
-const $gallery_posts_create: Provider = { provide: 'ep:gallery/posts/create', useClass: ep___gallery_posts_create.default };
-const $gallery_posts_delete: Provider = { provide: 'ep:gallery/posts/delete', useClass: ep___gallery_posts_delete.default };
-const $gallery_posts_like: Provider = { provide: 'ep:gallery/posts/like', useClass: ep___gallery_posts_like.default };
-const $gallery_posts_show: Provider = { provide: 'ep:gallery/posts/show', useClass: ep___gallery_posts_show.default };
-const $gallery_posts_unlike: Provider = { provide: 'ep:gallery/posts/unlike', useClass: ep___gallery_posts_unlike.default };
-const $gallery_posts_update: Provider = { provide: 'ep:gallery/posts/update', useClass: ep___gallery_posts_update.default };
-const $getOnlineUsersCount: Provider = { provide: 'ep:get-online-users-count', useClass: ep___getOnlineUsersCount.default };
-const $getAvatarDecorations: Provider = { provide: 'ep:get-avatar-decorations', useClass: ep___getAvatarDecorations.default };
-const $hashtags_list: Provider = { provide: 'ep:hashtags/list', useClass: ep___hashtags_list.default };
-const $hashtags_search: Provider = { provide: 'ep:hashtags/search', useClass: ep___hashtags_search.default };
-const $hashtags_show: Provider = { provide: 'ep:hashtags/show', useClass: ep___hashtags_show.default };
-const $hashtags_trend: Provider = { provide: 'ep:hashtags/trend', useClass: ep___hashtags_trend.default };
-const $hashtags_users: Provider = { provide: 'ep:hashtags/users', useClass: ep___hashtags_users.default };
-const $i: Provider = { provide: 'ep:i', useClass: ep___i.default };
-const $i_2fa_done: Provider = { provide: 'ep:i/2fa/done', useClass: ep___i_2fa_done.default };
-const $i_2fa_keyDone: Provider = { provide: 'ep:i/2fa/key-done', useClass: ep___i_2fa_keyDone.default };
-const $i_2fa_passwordLess: Provider = { provide: 'ep:i/2fa/password-less', useClass: ep___i_2fa_passwordLess.default };
-const $i_2fa_registerKey: Provider = { provide: 'ep:i/2fa/register-key', useClass: ep___i_2fa_registerKey.default };
-const $i_2fa_register: Provider = { provide: 'ep:i/2fa/register', useClass: ep___i_2fa_register.default };
-const $i_2fa_updateKey: Provider = { provide: 'ep:i/2fa/update-key', useClass: ep___i_2fa_updateKey.default };
-const $i_2fa_removeKey: Provider = { provide: 'ep:i/2fa/remove-key', useClass: ep___i_2fa_removeKey.default };
-const $i_2fa_unregister: Provider = { provide: 'ep:i/2fa/unregister', useClass: ep___i_2fa_unregister.default };
-const $i_apps: Provider = { provide: 'ep:i/apps', useClass: ep___i_apps.default };
-const $i_authorizedApps: Provider = { provide: 'ep:i/authorized-apps', useClass: ep___i_authorizedApps.default };
-const $i_claimAchievement: Provider = { provide: 'ep:i/claim-achievement', useClass: ep___i_claimAchievement.default };
-const $i_changePassword: Provider = { provide: 'ep:i/change-password', useClass: ep___i_changePassword.default };
-const $i_deleteAccount: Provider = { provide: 'ep:i/delete-account', useClass: ep___i_deleteAccount.default };
-const $i_exportData: Provider = { provide: 'ep:i/export-data', useClass: ep___i_exportData.default };
-const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass: ep___i_exportBlocking.default };
-const $i_exportFollowing: Provider = { provide: 'ep:i/export-following', useClass: ep___i_exportFollowing.default };
-const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_exportMute.default };
-const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default };
-const $i_exportClips: Provider = { provide: 'ep:i/export-clips', useClass: ep___i_exportClips.default };
-const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default };
-const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default };
-const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default };
-const $i_favorites: Provider = { provide: 'ep:i/favorites', useClass: ep___i_favorites.default };
-const $i_gallery_likes: Provider = { provide: 'ep:i/gallery/likes', useClass: ep___i_gallery_likes.default };
-const $i_gallery_posts: Provider = { provide: 'ep:i/gallery/posts', useClass: ep___i_gallery_posts.default };
-const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass: ep___i_importBlocking.default };
-const $i_importFollowing: Provider = { provide: 'ep:i/import-following', useClass: ep___i_importFollowing.default };
-const $i_importNotes: Provider = { provide: 'ep:i/import-notes', useClass: ep___i_importNotes.default };
-const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep___i_importMuting.default };
-const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default };
-const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default };
-const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default };
-const $i_notificationsGrouped: Provider = { provide: 'ep:i/notifications-grouped', useClass: ep___i_notificationsGrouped.default };
-const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default };
-const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default };
-const $i_pin: Provider = { provide: 'ep:i/pin', useClass: ep___i_pin.default };
-const $i_readAllUnreadNotes: Provider = { provide: 'ep:i/read-all-unread-notes', useClass: ep___i_readAllUnreadNotes.default };
-const $i_readAnnouncement: Provider = { provide: 'ep:i/read-announcement', useClass: ep___i_readAnnouncement.default };
-const $i_regenerateToken: Provider = { provide: 'ep:i/regenerate-token', useClass: ep___i_regenerateToken.default };
-const $i_registry_getAll: Provider = { provide: 'ep:i/registry/get-all', useClass: ep___i_registry_getAll.default };
-const $i_registry_getUnsecure: Provider = { provide: 'ep:i/registry/get-unsecure', useClass: ep___i_registry_getUnsecure.default };
-const $i_registry_getDetail: Provider = { provide: 'ep:i/registry/get-detail', useClass: ep___i_registry_getDetail.default };
-const $i_registry_get: Provider = { provide: 'ep:i/registry/get', useClass: ep___i_registry_get.default };
-const $i_registry_keysWithType: Provider = { provide: 'ep:i/registry/keys-with-type', useClass: ep___i_registry_keysWithType.default };
-const $i_registry_keys: Provider = { provide: 'ep:i/registry/keys', useClass: ep___i_registry_keys.default };
-const $i_registry_remove: Provider = { provide: 'ep:i/registry/remove', useClass: ep___i_registry_remove.default };
-const $i_registry_scopesWithDomain: Provider = { provide: 'ep:i/registry/scopes-with-domain', useClass: ep___i_registry_scopesWithDomain.default };
-const $i_registry_set: Provider = { provide: 'ep:i/registry/set', useClass: ep___i_registry_set.default };
-const $i_revokeToken: Provider = { provide: 'ep:i/revoke-token', useClass: ep___i_revokeToken.default };
-const $i_signinHistory: Provider = { provide: 'ep:i/signin-history', useClass: ep___i_signinHistory.default };
-const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.default };
-const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
-const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
-const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default };
-const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
-const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
-const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
-const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default };
-const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default };
-const $i_webhooks_test: Provider = { provide: 'ep:i/webhooks/test', useClass: ep___i_webhooks_test.default };
-const $invite_create: Provider = { provide: 'ep:invite/create', useClass: ep___invite_create.default };
-const $invite_delete: Provider = { provide: 'ep:invite/delete', useClass: ep___invite_delete.default };
-const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invite_list.default };
-const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default };
-const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
-const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
-const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
-const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
-const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
-const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
-const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default };
-const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default };
-const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default };
-const $renoteMute_list: Provider = { provide: 'ep:renote-mute/list', useClass: ep___renoteMute_list.default };
-const $my_apps: Provider = { provide: 'ep:my/apps', useClass: ep___my_apps.default };
-const $notes: Provider = { provide: 'ep:notes', useClass: ep___notes.default };
-const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep___notes_children.default };
-const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes_clips.default };
-const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
-const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default };
-const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default };
-const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
-const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
-const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
-const $notes_following: Provider = { provide: 'ep:notes/following', useClass: ep___notes_following.default };
-const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
-const $notes_bubbleTimeline: Provider = { provide: 'ep:notes/bubble-timeline', useClass: ep___notes_bubbleTimeline.default };
-const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
-const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default };
-const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
-const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default };
-const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default };
-const $notes_polls_refresh: Provider = { provide: 'ep:notes/polls/refresh', useClass: ep___notes_polls_refresh.default };
-const $notes_reactions: Provider = { provide: 'ep:notes/reactions', useClass: ep___notes_reactions.default };
-const $notes_reactions_create: Provider = { provide: 'ep:notes/reactions/create', useClass: ep___notes_reactions_create.default };
-const $notes_reactions_delete: Provider = { provide: 'ep:notes/reactions/delete', useClass: ep___notes_reactions_delete.default };
-const $notes_like: Provider = { provide: 'ep:notes/like', useClass: ep___notes_like.default };
-const $notes_renotes: Provider = { provide: 'ep:notes/renotes', useClass: ep___notes_renotes.default };
-const $notes_replies: Provider = { provide: 'ep:notes/replies', useClass: ep___notes_replies.default };
-const $notes_schedule_create: Provider = { provide: 'ep:notes/schedule/create', useClass: ep___notes_schedule_create.default };
-const $notes_schedule_delete: Provider = { provide: 'ep:notes/schedule/delete', useClass: ep___notes_schedule_delete.default };
-const $notes_schedule_list: Provider = { provide: 'ep:notes/schedule/list', useClass: ep___notes_schedule_list.default };
-const $notes_searchByTag: Provider = { provide: 'ep:notes/search-by-tag', useClass: ep___notes_searchByTag.default };
-const $notes_search: Provider = { provide: 'ep:notes/search', useClass: ep___notes_search.default };
-const $notes_show: Provider = { provide: 'ep:notes/show', useClass: ep___notes_show.default };
-const $notes_state: Provider = { provide: 'ep:notes/state', useClass: ep___notes_state.default };
-const $notes_threadMuting_create: Provider = { provide: 'ep:notes/thread-muting/create', useClass: ep___notes_threadMuting_create.default };
-const $notes_threadMuting_delete: Provider = { provide: 'ep:notes/thread-muting/delete', useClass: ep___notes_threadMuting_delete.default };
-const $notes_timeline: Provider = { provide: 'ep:notes/timeline', useClass: ep___notes_timeline.default };
-const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep___notes_translate.default };
-const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default };
-const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
-const $notes_edit: Provider = { provide: 'ep:notes/edit', useClass: ep___notes_edit.default };
-const $notes_versions: Provider = { provide: 'ep:notes/versions', useClass: ep___notes_versions.default };
-const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
-const $notifications_flush: Provider = { provide: 'ep:notifications/flush', useClass: ep___notifications_flush.default };
-const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
-const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default };
-const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
-const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default };
-const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default };
-const $pages_featured: Provider = { provide: 'ep:pages/featured', useClass: ep___pages_featured.default };
-const $pages_like: Provider = { provide: 'ep:pages/like', useClass: ep___pages_like.default };
-const $pages_show: Provider = { provide: 'ep:pages/show', useClass: ep___pages_show.default };
-const $pages_unlike: Provider = { provide: 'ep:pages/unlike', useClass: ep___pages_unlike.default };
-const $pages_update: Provider = { provide: 'ep:pages/update', useClass: ep___pages_update.default };
-const $flash_create: Provider = { provide: 'ep:flash/create', useClass: ep___flash_create.default };
-const $flash_delete: Provider = { provide: 'ep:flash/delete', useClass: ep___flash_delete.default };
-const $flash_featured: Provider = { provide: 'ep:flash/featured', useClass: ep___flash_featured.default };
-const $flash_like: Provider = { provide: 'ep:flash/like', useClass: ep___flash_like.default };
-const $flash_show: Provider = { provide: 'ep:flash/show', useClass: ep___flash_show.default };
-const $flash_unlike: Provider = { provide: 'ep:flash/unlike', useClass: ep___flash_unlike.default };
-const $flash_update: Provider = { provide: 'ep:flash/update', useClass: ep___flash_update.default };
-const $flash_my: Provider = { provide: 'ep:flash/my', useClass: ep___flash_my.default };
-const $flash_myLikes: Provider = { provide: 'ep:flash/my-likes', useClass: ep___flash_myLikes.default };
-const $ping: Provider = { provide: 'ep:ping', useClass: ep___ping.default };
-const $pinnedUsers: Provider = { provide: 'ep:pinned-users', useClass: ep___pinnedUsers.default };
-const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_read.default };
-const $roles_list: Provider = { provide: 'ep:roles/list', useClass: ep___roles_list.default };
-const $roles_show: Provider = { provide: 'ep:roles/show', useClass: ep___roles_show.default };
-const $roles_users: Provider = { provide: 'ep:roles/users', useClass: ep___roles_users.default };
-const $roles_notes: Provider = { provide: 'ep:roles/notes', useClass: ep___roles_notes.default };
-const $requestResetPassword: Provider = { provide: 'ep:request-reset-password', useClass: ep___requestResetPassword.default };
-const $resetDb: Provider = { provide: 'ep:reset-db', useClass: ep___resetDb.default };
-const $resetPassword: Provider = { provide: 'ep:reset-password', useClass: ep___resetPassword.default };
-const $serverInfo: Provider = { provide: 'ep:server-info', useClass: ep___serverInfo.default };
-const $stats: Provider = { provide: 'ep:stats', useClass: ep___stats.default };
-const $sw_show_registration: Provider = { provide: 'ep:sw/show-registration', useClass: ep___sw_show_registration.default };
-const $sw_update_registration: Provider = { provide: 'ep:sw/update-registration', useClass: ep___sw_update_registration.default };
-const $sw_register: Provider = { provide: 'ep:sw/register', useClass: ep___sw_register.default };
-const $sw_unregister: Provider = { provide: 'ep:sw/unregister', useClass: ep___sw_unregister.default };
-const $test: Provider = { provide: 'ep:test', useClass: ep___test.default };
-const $username_available: Provider = { provide: 'ep:username/available', useClass: ep___username_available.default };
-const $users: Provider = { provide: 'ep:users', useClass: ep___users.default };
-const $users_clips: Provider = { provide: 'ep:users/clips', useClass: ep___users_clips.default };
-const $users_followers: Provider = { provide: 'ep:users/followers', useClass: ep___users_followers.default };
-const $users_following: Provider = { provide: 'ep:users/following', useClass: ep___users_following.default };
-const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
-const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
-const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default };
-const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
-const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
-const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default };
-const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass: ep___users_lists_pull.default };
-const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default };
-const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default };
-const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default };
-const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default };
-const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default };
-const $users_lists_createFromPublic: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_createFromPublic.default };
-const $users_lists_updateMembership: Provider = { provide: 'ep:users/lists/update-membership', useClass: ep___users_lists_updateMembership.default };
-const $users_lists_getMemberships: Provider = { provide: 'ep:users/lists/get-memberships', useClass: ep___users_lists_getMemberships.default };
-const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default };
-const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
-const $users_flashs: Provider = { provide: 'ep:users/flashs', useClass: ep___users_flashs.default };
-const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default };
-const $users_recommendation: Provider = { provide: 'ep:users/recommendation', useClass: ep___users_recommendation.default };
-const $users_relation: Provider = { provide: 'ep:users/relation', useClass: ep___users_relation.default };
-const $users_reportAbuse: Provider = { provide: 'ep:users/report-abuse', useClass: ep___users_reportAbuse.default };
-const $users_searchByUsernameAndHost: Provider = { provide: 'ep:users/search-by-username-and-host', useClass: ep___users_searchByUsernameAndHost.default };
-const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___users_search.default };
-const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default };
-const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default };
-const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default };
-const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
-const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resources', useClass: ep___fetchExternalResources.default };
-const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
-const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.default };
-const $bubbleGame_register: Provider = { provide: 'ep:bubble-game/register', useClass: ep___bubbleGame_register.default };
-const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useClass: ep___bubbleGame_ranking.default };
-const $reversi_cancelMatch: Provider = { provide: 'ep:reversi/cancel-match', useClass: ep___reversi_cancelMatch.default };
-const $reversi_games: Provider = { provide: 'ep:reversi/games', useClass: ep___reversi_games.default };
-const $reversi_match: Provider = { provide: 'ep:reversi/match', useClass: ep___reversi_match.default };
-const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useClass: ep___reversi_invitations.default };
-const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default };
-const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
-const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep___reversi_verify.default };
+const endpoints = Object.entries(endpointsObject);
+const endpointProviders = endpoints.map(([path, endpoint]): Provider => ({ provide: `ep:${path}`, useClass: endpoint.default }));
@Module({
imports: [
@@ -824,811 +21,10 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
providers: [
GetterService,
ApiLoggerService,
- $admin_meta,
- $admin_abuseUserReports,
- $admin_abuseReport_notificationRecipient_list,
- $admin_abuseReport_notificationRecipient_show,
- $admin_abuseReport_notificationRecipient_create,
- $admin_abuseReport_notificationRecipient_update,
- $admin_abuseReport_notificationRecipient_delete,
- $admin_accounts_create,
- $admin_accounts_delete,
- $admin_accounts_findByEmail,
- $admin_ad_create,
- $admin_ad_delete,
- $admin_ad_list,
- $admin_ad_update,
- $admin_announcements_create,
- $admin_announcements_delete,
- $admin_announcements_list,
- $admin_announcements_update,
- $admin_avatarDecorations_create,
- $admin_avatarDecorations_delete,
- $admin_avatarDecorations_list,
- $admin_avatarDecorations_update,
- $admin_deleteAllFilesOfAUser,
- $admin_unsetUserAvatar,
- $admin_unsetUserBanner,
- $admin_drive_cleanRemoteFiles,
- $admin_drive_cleanup,
- $admin_drive_files,
- $admin_drive_showFile,
- $admin_emoji_addAliasesBulk,
- $admin_emoji_add,
- $admin_emoji_copy,
- $admin_emoji_deleteBulk,
- $admin_emoji_delete,
- $admin_emoji_importZip,
- $admin_emoji_listRemote,
- $admin_emoji_list,
- $admin_emoji_removeAliasesBulk,
- $admin_emoji_setAliasesBulk,
- $admin_emoji_setCategoryBulk,
- $admin_emoji_setLicenseBulk,
- $admin_emoji_update,
- $admin_federation_deleteAllFiles,
- $admin_federation_refreshRemoteInstanceMetadata,
- $admin_federation_removeAllFollowing,
- $admin_federation_updateInstance,
- $admin_getIndexStats,
- $admin_getTableStats,
- $admin_getUserIps,
- $admin_invite_create,
- $admin_invite_list,
- $admin_promo_create,
- $admin_queue_clear,
- $admin_queue_deliverDelayed,
- $admin_queue_inboxDelayed,
- $admin_queue_promote,
- $admin_queue_stats,
- $admin_relays_add,
- $admin_relays_list,
- $admin_relays_remove,
- $admin_resetPassword,
- $admin_resolveAbuseUserReport,
- $admin_forwardAbuseUserReport,
- $admin_updateAbuseUserReport,
- $admin_sendEmail,
- $admin_serverInfo,
- $admin_showModerationLogs,
- $admin_showUser,
- $admin_showUsers,
- $admin_nsfwUser,
- $admin_unnsfwUser,
- $admin_silenceUser,
- $admin_unsilenceUser,
- $admin_suspendUser,
- $admin_approveUser,
- $admin_declineUser,
- $admin_unsuspendUser,
- $admin_updateMeta,
- $admin_deleteAccount,
- $admin_updateUserNote,
- $admin_roles_create,
- $admin_roles_delete,
- $admin_roles_list,
- $admin_roles_show,
- $admin_roles_update,
- $admin_roles_assign,
- $admin_roles_unassign,
- $admin_roles_updateDefaultPolicies,
- $admin_roles_users,
- $admin_systemWebhook_create,
- $admin_systemWebhook_delete,
- $admin_systemWebhook_list,
- $admin_systemWebhook_show,
- $admin_systemWebhook_update,
- $admin_systemWebhook_test,
- $announcements,
- $announcements_show,
- $antennas_create,
- $antennas_delete,
- $antennas_list,
- $antennas_notes,
- $antennas_show,
- $antennas_update,
- $ap_get,
- $ap_show,
- $app_create,
- $app_show,
- $auth_accept,
- $auth_session_generate,
- $auth_session_show,
- $auth_session_userkey,
- $blocking_create,
- $blocking_delete,
- $blocking_list,
- $channels_create,
- $channels_featured,
- $channels_follow,
- $channels_followed,
- $channels_owned,
- $channels_show,
- $channels_timeline,
- $channels_unfollow,
- $channels_update,
- $channels_favorite,
- $channels_unfavorite,
- $channels_myFavorites,
- $channels_search,
- $charts_activeUsers,
- $charts_apRequest,
- $charts_drive,
- $charts_federation,
- $charts_instance,
- $charts_notes,
- $charts_user_drive,
- $charts_user_following,
- $charts_user_notes,
- $charts_user_pv,
- $charts_user_reactions,
- $charts_users,
- $clips_addNote,
- $clips_removeNote,
- $clips_create,
- $clips_delete,
- $clips_list,
- $clips_notes,
- $clips_show,
- $clips_update,
- $clips_favorite,
- $clips_unfavorite,
- $clips_myFavorites,
- $drive,
- $drive_files,
- $drive_files_attachedNotes,
- $drive_files_checkExistence,
- $drive_files_create,
- $drive_files_delete,
- $drive_files_findByHash,
- $drive_files_find,
- $drive_files_show,
- $drive_files_update,
- $drive_files_uploadFromUrl,
- $drive_folders,
- $drive_folders_create,
- $drive_folders_delete,
- $drive_folders_find,
- $drive_folders_show,
- $drive_folders_update,
- $drive_stream,
- $emailAddress_available,
- $endpoint,
- $endpoints,
- $exportCustomEmojis,
- $federation_followers,
- $federation_following,
- $federation_instances,
- $federation_showInstance,
- $federation_updateRemoteUser,
- $federation_users,
- $federation_stats,
- $following_create,
- $following_delete,
- $following_update,
- $following_update_all,
- $following_invalidate,
- $following_requests_accept,
- $following_requests_cancel,
- $following_requests_list,
- $following_requests_sent,
- $following_requests_reject,
- $gallery_featured,
- $gallery_popular,
- $gallery_posts,
- $gallery_posts_create,
- $gallery_posts_delete,
- $gallery_posts_like,
- $gallery_posts_show,
- $gallery_posts_unlike,
- $gallery_posts_update,
- $getOnlineUsersCount,
- $getAvatarDecorations,
- $hashtags_list,
- $hashtags_search,
- $hashtags_show,
- $hashtags_trend,
- $hashtags_users,
- $i,
- $i_2fa_done,
- $i_2fa_keyDone,
- $i_2fa_passwordLess,
- $i_2fa_registerKey,
- $i_2fa_register,
- $i_2fa_updateKey,
- $i_2fa_removeKey,
- $i_2fa_unregister,
- $i_apps,
- $i_authorizedApps,
- $i_claimAchievement,
- $i_changePassword,
- $i_deleteAccount,
- $i_exportData,
- $i_exportBlocking,
- $i_exportFollowing,
- $i_exportMute,
- $i_exportNotes,
- $i_exportClips,
- $i_exportFavorites,
- $i_exportUserLists,
- $i_exportAntennas,
- $i_favorites,
- $i_gallery_likes,
- $i_gallery_posts,
- $i_importBlocking,
- $i_importFollowing,
- $i_importNotes,
- $i_importMuting,
- $i_importUserLists,
- $i_importAntennas,
- $i_notifications,
- $i_notificationsGrouped,
- $i_pageLikes,
- $i_pages,
- $i_pin,
- $i_readAllUnreadNotes,
- $i_readAnnouncement,
- $i_regenerateToken,
- $i_registry_getAll,
- $i_registry_getUnsecure,
- $i_registry_getDetail,
- $i_registry_get,
- $i_registry_keysWithType,
- $i_registry_keys,
- $i_registry_remove,
- $i_registry_scopesWithDomain,
- $i_registry_set,
- $i_revokeToken,
- $i_signinHistory,
- $i_unpin,
- $i_updateEmail,
- $i_update,
- $i_move,
- $i_webhooks_create,
- $i_webhooks_list,
- $i_webhooks_show,
- $i_webhooks_update,
- $i_webhooks_delete,
- $i_webhooks_test,
- $invite_create,
- $invite_delete,
- $invite_list,
- $invite_limit,
- $meta,
- $emojis,
- $emoji,
- $miauth_genToken,
- $mute_create,
- $mute_delete,
- $mute_list,
- $renoteMute_create,
- $renoteMute_delete,
- $renoteMute_list,
- $my_apps,
- $notes,
- $notes_children,
- $notes_clips,
- $notes_conversation,
- $notes_create,
- $notes_delete,
- $notes_favorites_create,
- $notes_favorites_delete,
- $notes_featured,
- $notes_following,
- $notes_globalTimeline,
- $notes_bubbleTimeline,
- $notes_hybridTimeline,
- $notes_localTimeline,
- $notes_mentions,
- $notes_polls_recommendation,
- $notes_polls_vote,
- $notes_polls_refresh,
- $notes_reactions,
- $notes_reactions_create,
- $notes_reactions_delete,
- $notes_like,
- $notes_renotes,
- $notes_replies,
- $notes_schedule_create,
- $notes_schedule_delete,
- $notes_schedule_list,
- $notes_searchByTag,
- $notes_search,
- $notes_show,
- $notes_state,
- $notes_threadMuting_create,
- $notes_threadMuting_delete,
- $notes_timeline,
- $notes_translate,
- $notes_unrenote,
- $notes_userListTimeline,
- $notes_edit,
- $notes_versions,
- $notifications_create,
- $notifications_flush,
- $notifications_markAllAsRead,
- $notifications_testNotification,
- $pagePush,
- $pages_create,
- $pages_delete,
- $pages_featured,
- $pages_like,
- $pages_show,
- $pages_unlike,
- $pages_update,
- $flash_create,
- $flash_delete,
- $flash_featured,
- $flash_like,
- $flash_show,
- $flash_unlike,
- $flash_update,
- $flash_my,
- $flash_myLikes,
- $ping,
- $pinnedUsers,
- $promo_read,
- $roles_list,
- $roles_show,
- $roles_users,
- $roles_notes,
- $requestResetPassword,
- $resetDb,
- $resetPassword,
- $serverInfo,
- $stats,
- $sw_show_registration,
- $sw_update_registration,
- $sw_register,
- $sw_unregister,
- $test,
- $username_available,
- $users,
- $users_clips,
- $users_followers,
- $users_following,
- $users_gallery_posts,
- $users_getFrequentlyRepliedUsers,
- $users_featuredNotes,
- $users_lists_create,
- $users_lists_delete,
- $users_lists_list,
- $users_lists_pull,
- $users_lists_push,
- $users_lists_show,
- $users_lists_update,
- $users_lists_favorite,
- $users_lists_unfavorite,
- $users_lists_createFromPublic,
- $users_lists_updateMembership,
- $users_lists_getMemberships,
- $users_notes,
- $users_pages,
- $users_flashs,
- $users_reactions,
- $users_recommendation,
- $users_relation,
- $users_reportAbuse,
- $users_searchByUsernameAndHost,
- $users_search,
- $users_show,
- $users_achievements,
- $users_updateMemo,
- $fetchRss,
- $fetchExternalResources,
- $retention,
- $sponsors,
- $bubbleGame_register,
- $bubbleGame_ranking,
- $reversi_cancelMatch,
- $reversi_games,
- $reversi_match,
- $reversi_invitations,
- $reversi_showGame,
- $reversi_surrender,
- $reversi_verify,
+ ...endpointProviders,
],
exports: [
- $admin_meta,
- $admin_abuseUserReports,
- $admin_abuseReport_notificationRecipient_list,
- $admin_abuseReport_notificationRecipient_show,
- $admin_abuseReport_notificationRecipient_create,
- $admin_abuseReport_notificationRecipient_update,
- $admin_abuseReport_notificationRecipient_delete,
- $admin_accounts_create,
- $admin_accounts_delete,
- $admin_accounts_findByEmail,
- $admin_ad_create,
- $admin_ad_delete,
- $admin_ad_list,
- $admin_ad_update,
- $admin_announcements_create,
- $admin_announcements_delete,
- $admin_announcements_list,
- $admin_announcements_update,
- $admin_avatarDecorations_create,
- $admin_avatarDecorations_delete,
- $admin_avatarDecorations_list,
- $admin_avatarDecorations_update,
- $admin_deleteAllFilesOfAUser,
- $admin_unsetUserAvatar,
- $admin_unsetUserBanner,
- $admin_drive_cleanRemoteFiles,
- $admin_drive_cleanup,
- $admin_drive_files,
- $admin_drive_showFile,
- $admin_emoji_addAliasesBulk,
- $admin_emoji_add,
- $admin_emoji_copy,
- $admin_emoji_deleteBulk,
- $admin_emoji_delete,
- $admin_emoji_importZip,
- $admin_emoji_listRemote,
- $admin_emoji_list,
- $admin_emoji_removeAliasesBulk,
- $admin_emoji_setAliasesBulk,
- $admin_emoji_setCategoryBulk,
- $admin_emoji_setLicenseBulk,
- $admin_emoji_update,
- $admin_federation_deleteAllFiles,
- $admin_federation_refreshRemoteInstanceMetadata,
- $admin_federation_removeAllFollowing,
- $admin_federation_updateInstance,
- $admin_getIndexStats,
- $admin_getTableStats,
- $admin_getUserIps,
- $admin_invite_create,
- $admin_invite_list,
- $admin_promo_create,
- $admin_queue_clear,
- $admin_queue_deliverDelayed,
- $admin_queue_inboxDelayed,
- $admin_queue_promote,
- $admin_queue_stats,
- $admin_relays_add,
- $admin_relays_list,
- $admin_relays_remove,
- $admin_resetPassword,
- $admin_resolveAbuseUserReport,
- $admin_forwardAbuseUserReport,
- $admin_updateAbuseUserReport,
- $admin_sendEmail,
- $admin_serverInfo,
- $admin_showModerationLogs,
- $admin_showUser,
- $admin_showUsers,
- $admin_nsfwUser,
- $admin_unnsfwUser,
- $admin_silenceUser,
- $admin_unsilenceUser,
- $admin_suspendUser,
- $admin_approveUser,
- $admin_declineUser,
- $admin_unsuspendUser,
- $admin_updateMeta,
- $admin_deleteAccount,
- $admin_updateUserNote,
- $admin_roles_create,
- $admin_roles_delete,
- $admin_roles_list,
- $admin_roles_show,
- $admin_roles_update,
- $admin_roles_assign,
- $admin_roles_unassign,
- $admin_roles_updateDefaultPolicies,
- $admin_roles_users,
- $admin_systemWebhook_create,
- $admin_systemWebhook_delete,
- $admin_systemWebhook_list,
- $admin_systemWebhook_show,
- $admin_systemWebhook_update,
- $admin_systemWebhook_test,
- $announcements,
- $announcements_show,
- $antennas_create,
- $antennas_delete,
- $antennas_list,
- $antennas_notes,
- $antennas_show,
- $antennas_update,
- $ap_get,
- $ap_show,
- $app_create,
- $app_show,
- $auth_accept,
- $auth_session_generate,
- $auth_session_show,
- $auth_session_userkey,
- $blocking_create,
- $blocking_delete,
- $blocking_list,
- $channels_create,
- $channels_featured,
- $channels_follow,
- $channels_followed,
- $channels_owned,
- $channels_show,
- $channels_timeline,
- $channels_unfollow,
- $channels_update,
- $channels_favorite,
- $channels_unfavorite,
- $channels_myFavorites,
- $channels_search,
- $charts_activeUsers,
- $charts_apRequest,
- $charts_drive,
- $charts_federation,
- $charts_instance,
- $charts_notes,
- $charts_user_drive,
- $charts_user_following,
- $charts_user_notes,
- $charts_user_pv,
- $charts_user_reactions,
- $charts_users,
- $clips_addNote,
- $clips_removeNote,
- $clips_create,
- $clips_delete,
- $clips_list,
- $clips_notes,
- $clips_show,
- $clips_update,
- $clips_favorite,
- $clips_unfavorite,
- $clips_myFavorites,
- $drive,
- $drive_files,
- $drive_files_attachedNotes,
- $drive_files_checkExistence,
- $drive_files_create,
- $drive_files_delete,
- $drive_files_findByHash,
- $drive_files_find,
- $drive_files_show,
- $drive_files_update,
- $drive_files_uploadFromUrl,
- $drive_folders,
- $drive_folders_create,
- $drive_folders_delete,
- $drive_folders_find,
- $drive_folders_show,
- $drive_folders_update,
- $drive_stream,
- $emailAddress_available,
- $endpoint,
- $endpoints,
- $exportCustomEmojis,
- $federation_followers,
- $federation_following,
- $federation_instances,
- $federation_showInstance,
- $federation_updateRemoteUser,
- $federation_users,
- $federation_stats,
- $following_create,
- $following_delete,
- $following_update,
- $following_update_all,
- $following_invalidate,
- $following_requests_accept,
- $following_requests_cancel,
- $following_requests_list,
- $following_requests_reject,
- $gallery_featured,
- $gallery_popular,
- $gallery_posts,
- $gallery_posts_create,
- $gallery_posts_delete,
- $gallery_posts_like,
- $gallery_posts_show,
- $gallery_posts_unlike,
- $gallery_posts_update,
- $getOnlineUsersCount,
- $getAvatarDecorations,
- $hashtags_list,
- $hashtags_search,
- $hashtags_show,
- $hashtags_trend,
- $hashtags_users,
- $i,
- $i_2fa_done,
- $i_2fa_keyDone,
- $i_2fa_passwordLess,
- $i_2fa_registerKey,
- $i_2fa_register,
- $i_2fa_updateKey,
- $i_2fa_removeKey,
- $i_2fa_unregister,
- $i_apps,
- $i_authorizedApps,
- $i_claimAchievement,
- $i_changePassword,
- $i_deleteAccount,
- $i_exportData,
- $i_exportBlocking,
- $i_exportFollowing,
- $i_exportMute,
- $i_exportNotes,
- $i_exportClips,
- $i_exportFavorites,
- $i_exportUserLists,
- $i_exportAntennas,
- $i_favorites,
- $i_gallery_likes,
- $i_gallery_posts,
- $i_importBlocking,
- $i_importFollowing,
- $i_importNotes,
- $i_importMuting,
- $i_importUserLists,
- $i_importAntennas,
- $i_notifications,
- $i_notificationsGrouped,
- $i_pageLikes,
- $i_pages,
- $i_pin,
- $i_readAllUnreadNotes,
- $i_readAnnouncement,
- $i_regenerateToken,
- $i_registry_getAll,
- $i_registry_getUnsecure,
- $i_registry_getDetail,
- $i_registry_get,
- $i_registry_keysWithType,
- $i_registry_keys,
- $i_registry_remove,
- $i_registry_scopesWithDomain,
- $i_registry_set,
- $i_revokeToken,
- $i_signinHistory,
- $i_unpin,
- $i_updateEmail,
- $i_update,
- $i_move,
- $i_webhooks_create,
- $i_webhooks_list,
- $i_webhooks_show,
- $i_webhooks_update,
- $i_webhooks_delete,
- $i_webhooks_test,
- $invite_create,
- $invite_delete,
- $invite_list,
- $invite_limit,
- $meta,
- $emojis,
- $emoji,
- $miauth_genToken,
- $mute_create,
- $mute_delete,
- $mute_list,
- $renoteMute_create,
- $renoteMute_delete,
- $renoteMute_list,
- $my_apps,
- $notes,
- $notes_children,
- $notes_clips,
- $notes_conversation,
- $notes_create,
- $notes_delete,
- $notes_favorites_create,
- $notes_favorites_delete,
- $notes_featured,
- $notes_following,
- $notes_globalTimeline,
- $notes_bubbleTimeline,
- $notes_hybridTimeline,
- $notes_localTimeline,
- $notes_mentions,
- $notes_polls_recommendation,
- $notes_polls_vote,
- $notes_polls_refresh,
- $notes_reactions,
- $notes_reactions_create,
- $notes_reactions_delete,
- $notes_like,
- $notes_renotes,
- $notes_replies,
- $notes_schedule_create,
- $notes_schedule_delete,
- $notes_schedule_list,
- $notes_searchByTag,
- $notes_search,
- $notes_show,
- $notes_state,
- $notes_threadMuting_create,
- $notes_threadMuting_delete,
- $notes_timeline,
- $notes_translate,
- $notes_unrenote,
- $notes_userListTimeline,
- $notes_edit,
- $notes_versions,
- $notifications_create,
- $notifications_flush,
- $notifications_markAllAsRead,
- $notifications_testNotification,
- $pagePush,
- $pages_create,
- $pages_delete,
- $pages_featured,
- $pages_like,
- $pages_show,
- $pages_unlike,
- $pages_update,
- $flash_create,
- $flash_delete,
- $flash_featured,
- $flash_like,
- $flash_show,
- $flash_unlike,
- $flash_update,
- $flash_my,
- $flash_myLikes,
- $ping,
- $pinnedUsers,
- $promo_read,
- $roles_list,
- $roles_show,
- $roles_users,
- $roles_notes,
- $requestResetPassword,
- $resetDb,
- $resetPassword,
- $serverInfo,
- $stats,
- $sw_register,
- $sw_unregister,
- $test,
- $username_available,
- $users,
- $users_clips,
- $users_followers,
- $users_following,
- $users_gallery_posts,
- $users_getFrequentlyRepliedUsers,
- $users_featuredNotes,
- $users_lists_create,
- $users_lists_delete,
- $users_lists_list,
- $users_lists_pull,
- $users_lists_push,
- $users_lists_show,
- $users_lists_update,
- $users_lists_favorite,
- $users_lists_unfavorite,
- $users_lists_createFromPublic,
- $users_lists_updateMembership,
- $users_lists_getMemberships,
- $users_notes,
- $users_pages,
- $users_flashs,
- $users_reactions,
- $users_recommendation,
- $users_relation,
- $users_reportAbuse,
- $users_searchByUsernameAndHost,
- $users_search,
- $users_show,
- $users_achievements,
- $users_updateMemo,
- $fetchRss,
- $fetchExternalResources,
- $retention,
- $sponsors,
- $bubbleGame_register,
- $bubbleGame_ranking,
- $reversi_cancelMatch,
- $reversi_games,
- $reversi_match,
- $reversi_invitations,
- $reversi_showGame,
- $reversi_surrender,
- $reversi_verify,
+ ...endpointProviders,
],
})
export class EndpointsModule {}
diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts
new file mode 100644
index 0000000000..28f7cfea04
--- /dev/null
+++ b/packages/backend/src/server/api/endpoint-list.ts
@@ -0,0 +1,399 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/*
+ * This file contains list of all endpoints exported as pathname of API endpoint
+ *
+ * When you add new endpoint, you should add it to this file.
+ * This file is used to generate API documentation and EndpointsModule.
+ */
+
+export * as 'admin/abuse-report/notification-recipient/create' from './endpoints/admin/abuse-report/notification-recipient/create.js';
+export * as 'admin/abuse-report/notification-recipient/delete' from './endpoints/admin/abuse-report/notification-recipient/delete.js';
+export * as 'admin/abuse-report/notification-recipient/list' from './endpoints/admin/abuse-report/notification-recipient/list.js';
+export * as 'admin/abuse-report/notification-recipient/show' from './endpoints/admin/abuse-report/notification-recipient/show.js';
+export * as 'admin/abuse-report/notification-recipient/update' from './endpoints/admin/abuse-report/notification-recipient/update.js';
+export * as 'admin/abuse-user-reports' from './endpoints/admin/abuse-user-reports.js';
+export * as 'admin/accounts/create' from './endpoints/admin/accounts/create.js';
+export * as 'admin/accounts/delete' from './endpoints/admin/accounts/delete.js';
+export * as 'admin/accounts/find-by-email' from './endpoints/admin/accounts/find-by-email.js';
+export * as 'admin/ad/create' from './endpoints/admin/ad/create.js';
+export * as 'admin/ad/delete' from './endpoints/admin/ad/delete.js';
+export * as 'admin/ad/list' from './endpoints/admin/ad/list.js';
+export * as 'admin/ad/update' from './endpoints/admin/ad/update.js';
+export * as 'admin/announcements/create' from './endpoints/admin/announcements/create.js';
+export * as 'admin/announcements/delete' from './endpoints/admin/announcements/delete.js';
+export * as 'admin/announcements/list' from './endpoints/admin/announcements/list.js';
+export * as 'admin/announcements/update' from './endpoints/admin/announcements/update.js';
+export * as 'admin/avatar-decorations/create' from './endpoints/admin/avatar-decorations/create.js';
+export * as 'admin/avatar-decorations/delete' from './endpoints/admin/avatar-decorations/delete.js';
+export * as 'admin/avatar-decorations/list' from './endpoints/admin/avatar-decorations/list.js';
+export * as 'admin/avatar-decorations/update' from './endpoints/admin/avatar-decorations/update.js';
+export * as 'admin/captcha/current' from './endpoints/admin/captcha/current.js';
+export * as 'admin/captcha/save' from './endpoints/admin/captcha/save.js';
+export * as 'admin/delete-account' from './endpoints/admin/delete-account.js';
+export * as 'admin/delete-all-files-of-a-user' from './endpoints/admin/delete-all-files-of-a-user.js';
+export * as 'admin/drive/clean-remote-files' from './endpoints/admin/drive/clean-remote-files.js';
+export * as 'admin/drive/cleanup' from './endpoints/admin/drive/cleanup.js';
+export * as 'admin/drive/files' from './endpoints/admin/drive/files.js';
+export * as 'admin/drive/show-file' from './endpoints/admin/drive/show-file.js';
+export * as 'admin/emoji/add' from './endpoints/admin/emoji/add.js';
+export * as 'admin/emoji/add-aliases-bulk' from './endpoints/admin/emoji/add-aliases-bulk.js';
+export * as 'admin/emoji/copy' from './endpoints/admin/emoji/copy.js';
+export * as 'admin/emoji/delete' from './endpoints/admin/emoji/delete.js';
+export * as 'admin/emoji/delete-bulk' from './endpoints/admin/emoji/delete-bulk.js';
+export * as 'admin/emoji/import-zip' from './endpoints/admin/emoji/import-zip.js';
+export * as 'admin/emoji/list' from './endpoints/admin/emoji/list.js';
+export * as 'admin/emoji/list-remote' from './endpoints/admin/emoji/list-remote.js';
+export * as 'admin/emoji/remove-aliases-bulk' from './endpoints/admin/emoji/remove-aliases-bulk.js';
+export * as 'admin/emoji/set-aliases-bulk' from './endpoints/admin/emoji/set-aliases-bulk.js';
+export * as 'admin/emoji/set-category-bulk' from './endpoints/admin/emoji/set-category-bulk.js';
+export * as 'admin/emoji/set-license-bulk' from './endpoints/admin/emoji/set-license-bulk.js';
+export * as 'admin/emoji/update' from './endpoints/admin/emoji/update.js';
+export * as 'admin/federation/delete-all-files' from './endpoints/admin/federation/delete-all-files.js';
+export * as 'admin/federation/refresh-remote-instance-metadata' from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
+export * as 'admin/federation/remove-all-following' from './endpoints/admin/federation/remove-all-following.js';
+export * as 'admin/federation/update-instance' from './endpoints/admin/federation/update-instance.js';
+export * as 'admin/forward-abuse-user-report' from './endpoints/admin/forward-abuse-user-report.js';
+export * as 'admin/get-index-stats' from './endpoints/admin/get-index-stats.js';
+export * as 'admin/get-table-stats' from './endpoints/admin/get-table-stats.js';
+export * as 'admin/get-user-ips' from './endpoints/admin/get-user-ips.js';
+export * as 'admin/invite/create' from './endpoints/admin/invite/create.js';
+export * as 'admin/invite/list' from './endpoints/admin/invite/list.js';
+export * as 'admin/meta' from './endpoints/admin/meta.js';
+export * as 'admin/promo/create' from './endpoints/admin/promo/create.js';
+export * as 'admin/queue/clear' from './endpoints/admin/queue/clear.js';
+export * as 'admin/queue/deliver-delayed' from './endpoints/admin/queue/deliver-delayed.js';
+export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-delayed.js';
+export * as 'admin/queue/promote' from './endpoints/admin/queue/promote.js';
+export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js';
+export * as 'admin/relays/add' from './endpoints/admin/relays/add.js';
+export * as 'admin/relays/list' from './endpoints/admin/relays/list.js';
+export * as 'admin/relays/remove' from './endpoints/admin/relays/remove.js';
+export * as 'admin/reset-password' from './endpoints/admin/reset-password.js';
+export * as 'admin/resolve-abuse-user-report' from './endpoints/admin/resolve-abuse-user-report.js';
+export * as 'admin/roles/assign' from './endpoints/admin/roles/assign.js';
+export * as 'admin/roles/create' from './endpoints/admin/roles/create.js';
+export * as 'admin/roles/delete' from './endpoints/admin/roles/delete.js';
+export * as 'admin/roles/list' from './endpoints/admin/roles/list.js';
+export * as 'admin/roles/show' from './endpoints/admin/roles/show.js';
+export * as 'admin/roles/unassign' from './endpoints/admin/roles/unassign.js';
+export * as 'admin/roles/update' from './endpoints/admin/roles/update.js';
+export * as 'admin/roles/update-default-policies' from './endpoints/admin/roles/update-default-policies.js';
+export * as 'admin/roles/users' from './endpoints/admin/roles/users.js';
+export * as 'admin/send-email' from './endpoints/admin/send-email.js';
+export * as 'admin/server-info' from './endpoints/admin/server-info.js';
+export * as 'admin/show-moderation-logs' from './endpoints/admin/show-moderation-logs.js';
+export * as 'admin/show-user' from './endpoints/admin/show-user.js';
+export * as 'admin/show-users' from './endpoints/admin/show-users.js';
+export * as 'admin/suspend-user' from './endpoints/admin/suspend-user.js';
+export * as 'admin/system-webhook/create' from './endpoints/admin/system-webhook/create.js';
+export * as 'admin/system-webhook/delete' from './endpoints/admin/system-webhook/delete.js';
+export * as 'admin/system-webhook/list' from './endpoints/admin/system-webhook/list.js';
+export * as 'admin/system-webhook/show' from './endpoints/admin/system-webhook/show.js';
+export * as 'admin/system-webhook/test' from './endpoints/admin/system-webhook/test.js';
+export * as 'admin/system-webhook/update' from './endpoints/admin/system-webhook/update.js';
+export * as 'admin/unset-user-avatar' from './endpoints/admin/unset-user-avatar.js';
+export * as 'admin/unset-user-banner' from './endpoints/admin/unset-user-banner.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-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';
+export * as 'antennas/create' from './endpoints/antennas/create.js';
+export * as 'antennas/delete' from './endpoints/antennas/delete.js';
+export * as 'antennas/list' from './endpoints/antennas/list.js';
+export * as 'antennas/notes' from './endpoints/antennas/notes.js';
+export * as 'antennas/show' from './endpoints/antennas/show.js';
+export * as 'antennas/update' from './endpoints/antennas/update.js';
+export * as 'ap/get' from './endpoints/ap/get.js';
+export * as 'ap/show' from './endpoints/ap/show.js';
+export * as 'app/create' from './endpoints/app/create.js';
+export * as 'app/show' from './endpoints/app/show.js';
+export * as 'auth/accept' from './endpoints/auth/accept.js';
+export * as 'auth/session/generate' from './endpoints/auth/session/generate.js';
+export * as 'auth/session/show' from './endpoints/auth/session/show.js';
+export * as 'auth/session/userkey' from './endpoints/auth/session/userkey.js';
+export * as 'blocking/create' from './endpoints/blocking/create.js';
+export * as 'blocking/delete' from './endpoints/blocking/delete.js';
+export * as 'blocking/list' from './endpoints/blocking/list.js';
+export * as 'bubble-game/ranking' from './endpoints/bubble-game/ranking.js';
+export * as 'bubble-game/register' from './endpoints/bubble-game/register.js';
+export * as 'channels/create' from './endpoints/channels/create.js';
+export * as 'channels/favorite' from './endpoints/channels/favorite.js';
+export * as 'channels/featured' from './endpoints/channels/featured.js';
+export * as 'channels/follow' from './endpoints/channels/follow.js';
+export * as 'channels/followed' from './endpoints/channels/followed.js';
+export * as 'channels/my-favorites' from './endpoints/channels/my-favorites.js';
+export * as 'channels/owned' from './endpoints/channels/owned.js';
+export * as 'channels/search' from './endpoints/channels/search.js';
+export * as 'channels/show' from './endpoints/channels/show.js';
+export * as 'channels/timeline' from './endpoints/channels/timeline.js';
+export * as 'channels/unfavorite' from './endpoints/channels/unfavorite.js';
+export * as 'channels/unfollow' from './endpoints/channels/unfollow.js';
+export * as 'channels/update' from './endpoints/channels/update.js';
+export * as 'charts/active-users' from './endpoints/charts/active-users.js';
+export * as 'charts/ap-request' from './endpoints/charts/ap-request.js';
+export * as 'charts/drive' from './endpoints/charts/drive.js';
+export * as 'charts/federation' from './endpoints/charts/federation.js';
+export * as 'charts/instance' from './endpoints/charts/instance.js';
+export * as 'charts/notes' from './endpoints/charts/notes.js';
+export * as 'charts/user/drive' from './endpoints/charts/user/drive.js';
+export * as 'charts/user/following' from './endpoints/charts/user/following.js';
+export * as 'charts/user/notes' from './endpoints/charts/user/notes.js';
+export * as 'charts/user/pv' from './endpoints/charts/user/pv.js';
+export * as 'charts/user/reactions' from './endpoints/charts/user/reactions.js';
+export * as 'charts/users' from './endpoints/charts/users.js';
+export * as 'clips/add-note' from './endpoints/clips/add-note.js';
+export * as 'clips/create' from './endpoints/clips/create.js';
+export * as 'clips/delete' from './endpoints/clips/delete.js';
+export * as 'clips/favorite' from './endpoints/clips/favorite.js';
+export * as 'clips/list' from './endpoints/clips/list.js';
+export * as 'clips/my-favorites' from './endpoints/clips/my-favorites.js';
+export * as 'clips/notes' from './endpoints/clips/notes.js';
+export * as 'clips/remove-note' from './endpoints/clips/remove-note.js';
+export * as 'clips/show' from './endpoints/clips/show.js';
+export * as 'clips/unfavorite' from './endpoints/clips/unfavorite.js';
+export * as 'clips/update' from './endpoints/clips/update.js';
+export * as 'drive' from './endpoints/drive.js';
+export * as 'drive/files' from './endpoints/drive/files.js';
+export * as 'drive/files/attached-notes' from './endpoints/drive/files/attached-notes.js';
+export * as 'drive/files/check-existence' from './endpoints/drive/files/check-existence.js';
+export * as 'drive/files/create' from './endpoints/drive/files/create.js';
+export * as 'drive/files/delete' from './endpoints/drive/files/delete.js';
+export * as 'drive/files/find' from './endpoints/drive/files/find.js';
+export * as 'drive/files/find-by-hash' from './endpoints/drive/files/find-by-hash.js';
+export * as 'drive/files/show' from './endpoints/drive/files/show.js';
+export * as 'drive/files/update' from './endpoints/drive/files/update.js';
+export * as 'drive/files/upload-from-url' from './endpoints/drive/files/upload-from-url.js';
+export * as 'drive/folders' from './endpoints/drive/folders.js';
+export * as 'drive/folders/create' from './endpoints/drive/folders/create.js';
+export * as 'drive/folders/delete' from './endpoints/drive/folders/delete.js';
+export * as 'drive/folders/find' from './endpoints/drive/folders/find.js';
+export * as 'drive/folders/show' from './endpoints/drive/folders/show.js';
+export * as 'drive/folders/update' from './endpoints/drive/folders/update.js';
+export * as 'drive/stream' from './endpoints/drive/stream.js';
+export * as 'email-address/available' from './endpoints/email-address/available.js';
+export * as 'emoji' from './endpoints/emoji.js';
+export * as 'emojis' from './endpoints/emojis.js';
+export * as 'endpoint' from './endpoints/endpoint.js';
+export * as 'endpoints' from './endpoints/endpoints.js';
+export * as 'export-custom-emojis' from './endpoints/export-custom-emojis.js';
+export * as 'federation/followers' from './endpoints/federation/followers.js';
+export * as 'federation/following' from './endpoints/federation/following.js';
+export * as 'federation/instances' from './endpoints/federation/instances.js';
+export * as 'federation/show-instance' from './endpoints/federation/show-instance.js';
+export * as 'federation/stats' from './endpoints/federation/stats.js';
+export * as 'federation/update-remote-user' from './endpoints/federation/update-remote-user.js';
+export * as 'federation/users' from './endpoints/federation/users.js';
+export * as 'fetch-external-resources' from './endpoints/fetch-external-resources.js';
+export * as 'fetch-rss' from './endpoints/fetch-rss.js';
+export * as 'flash/create' from './endpoints/flash/create.js';
+export * as 'flash/delete' from './endpoints/flash/delete.js';
+export * as 'flash/featured' from './endpoints/flash/featured.js';
+export * as 'flash/like' from './endpoints/flash/like.js';
+export * as 'flash/my' from './endpoints/flash/my.js';
+export * as 'flash/my-likes' from './endpoints/flash/my-likes.js';
+export * as 'flash/show' from './endpoints/flash/show.js';
+export * as 'flash/unlike' from './endpoints/flash/unlike.js';
+export * as 'flash/update' from './endpoints/flash/update.js';
+export * as 'following/create' from './endpoints/following/create.js';
+export * as 'following/delete' from './endpoints/following/delete.js';
+export * as 'following/invalidate' from './endpoints/following/invalidate.js';
+export * as 'following/requests/accept' from './endpoints/following/requests/accept.js';
+export * as 'following/requests/cancel' from './endpoints/following/requests/cancel.js';
+export * as 'following/requests/list' from './endpoints/following/requests/list.js';
+export * as 'following/requests/reject' from './endpoints/following/requests/reject.js';
+export * as 'following/requests/sent' from './endpoints/following/requests/sent.js';
+export * as 'following/update' from './endpoints/following/update.js';
+export * as 'following/update-all' from './endpoints/following/update-all.js';
+export * as 'gallery/featured' from './endpoints/gallery/featured.js';
+export * as 'gallery/popular' from './endpoints/gallery/popular.js';
+export * as 'gallery/posts' from './endpoints/gallery/posts.js';
+export * as 'gallery/posts/create' from './endpoints/gallery/posts/create.js';
+export * as 'gallery/posts/delete' from './endpoints/gallery/posts/delete.js';
+export * as 'gallery/posts/like' from './endpoints/gallery/posts/like.js';
+export * as 'gallery/posts/show' from './endpoints/gallery/posts/show.js';
+export * as 'gallery/posts/unlike' from './endpoints/gallery/posts/unlike.js';
+export * as 'gallery/posts/update' from './endpoints/gallery/posts/update.js';
+export * as 'get-avatar-decorations' from './endpoints/get-avatar-decorations.js';
+export * as 'get-online-users-count' from './endpoints/get-online-users-count.js';
+export * as 'hashtags/list' from './endpoints/hashtags/list.js';
+export * as 'hashtags/search' from './endpoints/hashtags/search.js';
+export * as 'hashtags/show' from './endpoints/hashtags/show.js';
+export * as 'hashtags/trend' from './endpoints/hashtags/trend.js';
+export * as 'hashtags/users' from './endpoints/hashtags/users.js';
+export * as 'i' from './endpoints/i.js';
+export * as 'i/2fa/done' from './endpoints/i/2fa/done.js';
+export * as 'i/2fa/key-done' from './endpoints/i/2fa/key-done.js';
+export * as 'i/2fa/password-less' from './endpoints/i/2fa/password-less.js';
+export * as 'i/2fa/register' from './endpoints/i/2fa/register.js';
+export * as 'i/2fa/register-key' from './endpoints/i/2fa/register-key.js';
+export * as 'i/2fa/remove-key' from './endpoints/i/2fa/remove-key.js';
+export * as 'i/2fa/unregister' from './endpoints/i/2fa/unregister.js';
+export * as 'i/2fa/update-key' from './endpoints/i/2fa/update-key.js';
+export * as 'i/apps' from './endpoints/i/apps.js';
+export * as 'i/authorized-apps' from './endpoints/i/authorized-apps.js';
+export * as 'i/change-password' from './endpoints/i/change-password.js';
+export * as 'i/claim-achievement' from './endpoints/i/claim-achievement.js';
+export * as 'i/delete-account' from './endpoints/i/delete-account.js';
+export * as 'i/export-antennas' from './endpoints/i/export-antennas.js';
+export * as 'i/export-blocking' from './endpoints/i/export-blocking.js';
+export * as 'i/export-clips' from './endpoints/i/export-clips.js';
+export * as 'i/export-favorites' from './endpoints/i/export-favorites.js';
+export * as 'i/export-following' from './endpoints/i/export-following.js';
+export * as 'i/export-mute' from './endpoints/i/export-mute.js';
+export * as 'i/export-notes' from './endpoints/i/export-notes.js';
+export * as 'i/export-user-lists' from './endpoints/i/export-user-lists.js';
+export * as 'i/favorites' from './endpoints/i/favorites.js';
+export * as 'i/gallery/likes' from './endpoints/i/gallery/likes.js';
+export * as 'i/gallery/posts' from './endpoints/i/gallery/posts.js';
+export * as 'i/import-antennas' from './endpoints/i/import-antennas.js';
+export * as 'i/import-blocking' from './endpoints/i/import-blocking.js';
+export * as 'i/import-following' from './endpoints/i/import-following.js';
+export * as 'i/import-muting' from './endpoints/i/import-muting.js';
+export * as 'i/import-user-lists' from './endpoints/i/import-user-lists.js';
+export * as 'i/move' from './endpoints/i/move.js';
+export * as 'i/notifications' from './endpoints/i/notifications.js';
+export * as 'i/notifications-grouped' from './endpoints/i/notifications-grouped.js';
+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';
+export * as 'i/registry/get-all' from './endpoints/i/registry/get-all.js';
+export * as 'i/registry/get-detail' from './endpoints/i/registry/get-detail.js';
+export * as 'i/registry/keys' from './endpoints/i/registry/keys.js';
+export * as 'i/registry/keys-with-type' from './endpoints/i/registry/keys-with-type.js';
+export * as 'i/registry/remove' from './endpoints/i/registry/remove.js';
+export * as 'i/registry/scopes-with-domain' from './endpoints/i/registry/scopes-with-domain.js';
+export * as 'i/registry/set' from './endpoints/i/registry/set.js';
+export * as 'i/revoke-token' from './endpoints/i/revoke-token.js';
+export * as 'i/signin-history' from './endpoints/i/signin-history.js';
+export * as 'i/unpin' from './endpoints/i/unpin.js';
+export * as 'i/update' from './endpoints/i/update.js';
+export * as 'i/update-email' from './endpoints/i/update-email.js';
+export * as 'i/webhooks/create' from './endpoints/i/webhooks/create.js';
+export * as 'i/webhooks/delete' from './endpoints/i/webhooks/delete.js';
+export * as 'i/webhooks/list' from './endpoints/i/webhooks/list.js';
+export * as 'i/webhooks/show' from './endpoints/i/webhooks/show.js';
+export * as 'i/webhooks/test' from './endpoints/i/webhooks/test.js';
+export * as 'i/webhooks/update' from './endpoints/i/webhooks/update.js';
+export * as 'invite/create' from './endpoints/invite/create.js';
+export * as 'invite/delete' from './endpoints/invite/delete.js';
+export * as 'invite/limit' from './endpoints/invite/limit.js';
+export * as 'invite/list' from './endpoints/invite/list.js';
+export * as 'meta' from './endpoints/meta.js';
+export * as 'miauth/gen-token' from './endpoints/miauth/gen-token.js';
+export * as 'mute/create' from './endpoints/mute/create.js';
+export * as 'mute/delete' from './endpoints/mute/delete.js';
+export * as 'mute/list' from './endpoints/mute/list.js';
+export * as 'my/apps' from './endpoints/my/apps.js';
+export * as 'notes' from './endpoints/notes.js';
+export * as 'notes/children' from './endpoints/notes/children.js';
+export * as 'notes/clips' from './endpoints/notes/clips.js';
+export * as 'notes/conversation' from './endpoints/notes/conversation.js';
+export * as 'notes/create' from './endpoints/notes/create.js';
+export * as 'notes/delete' from './endpoints/notes/delete.js';
+export * as 'notes/favorites/create' from './endpoints/notes/favorites/create.js';
+export * as 'notes/favorites/delete' from './endpoints/notes/favorites/delete.js';
+export * as 'notes/featured' from './endpoints/notes/featured.js';
+export * as 'notes/global-timeline' from './endpoints/notes/global-timeline.js';
+export * as 'notes/hybrid-timeline' from './endpoints/notes/hybrid-timeline.js';
+export * as 'notes/local-timeline' from './endpoints/notes/local-timeline.js';
+export * as 'notes/mentions' from './endpoints/notes/mentions.js';
+export * as 'notes/polls/recommendation' from './endpoints/notes/polls/recommendation.js';
+export * as 'notes/polls/vote' from './endpoints/notes/polls/vote.js';
+export * as 'notes/reactions' from './endpoints/notes/reactions.js';
+export * as 'notes/reactions/create' from './endpoints/notes/reactions/create.js';
+export * as 'notes/reactions/delete' from './endpoints/notes/reactions/delete.js';
+export * as 'notes/renotes' from './endpoints/notes/renotes.js';
+export * as 'notes/replies' from './endpoints/notes/replies.js';
+export * as 'notes/search' from './endpoints/notes/search.js';
+export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.js';
+export * as 'notes/show' from './endpoints/notes/show.js';
+export * as 'notes/state' from './endpoints/notes/state.js';
+export * as 'notes/thread-muting/create' from './endpoints/notes/thread-muting/create.js';
+export * as 'notes/thread-muting/delete' from './endpoints/notes/thread-muting/delete.js';
+export * as 'notes/timeline' from './endpoints/notes/timeline.js';
+export * as 'notes/translate' from './endpoints/notes/translate.js';
+export * as 'notes/unrenote' from './endpoints/notes/unrenote.js';
+export * as 'notes/user-list-timeline' from './endpoints/notes/user-list-timeline.js';
+export * as 'notifications/create' from './endpoints/notifications/create.js';
+export * as 'notifications/flush' from './endpoints/notifications/flush.js';
+export * as 'notifications/mark-all-as-read' from './endpoints/notifications/mark-all-as-read.js';
+export * as 'notifications/test-notification' from './endpoints/notifications/test-notification.js';
+export * as 'page-push' from './endpoints/page-push.js';
+export * as 'pages/create' from './endpoints/pages/create.js';
+export * as 'pages/delete' from './endpoints/pages/delete.js';
+export * as 'pages/featured' from './endpoints/pages/featured.js';
+export * as 'pages/like' from './endpoints/pages/like.js';
+export * as 'pages/show' from './endpoints/pages/show.js';
+export * as 'pages/unlike' from './endpoints/pages/unlike.js';
+export * as 'pages/update' from './endpoints/pages/update.js';
+export * as 'ping' from './endpoints/ping.js';
+export * as 'pinned-users' from './endpoints/pinned-users.js';
+export * as 'promo/read' from './endpoints/promo/read.js';
+export * as 'renote-mute/create' from './endpoints/renote-mute/create.js';
+export * as 'renote-mute/delete' from './endpoints/renote-mute/delete.js';
+export * as 'renote-mute/list' from './endpoints/renote-mute/list.js';
+export * as 'request-reset-password' from './endpoints/request-reset-password.js';
+export * as 'reset-db' from './endpoints/reset-db.js';
+export * as 'reset-password' from './endpoints/reset-password.js';
+export * as 'retention' from './endpoints/retention.js';
+export * as 'reversi/cancel-match' from './endpoints/reversi/cancel-match.js';
+export * as 'reversi/games' from './endpoints/reversi/games.js';
+export * as 'reversi/invitations' from './endpoints/reversi/invitations.js';
+export * as 'reversi/match' from './endpoints/reversi/match.js';
+export * as 'reversi/show-game' from './endpoints/reversi/show-game.js';
+export * as 'reversi/surrender' from './endpoints/reversi/surrender.js';
+export * as 'reversi/verify' from './endpoints/reversi/verify.js';
+export * as 'roles/list' from './endpoints/roles/list.js';
+export * as 'roles/notes' from './endpoints/roles/notes.js';
+export * as 'roles/show' from './endpoints/roles/show.js';
+export * as 'roles/users' from './endpoints/roles/users.js';
+export * as 'server-info' from './endpoints/server-info.js';
+export * as 'stats' from './endpoints/stats.js';
+export * as 'sw/register' from './endpoints/sw/register.js';
+export * as 'sw/show-registration' from './endpoints/sw/show-registration.js';
+export * as 'sw/unregister' from './endpoints/sw/unregister.js';
+export * as 'sw/update-registration' from './endpoints/sw/update-registration.js';
+export * as 'test' from './endpoints/test.js';
+export * as 'username/available' from './endpoints/username/available.js';
+export * as 'users' from './endpoints/users.js';
+export * as 'users/achievements' from './endpoints/users/achievements.js';
+export * as 'users/clips' from './endpoints/users/clips.js';
+export * as 'users/featured-notes' from './endpoints/users/featured-notes.js';
+export * as 'users/flashs' from './endpoints/users/flashs.js';
+export * as 'users/followers' from './endpoints/users/followers.js';
+export * as 'users/following' from './endpoints/users/following.js';
+export * as 'users/gallery/posts' from './endpoints/users/gallery/posts.js';
+export * as 'users/get-frequently-replied-users' from './endpoints/users/get-frequently-replied-users.js';
+export * as 'users/lists/create' from './endpoints/users/lists/create.js';
+export * as 'users/lists/create-from-public' from './endpoints/users/lists/create-from-public.js';
+export * as 'users/lists/delete' from './endpoints/users/lists/delete.js';
+export * as 'users/lists/favorite' from './endpoints/users/lists/favorite.js';
+export * as 'users/lists/get-memberships' from './endpoints/users/lists/get-memberships.js';
+export * as 'users/lists/list' from './endpoints/users/lists/list.js';
+export * as 'users/lists/pull' from './endpoints/users/lists/pull.js';
+export * as 'users/lists/push' from './endpoints/users/lists/push.js';
+export * as 'users/lists/show' from './endpoints/users/lists/show.js';
+export * as 'users/lists/unfavorite' from './endpoints/users/lists/unfavorite.js';
+export * as 'users/lists/update' from './endpoints/users/lists/update.js';
+export * as 'users/lists/update-membership' from './endpoints/users/lists/update-membership.js';
+export * as 'users/notes' from './endpoints/users/notes.js';
+export * as 'users/pages' from './endpoints/users/pages.js';
+export * as 'users/reactions' from './endpoints/users/reactions.js';
+export * as 'users/recommendation' from './endpoints/users/recommendation.js';
+export * as 'users/relation' from './endpoints/users/relation.js';
+export * as 'users/report-abuse' from './endpoints/users/report-abuse.js';
+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 '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 b4f36234f0..fd6b9bb14b 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -7,821 +7,7 @@ import { permissions } from 'misskey-js';
import type { KeyOf, Schema } from '@/misc/json-schema.js';
import type { RateLimit } from '@/misc/rate-limit-utils.js';
-import * as ep___admin_abuseReport_notificationRecipient_list
- from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
-import * as ep___admin_abuseReport_notificationRecipient_show
- from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js';
-import * as ep___admin_abuseReport_notificationRecipient_create
- from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js';
-import * as ep___admin_abuseReport_notificationRecipient_update
- from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js';
-import * as ep___admin_abuseReport_notificationRecipient_delete
- from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js';
-import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
-import * as ep___admin_meta from './endpoints/admin/meta.js';
-import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
-import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
-import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
-import * as ep___admin_ad_create from './endpoints/admin/ad/create.js';
-import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js';
-import * as ep___admin_ad_list from './endpoints/admin/ad/list.js';
-import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
-import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
-import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
-import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
-import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
-import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
-import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
-import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
-import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
-import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
-import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
-import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
-import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
-import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
-import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
-import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
-import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
-import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js';
-import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js';
-import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js';
-import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js';
-import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js';
-import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js';
-import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
-import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
-import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
-import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
-import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
-import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
-import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
-import * as ep___admin_federation_refreshRemoteInstanceMetadata
- from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
-import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
-import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js';
-import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js';
-import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
-import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js';
-import * as ep___admin_invite_create from './endpoints/admin/invite/create.js';
-import * as ep___admin_invite_list from './endpoints/admin/invite/list.js';
-import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
-import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
-import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
-import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
-import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
-import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
-import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
-import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
-import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
-import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
-import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
-import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js';
-import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js';
-import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
-import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
-import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
-import * as ep___admin_showUser from './endpoints/admin/show-user.js';
-import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
-import * as ep___admin_nsfwUser from './endpoints/admin/nsfw-user.js';
-import * as ep___admin_unnsfwUser from './endpoints/admin/unnsfw-user.js';
-import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js';
-import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js';
-import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
-import * as ep___admin_approveUser from './endpoints/admin/approve-user.js';
-import * as ep___admin_declineUser from './endpoints/admin/decline-user.js';
-import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
-import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
-import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
-import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
-import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
-import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
-import * as ep___admin_roles_list from './endpoints/admin/roles/list.js';
-import * as ep___admin_roles_show from './endpoints/admin/roles/show.js';
-import * as ep___admin_roles_update from './endpoints/admin/roles/update.js';
-import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
-import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
-import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
-import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
-import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js';
-import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js';
-import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
-import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
-import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
-import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
-import * as ep___announcements from './endpoints/announcements.js';
-import * as ep___announcements_show from './endpoints/announcements/show.js';
-import * as ep___antennas_create from './endpoints/antennas/create.js';
-import * as ep___antennas_delete from './endpoints/antennas/delete.js';
-import * as ep___antennas_list from './endpoints/antennas/list.js';
-import * as ep___antennas_notes from './endpoints/antennas/notes.js';
-import * as ep___antennas_show from './endpoints/antennas/show.js';
-import * as ep___antennas_update from './endpoints/antennas/update.js';
-import * as ep___ap_get from './endpoints/ap/get.js';
-import * as ep___ap_show from './endpoints/ap/show.js';
-import * as ep___app_create from './endpoints/app/create.js';
-import * as ep___app_show from './endpoints/app/show.js';
-import * as ep___auth_accept from './endpoints/auth/accept.js';
-import * as ep___auth_session_generate from './endpoints/auth/session/generate.js';
-import * as ep___auth_session_show from './endpoints/auth/session/show.js';
-import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js';
-import * as ep___blocking_create from './endpoints/blocking/create.js';
-import * as ep___blocking_delete from './endpoints/blocking/delete.js';
-import * as ep___blocking_list from './endpoints/blocking/list.js';
-import * as ep___channels_create from './endpoints/channels/create.js';
-import * as ep___channels_featured from './endpoints/channels/featured.js';
-import * as ep___channels_follow from './endpoints/channels/follow.js';
-import * as ep___channels_followed from './endpoints/channels/followed.js';
-import * as ep___channels_owned from './endpoints/channels/owned.js';
-import * as ep___channels_show from './endpoints/channels/show.js';
-import * as ep___channels_timeline from './endpoints/channels/timeline.js';
-import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
-import * as ep___channels_update from './endpoints/channels/update.js';
-import * as ep___channels_favorite from './endpoints/channels/favorite.js';
-import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
-import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
-import * as ep___channels_search from './endpoints/channels/search.js';
-import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
-import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
-import * as ep___charts_drive from './endpoints/charts/drive.js';
-import * as ep___charts_federation from './endpoints/charts/federation.js';
-import * as ep___charts_instance from './endpoints/charts/instance.js';
-import * as ep___charts_notes from './endpoints/charts/notes.js';
-import * as ep___charts_user_drive from './endpoints/charts/user/drive.js';
-import * as ep___charts_user_following from './endpoints/charts/user/following.js';
-import * as ep___charts_user_notes from './endpoints/charts/user/notes.js';
-import * as ep___charts_user_pv from './endpoints/charts/user/pv.js';
-import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js';
-import * as ep___charts_users from './endpoints/charts/users.js';
-import * as ep___clips_addNote from './endpoints/clips/add-note.js';
-import * as ep___clips_removeNote from './endpoints/clips/remove-note.js';
-import * as ep___clips_create from './endpoints/clips/create.js';
-import * as ep___clips_delete from './endpoints/clips/delete.js';
-import * as ep___clips_list from './endpoints/clips/list.js';
-import * as ep___clips_notes from './endpoints/clips/notes.js';
-import * as ep___clips_show from './endpoints/clips/show.js';
-import * as ep___clips_update from './endpoints/clips/update.js';
-import * as ep___clips_favorite from './endpoints/clips/favorite.js';
-import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js';
-import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js';
-import * as ep___drive from './endpoints/drive.js';
-import * as ep___drive_files from './endpoints/drive/files.js';
-import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js';
-import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js';
-import * as ep___drive_files_create from './endpoints/drive/files/create.js';
-import * as ep___drive_files_delete from './endpoints/drive/files/delete.js';
-import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js';
-import * as ep___drive_files_find from './endpoints/drive/files/find.js';
-import * as ep___drive_files_show from './endpoints/drive/files/show.js';
-import * as ep___drive_files_update from './endpoints/drive/files/update.js';
-import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js';
-import * as ep___drive_folders from './endpoints/drive/folders.js';
-import * as ep___drive_folders_create from './endpoints/drive/folders/create.js';
-import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js';
-import * as ep___drive_folders_find from './endpoints/drive/folders/find.js';
-import * as ep___drive_folders_show from './endpoints/drive/folders/show.js';
-import * as ep___drive_folders_update from './endpoints/drive/folders/update.js';
-import * as ep___drive_stream from './endpoints/drive/stream.js';
-import * as ep___emailAddress_available from './endpoints/email-address/available.js';
-import * as ep___endpoint from './endpoints/endpoint.js';
-import * as ep___endpoints from './endpoints/endpoints.js';
-import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js';
-import * as ep___federation_followers from './endpoints/federation/followers.js';
-import * as ep___federation_following from './endpoints/federation/following.js';
-import * as ep___federation_instances from './endpoints/federation/instances.js';
-import * as ep___federation_showInstance from './endpoints/federation/show-instance.js';
-import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js';
-import * as ep___federation_users from './endpoints/federation/users.js';
-import * as ep___federation_stats from './endpoints/federation/stats.js';
-import * as ep___following_create from './endpoints/following/create.js';
-import * as ep___following_delete from './endpoints/following/delete.js';
-import * as ep___following_update from './endpoints/following/update.js';
-import * as ep___following_update_all from './endpoints/following/update-all.js';
-import * as ep___following_invalidate from './endpoints/following/invalidate.js';
-import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
-import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
-import * as ep___following_requests_list from './endpoints/following/requests/list.js';
-import * as ep___following_requests_sent from './endpoints/following/requests/sent.js';
-import * as ep___following_requests_reject from './endpoints/following/requests/reject.js';
-import * as ep___gallery_featured from './endpoints/gallery/featured.js';
-import * as ep___gallery_popular from './endpoints/gallery/popular.js';
-import * as ep___gallery_posts from './endpoints/gallery/posts.js';
-import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js';
-import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js';
-import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js';
-import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js';
-import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
-import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
-import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
-import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js';
-import * as ep___hashtags_list from './endpoints/hashtags/list.js';
-import * as ep___hashtags_search from './endpoints/hashtags/search.js';
-import * as ep___hashtags_show from './endpoints/hashtags/show.js';
-import * as ep___hashtags_trend from './endpoints/hashtags/trend.js';
-import * as ep___hashtags_users from './endpoints/hashtags/users.js';
-import * as ep___i from './endpoints/i.js';
-import * as ep___i_2fa_done from './endpoints/i/2fa/done.js';
-import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js';
-import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js';
-import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js';
-import * as ep___i_2fa_register from './endpoints/i/2fa/register.js';
-import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js';
-import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js';
-import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js';
-import * as ep___i_apps from './endpoints/i/apps.js';
-import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js';
-import * as ep___i_claimAchievement from './endpoints/i/claim-achievement.js';
-import * as ep___i_changePassword from './endpoints/i/change-password.js';
-import * as ep___i_deleteAccount from './endpoints/i/delete-account.js';
-import * as ep___i_exportData from './endpoints/i/export-data.js';
-import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
-import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
-import * as ep___i_exportMute from './endpoints/i/export-mute.js';
-import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
-import * as ep___i_exportClips from './endpoints/i/export-clips.js';
-import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
-import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
-import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
-import * as ep___i_favorites from './endpoints/i/favorites.js';
-import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
-import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
-import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
-import * as ep___i_importFollowing from './endpoints/i/import-following.js';
-import * as ep___i_importNotes from './endpoints/i/import-notes.js';
-import * as ep___i_importMuting from './endpoints/i/import-muting.js';
-import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
-import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
-import * as ep___i_notifications from './endpoints/i/notifications.js';
-import * as ep___i_notificationsGrouped from './endpoints/i/notifications-grouped.js';
-import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
-import * as ep___i_pages from './endpoints/i/pages.js';
-import * as ep___i_pin from './endpoints/i/pin.js';
-import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js';
-import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js';
-import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js';
-import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js';
-import * as ep___i_registry_getUnsecure from './endpoints/i/registry/get-unsecure.js';
-import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js';
-import * as ep___i_registry_get from './endpoints/i/registry/get.js';
-import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js';
-import * as ep___i_registry_keys from './endpoints/i/registry/keys.js';
-import * as ep___i_registry_remove from './endpoints/i/registry/remove.js';
-import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js';
-import * as ep___i_registry_set from './endpoints/i/registry/set.js';
-import * as ep___i_revokeToken from './endpoints/i/revoke-token.js';
-import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
-import * as ep___i_unpin from './endpoints/i/unpin.js';
-import * as ep___i_updateEmail from './endpoints/i/update-email.js';
-import * as ep___i_update from './endpoints/i/update.js';
-import * as ep___i_move from './endpoints/i/move.js';
-import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
-import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
-import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
-import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
-import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
-import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
-import * as ep___invite_create from './endpoints/invite/create.js';
-import * as ep___invite_delete from './endpoints/invite/delete.js';
-import * as ep___invite_list from './endpoints/invite/list.js';
-import * as ep___invite_limit from './endpoints/invite/limit.js';
-import * as ep___meta from './endpoints/meta.js';
-import * as ep___emojis from './endpoints/emojis.js';
-import * as ep___emoji from './endpoints/emoji.js';
-import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
-import * as ep___mute_create from './endpoints/mute/create.js';
-import * as ep___mute_delete from './endpoints/mute/delete.js';
-import * as ep___mute_list from './endpoints/mute/list.js';
-import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
-import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
-import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
-import * as ep___my_apps from './endpoints/my/apps.js';
-import * as ep___notes from './endpoints/notes.js';
-import * as ep___notes_children from './endpoints/notes/children.js';
-import * as ep___notes_clips from './endpoints/notes/clips.js';
-import * as ep___notes_conversation from './endpoints/notes/conversation.js';
-import * as ep___notes_create from './endpoints/notes/create.js';
-import * as ep___notes_delete from './endpoints/notes/delete.js';
-import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
-import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
-import * as ep___notes_featured from './endpoints/notes/featured.js';
-import * as ep___notes_following from './endpoints/notes/following.js';
-import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
-import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
-import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
-import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
-import * as ep___notes_mentions from './endpoints/notes/mentions.js';
-import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
-import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
-import * as ep___notes_polls_refresh from './endpoints/notes/polls/refresh.js';
-import * as ep___notes_reactions from './endpoints/notes/reactions.js';
-import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
-import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
-import * as ep___notes_like from './endpoints/notes/like.js';
-import * as ep___notes_renotes from './endpoints/notes/renotes.js';
-import * as ep___notes_replies from './endpoints/notes/replies.js';
-import * as ep___notes_schedule_create from './endpoints/notes/schedule/create.js';
-import * as ep___notes_schedule_delete from './endpoints/notes/schedule/delete.js';
-import * as ep___notes_schedule_list from './endpoints/notes/schedule/list.js';
-import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js';
-import * as ep___notes_search from './endpoints/notes/search.js';
-import * as ep___notes_show from './endpoints/notes/show.js';
-import * as ep___notes_state from './endpoints/notes/state.js';
-import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js';
-import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js';
-import * as ep___notes_timeline from './endpoints/notes/timeline.js';
-import * as ep___notes_translate from './endpoints/notes/translate.js';
-import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
-import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
-import * as ep___notes_edit from './endpoints/notes/edit.js';
-import * as ep___notes_versions from './endpoints/notes/versions.js';
-import * as ep___notifications_create from './endpoints/notifications/create.js';
-import * as ep___notifications_flush from './endpoints/notifications/flush.js';
-import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
-import * as ep___pagePush from './endpoints/page-push.js';
-import * as ep___pages_create from './endpoints/pages/create.js';
-import * as ep___pages_delete from './endpoints/pages/delete.js';
-import * as ep___pages_featured from './endpoints/pages/featured.js';
-import * as ep___pages_like from './endpoints/pages/like.js';
-import * as ep___pages_show from './endpoints/pages/show.js';
-import * as ep___pages_unlike from './endpoints/pages/unlike.js';
-import * as ep___pages_update from './endpoints/pages/update.js';
-import * as ep___flash_create from './endpoints/flash/create.js';
-import * as ep___flash_delete from './endpoints/flash/delete.js';
-import * as ep___flash_featured from './endpoints/flash/featured.js';
-import * as ep___flash_like from './endpoints/flash/like.js';
-import * as ep___flash_show from './endpoints/flash/show.js';
-import * as ep___flash_unlike from './endpoints/flash/unlike.js';
-import * as ep___flash_update from './endpoints/flash/update.js';
-import * as ep___flash_my from './endpoints/flash/my.js';
-import * as ep___flash_myLikes from './endpoints/flash/my-likes.js';
-import * as ep___ping from './endpoints/ping.js';
-import * as ep___pinnedUsers from './endpoints/pinned-users.js';
-import * as ep___promo_read from './endpoints/promo/read.js';
-import * as ep___roles_list from './endpoints/roles/list.js';
-import * as ep___roles_show from './endpoints/roles/show.js';
-import * as ep___roles_users from './endpoints/roles/users.js';
-import * as ep___roles_notes from './endpoints/roles/notes.js';
-import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
-import * as ep___resetDb from './endpoints/reset-db.js';
-import * as ep___resetPassword from './endpoints/reset-password.js';
-import * as ep___serverInfo from './endpoints/server-info.js';
-import * as ep___stats from './endpoints/stats.js';
-import * as ep___sw_show_registration from './endpoints/sw/show-registration.js';
-import * as ep___sw_update_registration from './endpoints/sw/update-registration.js';
-import * as ep___sw_register from './endpoints/sw/register.js';
-import * as ep___sw_unregister from './endpoints/sw/unregister.js';
-import * as ep___test from './endpoints/test.js';
-import * as ep___username_available from './endpoints/username/available.js';
-import * as ep___users from './endpoints/users.js';
-import * as ep___users_clips from './endpoints/users/clips.js';
-import * as ep___users_followers from './endpoints/users/followers.js';
-import * as ep___users_following from './endpoints/users/following.js';
-import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
-import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
-import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
-import * as ep___users_lists_create from './endpoints/users/lists/create.js';
-import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
-import * as ep___users_lists_list from './endpoints/users/lists/list.js';
-import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
-import * as ep___users_lists_push from './endpoints/users/lists/push.js';
-import * as ep___users_lists_show from './endpoints/users/lists/show.js';
-import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
-import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
-import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
-import * as ep___users_lists_update from './endpoints/users/lists/update.js';
-import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
-import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
-import * as ep___users_notes from './endpoints/users/notes.js';
-import * as ep___users_pages from './endpoints/users/pages.js';
-import * as ep___users_flashs from './endpoints/users/flashs.js';
-import * as ep___users_reactions from './endpoints/users/reactions.js';
-import * as ep___users_recommendation from './endpoints/users/recommendation.js';
-import * as ep___users_relation from './endpoints/users/relation.js';
-import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js';
-import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js';
-import * as ep___users_search from './endpoints/users/search.js';
-import * as ep___users_show from './endpoints/users/show.js';
-import * as ep___users_achievements from './endpoints/users/achievements.js';
-import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
-import * as ep___fetchRss from './endpoints/fetch-rss.js';
-import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
-import * as ep___retention from './endpoints/retention.js';
-import * as ep___sponsors from './endpoints/sponsors.js';
-import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
-import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
-import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
-import * as ep___reversi_games from './endpoints/reversi/games.js';
-import * as ep___reversi_match from './endpoints/reversi/match.js';
-import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
-import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
-import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
-import * as ep___reversi_verify from './endpoints/reversi/verify.js';
-
-const eps = [
- ['admin/meta', ep___admin_meta],
- ['admin/abuse-user-reports', ep___admin_abuseUserReports],
- ['admin/abuse-report/notification-recipient/list', ep___admin_abuseReport_notificationRecipient_list],
- ['admin/abuse-report/notification-recipient/show', ep___admin_abuseReport_notificationRecipient_show],
- ['admin/abuse-report/notification-recipient/create', ep___admin_abuseReport_notificationRecipient_create],
- ['admin/abuse-report/notification-recipient/update', ep___admin_abuseReport_notificationRecipient_update],
- ['admin/abuse-report/notification-recipient/delete', ep___admin_abuseReport_notificationRecipient_delete],
- ['admin/accounts/create', ep___admin_accounts_create],
- ['admin/accounts/delete', ep___admin_accounts_delete],
- ['admin/accounts/find-by-email', ep___admin_accounts_findByEmail],
- ['admin/ad/create', ep___admin_ad_create],
- ['admin/ad/delete', ep___admin_ad_delete],
- ['admin/ad/list', ep___admin_ad_list],
- ['admin/ad/update', ep___admin_ad_update],
- ['admin/announcements/create', ep___admin_announcements_create],
- ['admin/announcements/delete', ep___admin_announcements_delete],
- ['admin/announcements/list', ep___admin_announcements_list],
- ['admin/announcements/update', ep___admin_announcements_update],
- ['admin/avatar-decorations/create', ep___admin_avatarDecorations_create],
- ['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
- ['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
- ['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
- ['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
- ['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
- ['admin/unset-user-banner', ep___admin_unsetUserBanner],
- ['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
- ['admin/drive/cleanup', ep___admin_drive_cleanup],
- ['admin/drive/files', ep___admin_drive_files],
- ['admin/drive/show-file', ep___admin_drive_showFile],
- ['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
- ['admin/emoji/add', ep___admin_emoji_add],
- ['admin/emoji/copy', ep___admin_emoji_copy],
- ['admin/emoji/delete-bulk', ep___admin_emoji_deleteBulk],
- ['admin/emoji/delete', ep___admin_emoji_delete],
- ['admin/emoji/import-zip', ep___admin_emoji_importZip],
- ['admin/emoji/list-remote', ep___admin_emoji_listRemote],
- ['admin/emoji/list', ep___admin_emoji_list],
- ['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk],
- ['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk],
- ['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
- ['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
- ['admin/emoji/update', ep___admin_emoji_update],
- ['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
- ['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
- ['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
- ['admin/federation/update-instance', ep___admin_federation_updateInstance],
- ['admin/get-index-stats', ep___admin_getIndexStats],
- ['admin/get-table-stats', ep___admin_getTableStats],
- ['admin/get-user-ips', ep___admin_getUserIps],
- ['admin/invite/create', ep___admin_invite_create],
- ['admin/invite/list', ep___admin_invite_list],
- ['admin/promo/create', ep___admin_promo_create],
- ['admin/queue/clear', ep___admin_queue_clear],
- ['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
- ['admin/queue/inbox-delayed', ep___admin_queue_inboxDelayed],
- ['admin/queue/promote', ep___admin_queue_promote],
- ['admin/queue/stats', ep___admin_queue_stats],
- ['admin/relays/add', ep___admin_relays_add],
- ['admin/relays/list', ep___admin_relays_list],
- ['admin/relays/remove', ep___admin_relays_remove],
- ['admin/reset-password', ep___admin_resetPassword],
- ['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport],
- ['admin/forward-abuse-user-report', ep___admin_forwardAbuseUserReport],
- ['admin/update-abuse-user-report', ep___admin_updateAbuseUserReport],
- ['admin/send-email', ep___admin_sendEmail],
- ['admin/server-info', ep___admin_serverInfo],
- ['admin/show-moderation-logs', ep___admin_showModerationLogs],
- ['admin/show-user', ep___admin_showUser],
- ['admin/show-users', ep___admin_showUsers],
- ['admin/nsfw-user', ep___admin_nsfwUser],
- ['admin/unnsfw-user', ep___admin_unnsfwUser],
- ['admin/silence-user', ep___admin_silenceUser],
- ['admin/unsilence-user', ep___admin_unsilenceUser],
- ['admin/suspend-user', ep___admin_suspendUser],
- ['admin/approve-user', ep___admin_approveUser],
- ['admin/decline-user', ep___admin_declineUser],
- ['admin/unsuspend-user', ep___admin_unsuspendUser],
- ['admin/update-meta', ep___admin_updateMeta],
- ['admin/delete-account', ep___admin_deleteAccount],
- ['admin/update-user-note', ep___admin_updateUserNote],
- ['admin/roles/create', ep___admin_roles_create],
- ['admin/roles/delete', ep___admin_roles_delete],
- ['admin/roles/list', ep___admin_roles_list],
- ['admin/roles/show', ep___admin_roles_show],
- ['admin/roles/update', ep___admin_roles_update],
- ['admin/roles/assign', ep___admin_roles_assign],
- ['admin/roles/unassign', ep___admin_roles_unassign],
- ['admin/roles/update-default-policies', ep___admin_roles_updateDefaultPolicies],
- ['admin/roles/users', ep___admin_roles_users],
- ['admin/system-webhook/create', ep___admin_systemWebhook_create],
- ['admin/system-webhook/delete', ep___admin_systemWebhook_delete],
- ['admin/system-webhook/list', ep___admin_systemWebhook_list],
- ['admin/system-webhook/show', ep___admin_systemWebhook_show],
- ['admin/system-webhook/update', ep___admin_systemWebhook_update],
- ['admin/system-webhook/test', ep___admin_systemWebhook_test],
- ['announcements', ep___announcements],
- ['announcements/show', ep___announcements_show],
- ['antennas/create', ep___antennas_create],
- ['antennas/delete', ep___antennas_delete],
- ['antennas/list', ep___antennas_list],
- ['antennas/notes', ep___antennas_notes],
- ['antennas/show', ep___antennas_show],
- ['antennas/update', ep___antennas_update],
- ['ap/get', ep___ap_get],
- ['ap/show', ep___ap_show],
- ['app/create', ep___app_create],
- ['app/show', ep___app_show],
- ['auth/accept', ep___auth_accept],
- ['auth/session/generate', ep___auth_session_generate],
- ['auth/session/show', ep___auth_session_show],
- ['auth/session/userkey', ep___auth_session_userkey],
- ['blocking/create', ep___blocking_create],
- ['blocking/delete', ep___blocking_delete],
- ['blocking/list', ep___blocking_list],
- ['channels/create', ep___channels_create],
- ['channels/featured', ep___channels_featured],
- ['channels/follow', ep___channels_follow],
- ['channels/followed', ep___channels_followed],
- ['channels/owned', ep___channels_owned],
- ['channels/show', ep___channels_show],
- ['channels/timeline', ep___channels_timeline],
- ['channels/unfollow', ep___channels_unfollow],
- ['channels/update', ep___channels_update],
- ['channels/favorite', ep___channels_favorite],
- ['channels/unfavorite', ep___channels_unfavorite],
- ['channels/my-favorites', ep___channels_myFavorites],
- ['channels/search', ep___channels_search],
- ['charts/active-users', ep___charts_activeUsers],
- ['charts/ap-request', ep___charts_apRequest],
- ['charts/drive', ep___charts_drive],
- ['charts/federation', ep___charts_federation],
- ['charts/instance', ep___charts_instance],
- ['charts/notes', ep___charts_notes],
- ['charts/user/drive', ep___charts_user_drive],
- ['charts/user/following', ep___charts_user_following],
- ['charts/user/notes', ep___charts_user_notes],
- ['charts/user/pv', ep___charts_user_pv],
- ['charts/user/reactions', ep___charts_user_reactions],
- ['charts/users', ep___charts_users],
- ['clips/add-note', ep___clips_addNote],
- ['clips/remove-note', ep___clips_removeNote],
- ['clips/create', ep___clips_create],
- ['clips/delete', ep___clips_delete],
- ['clips/list', ep___clips_list],
- ['clips/notes', ep___clips_notes],
- ['clips/show', ep___clips_show],
- ['clips/update', ep___clips_update],
- ['clips/favorite', ep___clips_favorite],
- ['clips/unfavorite', ep___clips_unfavorite],
- ['clips/my-favorites', ep___clips_myFavorites],
- ['drive', ep___drive],
- ['drive/files', ep___drive_files],
- ['drive/files/attached-notes', ep___drive_files_attachedNotes],
- ['drive/files/check-existence', ep___drive_files_checkExistence],
- ['drive/files/create', ep___drive_files_create],
- ['drive/files/delete', ep___drive_files_delete],
- ['drive/files/find-by-hash', ep___drive_files_findByHash],
- ['drive/files/find', ep___drive_files_find],
- ['drive/files/show', ep___drive_files_show],
- ['drive/files/update', ep___drive_files_update],
- ['drive/files/upload-from-url', ep___drive_files_uploadFromUrl],
- ['drive/folders', ep___drive_folders],
- ['drive/folders/create', ep___drive_folders_create],
- ['drive/folders/delete', ep___drive_folders_delete],
- ['drive/folders/find', ep___drive_folders_find],
- ['drive/folders/show', ep___drive_folders_show],
- ['drive/folders/update', ep___drive_folders_update],
- ['drive/stream', ep___drive_stream],
- ['email-address/available', ep___emailAddress_available],
- ['endpoint', ep___endpoint],
- ['endpoints', ep___endpoints],
- ['export-custom-emojis', ep___exportCustomEmojis],
- ['federation/followers', ep___federation_followers],
- ['federation/following', ep___federation_following],
- ['federation/instances', ep___federation_instances],
- ['federation/show-instance', ep___federation_showInstance],
- ['federation/update-remote-user', ep___federation_updateRemoteUser],
- ['federation/users', ep___federation_users],
- ['federation/stats', ep___federation_stats],
- ['following/create', ep___following_create],
- ['following/delete', ep___following_delete],
- ['following/update', ep___following_update],
- ['following/update-all', ep___following_update_all],
- ['following/invalidate', ep___following_invalidate],
- ['following/requests/accept', ep___following_requests_accept],
- ['following/requests/cancel', ep___following_requests_cancel],
- ['following/requests/list', ep___following_requests_list],
- ['following/requests/sent', ep___following_requests_sent],
- ['following/requests/reject', ep___following_requests_reject],
- ['gallery/featured', ep___gallery_featured],
- ['gallery/popular', ep___gallery_popular],
- ['gallery/posts', ep___gallery_posts],
- ['gallery/posts/create', ep___gallery_posts_create],
- ['gallery/posts/delete', ep___gallery_posts_delete],
- ['gallery/posts/like', ep___gallery_posts_like],
- ['gallery/posts/show', ep___gallery_posts_show],
- ['gallery/posts/unlike', ep___gallery_posts_unlike],
- ['gallery/posts/update', ep___gallery_posts_update],
- ['get-online-users-count', ep___getOnlineUsersCount],
- ['get-avatar-decorations', ep___getAvatarDecorations],
- ['hashtags/list', ep___hashtags_list],
- ['hashtags/search', ep___hashtags_search],
- ['hashtags/show', ep___hashtags_show],
- ['hashtags/trend', ep___hashtags_trend],
- ['hashtags/users', ep___hashtags_users],
- ['i', ep___i],
- ['i/2fa/done', ep___i_2fa_done],
- ['i/2fa/key-done', ep___i_2fa_keyDone],
- ['i/2fa/password-less', ep___i_2fa_passwordLess],
- ['i/2fa/register-key', ep___i_2fa_registerKey],
- ['i/2fa/register', ep___i_2fa_register],
- ['i/2fa/update-key', ep___i_2fa_updateKey],
- ['i/2fa/remove-key', ep___i_2fa_removeKey],
- ['i/2fa/unregister', ep___i_2fa_unregister],
- ['i/apps', ep___i_apps],
- ['i/authorized-apps', ep___i_authorizedApps],
- ['i/claim-achievement', ep___i_claimAchievement],
- ['i/change-password', ep___i_changePassword],
- ['i/delete-account', ep___i_deleteAccount],
- ['i/export-data', ep___i_exportData],
- ['i/export-blocking', ep___i_exportBlocking],
- ['i/export-following', ep___i_exportFollowing],
- ['i/export-mute', ep___i_exportMute],
- ['i/export-notes', ep___i_exportNotes],
- ['i/export-clips', ep___i_exportClips],
- ['i/export-favorites', ep___i_exportFavorites],
- ['i/export-user-lists', ep___i_exportUserLists],
- ['i/export-antennas', ep___i_exportAntennas],
- ['i/favorites', ep___i_favorites],
- ['i/gallery/likes', ep___i_gallery_likes],
- ['i/gallery/posts', ep___i_gallery_posts],
- ['i/import-blocking', ep___i_importBlocking],
- ['i/import-following', ep___i_importFollowing],
- ['i/import-notes', ep___i_importNotes],
- ['i/import-muting', ep___i_importMuting],
- ['i/import-user-lists', ep___i_importUserLists],
- ['i/import-antennas', ep___i_importAntennas],
- ['i/notifications', ep___i_notifications],
- ['i/notifications-grouped', ep___i_notificationsGrouped],
- ['i/page-likes', ep___i_pageLikes],
- ['i/pages', ep___i_pages],
- ['i/pin', ep___i_pin],
- ['i/read-all-unread-notes', ep___i_readAllUnreadNotes],
- ['i/read-announcement', ep___i_readAnnouncement],
- ['i/regenerate-token', ep___i_regenerateToken],
- ['i/registry/get-all', ep___i_registry_getAll],
- ['i/registry/get-unsecure', ep___i_registry_getUnsecure],
- ['i/registry/get-detail', ep___i_registry_getDetail],
- ['i/registry/get', ep___i_registry_get],
- ['i/registry/keys-with-type', ep___i_registry_keysWithType],
- ['i/registry/keys', ep___i_registry_keys],
- ['i/registry/remove', ep___i_registry_remove],
- ['i/registry/scopes-with-domain', ep___i_registry_scopesWithDomain],
- ['i/registry/set', ep___i_registry_set],
- ['i/revoke-token', ep___i_revokeToken],
- ['i/signin-history', ep___i_signinHistory],
- ['i/unpin', ep___i_unpin],
- ['i/update-email', ep___i_updateEmail],
- ['i/update', ep___i_update],
- ['i/move', ep___i_move],
- ['i/webhooks/create', ep___i_webhooks_create],
- ['i/webhooks/list', ep___i_webhooks_list],
- ['i/webhooks/show', ep___i_webhooks_show],
- ['i/webhooks/update', ep___i_webhooks_update],
- ['i/webhooks/delete', ep___i_webhooks_delete],
- ['i/webhooks/test', ep___i_webhooks_test],
- ['invite/create', ep___invite_create],
- ['invite/delete', ep___invite_delete],
- ['invite/list', ep___invite_list],
- ['invite/limit', ep___invite_limit],
- ['meta', ep___meta],
- ['emojis', ep___emojis],
- ['emoji', ep___emoji],
- ['miauth/gen-token', ep___miauth_genToken],
- ['mute/create', ep___mute_create],
- ['mute/delete', ep___mute_delete],
- ['mute/list', ep___mute_list],
- ['renote-mute/create', ep___renoteMute_create],
- ['renote-mute/delete', ep___renoteMute_delete],
- ['renote-mute/list', ep___renoteMute_list],
- ['my/apps', ep___my_apps],
- ['notes', ep___notes],
- ['notes/children', ep___notes_children],
- ['notes/clips', ep___notes_clips],
- ['notes/conversation', ep___notes_conversation],
- ['notes/create', ep___notes_create],
- ['notes/delete', ep___notes_delete],
- ['notes/favorites/create', ep___notes_favorites_create],
- ['notes/favorites/delete', ep___notes_favorites_delete],
- ['notes/featured', ep___notes_featured],
- ['notes/following', ep___notes_following],
- ['notes/global-timeline', ep___notes_globalTimeline],
- ['notes/bubble-timeline', ep___notes_bubbleTimeline],
- ['notes/hybrid-timeline', ep___notes_hybridTimeline],
- ['notes/local-timeline', ep___notes_localTimeline],
- ['notes/mentions', ep___notes_mentions],
- ['notes/polls/recommendation', ep___notes_polls_recommendation],
- ['notes/polls/vote', ep___notes_polls_vote],
- ['notes/polls/refresh', ep___notes_polls_refresh],
- ['notes/reactions', ep___notes_reactions],
- ['notes/reactions/create', ep___notes_reactions_create],
- ['notes/reactions/delete', ep___notes_reactions_delete],
- ['notes/like', ep___notes_like],
- ['notes/renotes', ep___notes_renotes],
- ['notes/replies', ep___notes_replies],
- ['notes/schedule/create', ep___notes_schedule_create],
- ['notes/schedule/delete', ep___notes_schedule_delete],
- ['notes/schedule/list', ep___notes_schedule_list],
- ['notes/search-by-tag', ep___notes_searchByTag],
- ['notes/search', ep___notes_search],
- ['notes/show', ep___notes_show],
- ['notes/state', ep___notes_state],
- ['notes/thread-muting/create', ep___notes_threadMuting_create],
- ['notes/thread-muting/delete', ep___notes_threadMuting_delete],
- ['notes/timeline', ep___notes_timeline],
- ['notes/translate', ep___notes_translate],
- ['notes/unrenote', ep___notes_unrenote],
- ['notes/user-list-timeline', ep___notes_userListTimeline],
- ['notes/edit', ep___notes_edit],
- ['notes/versions', ep___notes_versions],
- ['notifications/create', ep___notifications_create],
- ['notifications/flush', ep___notifications_flush],
- ['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
- ['notifications/test-notification', ep___notifications_testNotification],
- ['page-push', ep___pagePush],
- ['pages/create', ep___pages_create],
- ['pages/delete', ep___pages_delete],
- ['pages/featured', ep___pages_featured],
- ['pages/like', ep___pages_like],
- ['pages/show', ep___pages_show],
- ['pages/unlike', ep___pages_unlike],
- ['pages/update', ep___pages_update],
- ['flash/create', ep___flash_create],
- ['flash/delete', ep___flash_delete],
- ['flash/featured', ep___flash_featured],
- ['flash/like', ep___flash_like],
- ['flash/show', ep___flash_show],
- ['flash/unlike', ep___flash_unlike],
- ['flash/update', ep___flash_update],
- ['flash/my', ep___flash_my],
- ['flash/my-likes', ep___flash_myLikes],
- ['ping', ep___ping],
- ['pinned-users', ep___pinnedUsers],
- ['promo/read', ep___promo_read],
- ['roles/list', ep___roles_list],
- ['roles/show', ep___roles_show],
- ['roles/users', ep___roles_users],
- ['roles/notes', ep___roles_notes],
- ['request-reset-password', ep___requestResetPassword],
- ['reset-db', ep___resetDb],
- ['reset-password', ep___resetPassword],
- ['server-info', ep___serverInfo],
- ['stats', ep___stats],
- ['sw/show-registration', ep___sw_show_registration],
- ['sw/update-registration', ep___sw_update_registration],
- ['sw/register', ep___sw_register],
- ['sw/unregister', ep___sw_unregister],
- ['test', ep___test],
- ['username/available', ep___username_available],
- ['users', ep___users],
- ['users/clips', ep___users_clips],
- ['users/followers', ep___users_followers],
- ['users/following', ep___users_following],
- ['users/gallery/posts', ep___users_gallery_posts],
- ['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
- ['users/featured-notes', ep___users_featuredNotes],
- ['users/lists/create', ep___users_lists_create],
- ['users/lists/delete', ep___users_lists_delete],
- ['users/lists/list', ep___users_lists_list],
- ['users/lists/pull', ep___users_lists_pull],
- ['users/lists/push', ep___users_lists_push],
- ['users/lists/show', ep___users_lists_show],
- ['users/lists/favorite', ep___users_lists_favorite],
- ['users/lists/unfavorite', ep___users_lists_unfavorite],
- ['users/lists/update', ep___users_lists_update],
- ['users/lists/create-from-public', ep___users_lists_createFromPublic],
- ['users/lists/update-membership', ep___users_lists_updateMembership],
- ['users/lists/get-memberships', ep___users_lists_getMemberships],
- ['users/notes', ep___users_notes],
- ['users/pages', ep___users_pages],
- ['users/flashs', ep___users_flashs],
- ['users/reactions', ep___users_reactions],
- ['users/recommendation', ep___users_recommendation],
- ['users/relation', ep___users_relation],
- ['users/report-abuse', ep___users_reportAbuse],
- ['users/search-by-username-and-host', ep___users_searchByUsernameAndHost],
- ['users/search', ep___users_search],
- ['users/show', ep___users_show],
- ['users/achievements', ep___users_achievements],
- ['users/update-memo', ep___users_updateMemo],
- ['fetch-rss', ep___fetchRss],
- ['fetch-external-resources', ep___fetchExternalResources],
- ['retention', ep___retention],
- ['sponsors', ep___sponsors],
- ['bubble-game/register', ep___bubbleGame_register],
- ['bubble-game/ranking', ep___bubbleGame_ranking],
- ['reversi/cancel-match', ep___reversi_cancelMatch],
- ['reversi/games', ep___reversi_games],
- ['reversi/match', ep___reversi_match],
- ['reversi/invitations', ep___reversi_invitations],
- ['reversi/show-game', ep___reversi_showGame],
- ['reversi/surrender', ep___reversi_surrender],
- ['reversi/verify', ep___reversi_verify],
-];
+import * as endpointsObject from './endpoint-list.js';
interface IEndpointMetaBase {
readonly stability?: 'deprecated' | 'experimental' | 'stable';
@@ -922,7 +108,7 @@ export interface IEndpoint {
params: Schema;
}
-const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => {
+const endpoints: IEndpoint[] = Object.entries(endpointsObject).map(([name, ep]) => {
return {
name: name,
get meta() {
diff --git a/packages/backend/src/server/api/endpoints/admin/captcha/current.ts b/packages/backend/src/server/api/endpoints/admin/captcha/current.ts
new file mode 100644
index 0000000000..63ec740348
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/captcha/current.ts
@@ -0,0 +1,70 @@
+/*
+ * 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 { CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js';
+
+export const meta = {
+ tags: ['admin', 'captcha'],
+
+ requireCredential: true,
+ requireAdmin: true,
+
+ // 実態ã¯metaã®å–å¾—ã§ã‚ã‚‹ãŸã‚
+ kind: 'read:admin:meta',
+
+ res: {
+ type: 'object',
+ properties: {
+ provider: {
+ type: 'string',
+ enum: supportedCaptchaProviders,
+ },
+ hcaptcha: {
+ type: 'object',
+ properties: {
+ siteKey: { type: 'string', nullable: true },
+ secretKey: { type: 'string', nullable: true },
+ },
+ },
+ mcaptcha: {
+ type: 'object',
+ properties: {
+ siteKey: { type: 'string', nullable: true },
+ secretKey: { type: 'string', nullable: true },
+ instanceUrl: { type: 'string', nullable: true },
+ },
+ },
+ recaptcha: {
+ type: 'object',
+ properties: {
+ siteKey: { type: 'string', nullable: true },
+ secretKey: { type: 'string', nullable: true },
+ },
+ },
+ turnstile: {
+ type: 'object',
+ properties: {
+ siteKey: { type: 'string', nullable: true },
+ secretKey: { type: 'string', nullable: true },
+ },
+ },
+ },
+ },
+} as const;
+
+export const paramDef = {} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private captchaService: CaptchaService,
+ ) {
+ super(meta, paramDef, async () => {
+ return this.captchaService.get();
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/captcha/save.ts b/packages/backend/src/server/api/endpoints/admin/captcha/save.ts
new file mode 100644
index 0000000000..98ec278ebe
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/captcha/save.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 { captchaErrorCodes, CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['admin', 'captcha'],
+
+ requireCredential: true,
+ requireAdmin: true,
+
+ // 実態ã¯metaã®æ›´æ–°ã§ã‚ã‚‹ãŸã‚
+ kind: 'write:admin:meta',
+
+ errors: {
+ invalidProvider: {
+ message: 'Invalid provider.',
+ code: 'INVALID_PROVIDER',
+ id: '14bf7ae1-80cc-4363-acb2-4fd61d086af0',
+ httpStatusCode: 400,
+ },
+ invalidParameters: {
+ message: 'Invalid parameters.',
+ code: 'INVALID_PARAMETERS',
+ id: '26654194-410e-44e2-b42e-460ff6f92476',
+ httpStatusCode: 400,
+ },
+ noResponseProvided: {
+ message: 'No response provided.',
+ code: 'NO_RESPONSE_PROVIDED',
+ id: '40acbba8-0937-41fb-bb3f-474514d40afe',
+ httpStatusCode: 400,
+ },
+ requestFailed: {
+ message: 'Request failed.',
+ code: 'REQUEST_FAILED',
+ id: '0f4fe2f1-2c15-4d6e-b714-efbfcde231cd',
+ httpStatusCode: 500,
+ },
+ verificationFailed: {
+ message: 'Verification failed.',
+ code: 'VERIFICATION_FAILED',
+ id: 'c41c067f-24f3-4150-84b2-b5a3ae8c2214',
+ httpStatusCode: 400,
+ },
+ unknown: {
+ message: 'unknown',
+ code: 'UNKNOWN',
+ id: 'f868d509-e257-42a9-99c1-42614b031a97',
+ httpStatusCode: 500,
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ provider: {
+ type: 'string',
+ enum: supportedCaptchaProviders,
+ },
+ captchaResult: {
+ type: 'string', nullable: true,
+ },
+ sitekey: {
+ type: 'string', nullable: true,
+ },
+ secret: {
+ type: 'string', nullable: true,
+ },
+ instanceUrl: {
+ type: 'string', nullable: true,
+ },
+ },
+ required: ['provider'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private captchaService: CaptchaService,
+ ) {
+ super(meta, paramDef, async (ps) => {
+ const result = await this.captchaService.save(ps.provider, {
+ sitekey: ps.sitekey,
+ secret: ps.secret,
+ instanceUrl: ps.instanceUrl,
+ captchaResult: ps.captchaResult,
+ });
+
+ if (!result.success) {
+ switch (result.error.code) {
+ case captchaErrorCodes.invalidProvider:
+ throw new ApiError({
+ ...meta.errors.invalidProvider,
+ message: result.error.message,
+ });
+ case captchaErrorCodes.invalidParameters:
+ throw new ApiError({
+ ...meta.errors.invalidParameters,
+ message: result.error.message,
+ });
+ case captchaErrorCodes.noResponseProvided:
+ throw new ApiError({
+ ...meta.errors.noResponseProvided,
+ message: result.error.message,
+ });
+ case captchaErrorCodes.requestFailed:
+ throw new ApiError({
+ ...meta.errors.requestFailed,
+ message: result.error.message,
+ });
+ case captchaErrorCodes.verificationFailed:
+ throw new ApiError({
+ ...meta.errors.verificationFailed,
+ message: result.error.message,
+ });
+ default:
+ throw new ApiError(meta.errors.unknown);
+ }
+ }
+ });
+ }
+}
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 b45a3c7156..1c5316a002 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -9,6 +9,7 @@ import type { DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+import { FILE_TYPE_IMAGE } from '@/const.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -24,6 +25,11 @@ export const meta = {
code: 'NO_SUCH_FILE',
id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
},
+ unsupportedFileType: {
+ message: 'Unsupported file type.',
+ code: 'UNSUPPORTED_FILE_TYPE',
+ id: 'f7599d96-8750-af68-1633-9575d625c1a7',
+ },
duplicateName: {
message: 'Duplicate name.',
code: 'DUPLICATE_NAME',
@@ -47,15 +53,21 @@ export const paramDef = {
nullable: true,
description: 'Use `null` to reset the category.',
},
- aliases: { type: 'array', items: {
- type: 'string',
- } },
+ aliases: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
license: { type: 'string', nullable: true },
isSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' },
- roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
- type: 'string',
- } },
+ roleIdsThatCanBeUsedThisEmojiAsReaction: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
},
required: ['name', 'fileId'],
} as const;
@@ -67,9 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
-
private customEmojiService: CustomEmojiService,
-
private emojiEntityService: EmojiEntityService,
) {
super(meta, paramDef, async (ps, me) => {
@@ -78,11 +88,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
const isDuplicate = await this.customEmojiService.checkDuplicate(nameNfc);
if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
+ if (!FILE_TYPE_IMAGE.includes(driveFile.type)) throw new ApiError(meta.errors.unsupportedFileType);
if (driveFile.user !== null) await this.driveFilesRepository.update(driveFile.id, { user: null });
const emoji = await this.customEmojiService.add({
- driveFile,
+ originalUrl: driveFile.url,
+ publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+ fileType: driveFile.webpublicType ?? driveFile.type,
name: nameNfc,
category: ps.category?.normalize('NFC') ?? null,
aliases: ps.aliases?.map(a => a.normalize('NFC')) ?? [],
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 acd2494131..07ffa0b1c7 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -4,7 +4,6 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import { IsNull } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { EmojisRepository } from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
@@ -88,10 +87,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
const addedEmoji = await this.customEmojiService.add({
- driveFile,
+ originalUrl: driveFile.url,
+ publicUrl: driveFile.webpublicUrl ?? driveFile.url,
+ fileType: driveFile.webpublicType ?? driveFile.type,
name: nameNfc,
category: emoji.category?.normalize('NFC') ?? null,
- aliases: emoji.aliases?.map(a => a.normalize('NFC')),
+ aliases: emoji.aliases.map(a => a.normalize('NFC')),
host: null,
license: emoji.license,
isSensitive: emoji.isSensitive,
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 071ddbef18..fd6db9c4ab 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -86,7 +86,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const error = await this.customEmojiService.update({
...required,
- driveFile,
+ originalUrl: driveFile != null ? driveFile.url : undefined,
+ publicUrl: driveFile != null ? (driveFile.webpublicUrl ?? driveFile.url) : undefined,
+ fileType: driveFile != null ? (driveFile.webpublicType ?? driveFile.type) : undefined,
category: ps.category?.normalize('NFC'),
aliases: ps.aliases?.map(a => a.normalize('NFC')),
license: ps.license,
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index 616a77e337..19ca3ceb8e 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -19,6 +19,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { ApiError } from '../../error.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
export const meta = {
tags: ['federation'],
@@ -32,6 +33,31 @@ export const meta = {
},
errors: {
+ federationNotAllowed: {
+ message: 'Federation for this host is not allowed.',
+ code: 'FEDERATION_NOT_ALLOWED',
+ id: '974b799e-1a29-4889-b706-18d4dd93e266',
+ },
+ uriInvalid: {
+ message: 'URI is invalid.',
+ code: 'URI_INVALID',
+ id: '1a5eab56-e47b-48c2-8d5e-217b897d70db',
+ },
+ requestFailed: {
+ message: 'Request failed.',
+ code: 'REQUEST_FAILED',
+ id: '81b539cf-4f57-4b29-bc98-032c33c0792e',
+ },
+ responseInvalid: {
+ message: 'Response from remote server is invalid.',
+ 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',
@@ -110,7 +136,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
*/
@bindThis
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
- if (!this.utilityService.isFederationAllowedUri(uri)) return null;
+ if (!this.utilityService.isFederationAllowedUri(uri)) {
+ throw new ApiError(meta.errors.federationNotAllowed);
+ }
let local = await this.mergePack(me, ...await Promise.all([
this.apDbResolverService.getUserFromApId(uri),
@@ -125,7 +153,40 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// リモートã‹ã‚‰ä¸€æ—¦ã‚ªãƒ–ジェクトフェッãƒ
const resolver = this.apResolverService.createResolver();
- const object = await resolver.resolve(uri) as any;
+ const object = await resolver.resolve(uri).catch((err) => {
+ if (err instanceof IdentifiableError) {
+ switch (err.id) {
+ // resolve
+ case 'b94fd5b1-0e3b-4678-9df2-dad4cd515ab2':
+ throw new ApiError(meta.errors.uriInvalid);
+ case '0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5':
+ case 'd592da9f-822f-4d91-83d7-4ceefabcf3d2':
+ throw new ApiError(meta.errors.requestFailed);
+ 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':
+ throw new ApiError(meta.errors.uriInvalid);
+ case 'a9d946e5-d276-47f8-95fb-f04230289bb0':
+ case '06ae3170-1796-4d93-a697-2611ea6d83b6':
+ throw new ApiError(meta.errors.noSuchObject);
+ case '7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0':
+ throw new ApiError(meta.errors.responseInvalid);
+ }
+ }
+
+ throw new ApiError(meta.errors.requestFailed);
+ });
+
+ if (object.id == null) {
+ throw new ApiError(meta.errors.responseInvalid);
+ }
// /@user ã®ã‚ˆã†ãªæ­£è¦id以外ã§å–å¾—ã§ãã‚‹URIãŒæŒ‡å®šã•れã¦ã„ãŸå ´åˆã€ã“ã“ã§åˆã‚ã¦æ­£è¦URIãŒç¢ºå®šã™ã‚‹
// ã“れã¯DBã«å­˜åœ¨ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ãŸã‚å†åº¦DB検索
diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts
index 661fa257a6..f290ff6844 100644
--- a/packages/backend/src/server/api/endpoints/i/apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/apps.ts
@@ -93,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
name: token.name ?? token.app?.name,
createdAt: this.idService.parse(token.id).date.toISOString(),
lastUsedAt: token.lastUsedAt?.toISOString(),
- permission: token.permission,
+ permission: token.app ? token.app.permission : token.permission,
})));
});
}
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index 09c06a108d..a80e5ed033 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -592,7 +592,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const html = await this.httpRequestService.getHtml(url);
const { window } = new JSDOM(html);
- const doc = window.document;
+ const doc: Document = window.document;
const myLink = `${this.config.url}/@${user.username}`;
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index f11bbbcb1a..e52d9c32df 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -102,15 +102,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
}
- await this.pagesRepository.findBy({
- id: Not(ps.pageId),
- userId: me.id,
- name: ps.name,
- }).then(result => {
- if (result.length > 0) {
- throw new ApiError(meta.errors.nameAlreadyExists);
- }
- });
+ if (ps.name != null) {
+ await this.pagesRepository.findBy({
+ id: Not(ps.pageId),
+ userId: me.id,
+ name: ps.name,
+ }).then(result => {
+ if (result.length > 0) {
+ throw new ApiError(meta.errors.nameAlreadyExists);
+ }
+ });
+ }
await this.pagesRepository.update(page.id, {
updatedAt: new Date(),
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
new file mode 100644
index 0000000000..9426318e34
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/v2/admin/emoji/list.ts
@@ -0,0 +1,126 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+import { CustomEmojiService, fetchEmojisHostTypes, fetchEmojisSortKeys } from '@/core/CustomEmojiService.js';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireRolePolicy: 'canManageCustomEmojis',
+ kind: 'read:admin:emoji',
+
+ res: {
+ type: 'object',
+ properties: {
+ emojis: {
+ type: 'array',
+ items: {
+ type: 'object',
+ ref: 'EmojiDetailedAdmin',
+ },
+ },
+ count: { type: 'integer' },
+ allCount: { type: 'integer' },
+ allPages: { type: 'integer' },
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ query: {
+ type: 'object',
+ nullable: true,
+ properties: {
+ updatedAtFrom: { type: 'string' },
+ updatedAtTo: { type: 'string' },
+ name: { type: 'string' },
+ host: { type: 'string' },
+ uri: { type: 'string' },
+ publicUrl: { type: 'string' },
+ originalUrl: { type: 'string' },
+ type: { type: 'string' },
+ aliases: { type: 'string' },
+ category: { type: 'string' },
+ license: { type: 'string' },
+ isSensitive: { type: 'boolean' },
+ localOnly: { type: 'boolean' },
+ hostType: {
+ type: 'string',
+ enum: fetchEmojisHostTypes,
+ default: 'all',
+ },
+ roleIds: {
+ type: 'array',
+ items: { type: 'string', format: 'misskey:id' },
+ },
+ },
+ },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ page: { type: 'integer' },
+ sortKeys: {
+ type: 'array',
+ default: ['-id'],
+ items: {
+ type: 'string',
+ enum: fetchEmojisSortKeys,
+ },
+ },
+ },
+ required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private customEmojiService: CustomEmojiService,
+ private emojiEntityService: EmojiEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const q = ps.query;
+ const result = await this.customEmojiService.fetchEmojis(
+ {
+ query: {
+ updatedAtFrom: q?.updatedAtFrom,
+ updatedAtTo: q?.updatedAtTo,
+ name: q?.name,
+ host: q?.host,
+ uri: q?.uri,
+ publicUrl: q?.publicUrl,
+ type: q?.type,
+ aliases: q?.aliases,
+ category: q?.category,
+ license: q?.license,
+ isSensitive: q?.isSensitive,
+ localOnly: q?.localOnly,
+ hostType: q?.hostType,
+ roleIds: q?.roleIds,
+ },
+ sinceId: ps.sinceId,
+ untilId: ps.untilId,
+ },
+ {
+ limit: ps.limit,
+ page: ps.page,
+ sortKeys: ps.sortKeys,
+ },
+ );
+
+ return {
+ emojis: await this.emojiEntityService.packDetailedAdminMany(result.emojis),
+ count: result.count,
+ allCount: result.allCount,
+ allPages: result.allPages,
+ };
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts
index eb854a7141..c80dda8d96 100644
--- a/packages/backend/src/server/api/openapi/schemas.ts
+++ b/packages/backend/src/server/api/openapi/schemas.ts
@@ -3,13 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import { deepClone } from '@/misc/clone.js';
import type { Schema } from '@/misc/json-schema.js';
import { refs } from '@/misc/json-schema.js';
export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res', includeSelfRef: boolean): any {
// optional, nullable, refã¯ã‚¹ã‚­ãƒ¼ãƒžå®šç¾©ã«å«ã¾ã‚Œãªã„ã®ã§åˆ†é›¢ã—ã¦ãŠã
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { optional, nullable, ref, selfRef, ...res }: any = schema;
+ const { optional, nullable, ref, selfRef, ..._res }: any = schema;
+ const res = deepClone(_res);
if (schema.type === 'object' && schema.properties) {
if (type === 'res') {
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index ae923ada1f..cd1df1605c 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -323,16 +323,19 @@ export class ClientServerService {
done();
});
} else {
+ const configUrl = new URL(this.config.url);
+ const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, '');
+
const port = (process.env.VITE_PORT ?? '5173');
fastify.register(fastifyProxy, {
- upstream: 'http://localhost:' + port,
+ upstream: urlOriginWithoutPort + ':' + port,
prefix: '/vite',
rewritePrefix: '/vite',
});
const embedPort = (process.env.EMBED_VITE_PORT ?? '5174');
fastify.register(fastifyProxy, {
- upstream: 'http://localhost:' + embedPort,
+ upstream: urlOriginWithoutPort + ':' + embedPort,
prefix: '/embed_vite',
rewritePrefix: '/embed_vite',
});
@@ -536,6 +539,7 @@ export class ClientServerService {
host: host ?? IsNull(),
isSuspended: false,
enableRss: true,
+ requireSigninToViewContents: false,
});
return user && await this.feedService.packFeed(user);
@@ -840,6 +844,7 @@ export class ClientServerService {
fastify.get<{ Params: { announcementId: string; } }>('/announcements/:announcementId', async (request, reply) => {
const announcement = await this.announcementsRepository.findOneBy({
id: request.params.announcementId,
+ userId: IsNull(),
});
if (announcement) {
diff --git a/packages/backend/test-federation/compose.tpl.yml b/packages/backend/test-federation/compose.tpl.yml
index 8c38f16919..8b270e58f7 100644
--- a/packages/backend/test-federation/compose.tpl.yml
+++ b/packages/backend/test-federation/compose.tpl.yml
@@ -17,6 +17,7 @@ services:
- ./.config/docker.env
environment:
- NODE_ENV=production
+ - COREPACK_DEFAULT_TO_LATEST=0
volumes:
- type: bind
source: ../../../built
diff --git a/packages/backend/test-federation/compose.yml b/packages/backend/test-federation/compose.yml
index 62d7e977c0..a5a7223982 100644
--- a/packages/backend/test-federation/compose.yml
+++ b/packages/backend/test-federation/compose.yml
@@ -25,6 +25,7 @@ services:
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
@@ -85,6 +86,8 @@ services:
depends_on:
redis.test:
condition: service_healthy
+ environment:
+ - COREPACK_DEFAULT_TO_LATEST=0
volumes:
- type: bind
source: ../package.json
diff --git a/packages/backend/test-federation/test/note.test.ts b/packages/backend/test-federation/test/note.test.ts
index bacc4cc54f..220c22e198 100644
--- a/packages/backend/test-federation/test/note.test.ts
+++ b/packages/backend/test-federation/test/note.test.ts
@@ -131,11 +131,7 @@ describe('Note', () => {
rejects(
async () => await bob.client.request('ap/show', { uri: `https://a.test/notes/${note.id}` }),
(err: any) => {
- /**
- * FIXME: this error is not handled
- * @see https://github.com/misskey-dev/misskey/issues/12736
- */
- strictEqual(err.code, 'INTERNAL_ERROR');
+ strictEqual(err.code, 'REQUEST_FAILED');
return true;
},
);
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
index d12be2a9ac..319c8581f4 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 * 10);
+ }, 1000 * 15);
test.concurrent('フォローã—ã¦ã„るユーザーã®ãƒãƒ£ãƒ³ãƒãƒ«æŠ•稿ãŒå«ã¾ã‚Œãªã„', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts
index 235af29f0d..1326003c5e 100644
--- a/packages/backend/test/unit/AbuseReportNotificationService.ts
+++ b/packages/backend/test/unit/AbuseReportNotificationService.ts
@@ -3,13 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { jest } from '@jest/globals';
+import { describe, jest } from '@jest/globals';
import { Test, TestingModule } from '@nestjs/testing';
import { randomString } from '../utils.js';
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
import {
AbuseReportNotificationRecipientRepository,
MiAbuseReportNotificationRecipient,
+ MiAbuseUserReport,
MiSystemWebhook,
MiUser,
SystemWebhooksRepository,
@@ -112,7 +113,10 @@ describe('AbuseReportNotificationService', () => {
provide: SystemWebhookService, useFactory: () => ({ enqueueSystemWebhook: jest.fn() }),
},
{
- provide: UserEntityService, useFactory: () => ({ pack: (v: any) => v }),
+ provide: UserEntityService, useFactory: () => ({
+ pack: (v: any) => Promise.resolve(v),
+ packMany: (v: any) => Promise.resolve(v),
+ }),
},
{
provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }),
@@ -344,4 +348,46 @@ describe('AbuseReportNotificationService', () => {
expect(recipients).toEqual([recipient3]);
});
});
+
+ describe('notifySystemWebhook', () => {
+ test('éžã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªé€šå ±é€šçŸ¥ã¯Webhooké€ä¿¡ã‹ã‚‰é™¤å¤–ã•れる', async () => {
+ const recipient1 = await createRecipient({
+ method: 'webhook',
+ systemWebhookId: systemWebhook1.id,
+ isActive: true,
+ });
+ const recipient2 = await createRecipient({
+ method: 'webhook',
+ systemWebhookId: systemWebhook2.id,
+ isActive: false,
+ });
+
+ const reports: MiAbuseUserReport[] = [
+ {
+ id: idService.gen(),
+ targetUserId: alice.id,
+ targetUser: alice,
+ reporterId: bob.id,
+ reporter: bob,
+ assigneeId: null,
+ assignee: null,
+ resolved: false,
+ forwarded: false,
+ comment: 'test',
+ moderationNote: '',
+ resolvedAs: null,
+ targetUserHost: null,
+ reporterHost: null,
+ },
+ ];
+
+ await service.notifySystemWebhook(reports, 'abuseReport');
+
+ // 実際ã«é™¤å¤–ã•れるã‹ã¯SystemWebhookServiceå´ã§ç¢ºèªã™ã‚‹.
+ // ã“ã“ã§ã¯éžã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªé€šå ±é€šçŸ¥ã‚’除外設定ã§ãã¦ã„ã‚‹ã‹ã‚’確èªã™ã‚‹
+ expect(webhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
+ expect(webhookService.enqueueSystemWebhook.mock.calls[0][0]).toBe('abuseReport');
+ expect(webhookService.enqueueSystemWebhook.mock.calls[0][2]).toEqual({ excludes: [systemWebhook2.id] });
+ });
+ });
});
diff --git a/packages/backend/test/unit/CaptchaService.ts b/packages/backend/test/unit/CaptchaService.ts
new file mode 100644
index 0000000000..51b70b05a1
--- /dev/null
+++ b/packages/backend/test/unit/CaptchaService.ts
@@ -0,0 +1,622 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { afterAll, beforeAll, beforeEach, describe, expect, jest } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import { Response } from 'node-fetch';
+import {
+ CaptchaError,
+ CaptchaErrorCode,
+ captchaErrorCodes,
+ CaptchaSaveResult,
+ CaptchaService,
+} from '@/core/CaptchaService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { HttpRequestService } from '@/core/HttpRequestService.js';
+import { MetaService } from '@/core/MetaService.js';
+import { MiMeta } from '@/models/Meta.js';
+import { LoggerService } from '@/core/LoggerService.js';
+
+describe('CaptchaService', () => {
+ let app: TestingModule;
+ let service: CaptchaService;
+ let httpRequestService: jest.Mocked<HttpRequestService>;
+ let metaService: jest.Mocked<MetaService>;
+
+ beforeAll(async () => {
+ app = await Test.createTestingModule({
+ imports: [
+ GlobalModule,
+ ],
+ providers: [
+ CaptchaService,
+ LoggerService,
+ {
+ provide: HttpRequestService, useFactory: () => ({ send: jest.fn() }),
+ },
+ {
+ provide: MetaService, useFactory: () => ({
+ fetch: jest.fn(),
+ update: jest.fn(),
+ }),
+ },
+ ],
+ }).compile();
+
+ app.enableShutdownHooks();
+
+ service = app.get(CaptchaService);
+ httpRequestService = app.get(HttpRequestService) as jest.Mocked<HttpRequestService>;
+ metaService = app.get(MetaService) as jest.Mocked<MetaService>;
+ });
+
+ beforeEach(() => {
+ httpRequestService.send.mockClear();
+ metaService.update.mockClear();
+ metaService.fetch.mockClear();
+ });
+
+ afterAll(async () => {
+ await app.close();
+ });
+
+ function successMock(result: object) {
+ httpRequestService.send.mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => (result),
+ } as Response);
+ }
+
+ function failureHttpMock() {
+ httpRequestService.send.mockResolvedValue({
+ ok: false,
+ status: 400,
+ } as Response);
+ }
+
+ function failureVerificationMock(result: object) {
+ httpRequestService.send.mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => (result),
+ } as Response);
+ }
+
+ async function testCaptchaError(code: CaptchaErrorCode, test: () => Promise<void>) {
+ try {
+ await test();
+ expect(false).toBe(true);
+ } catch (e) {
+ expect(e instanceof CaptchaError).toBe(true);
+
+ const _e = e as CaptchaError;
+ expect(_e.code).toBe(code);
+ }
+ }
+
+ describe('verifyRecaptcha', () => {
+ test('success', async () => {
+ successMock({ success: true });
+ await service.verifyRecaptcha('secret', 'response');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyRecaptcha('secret', null));
+ });
+
+ test('requestFailed', async () => {
+ failureHttpMock();
+ await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyRecaptcha('secret', 'response'));
+ });
+
+ test('verificationFailed', async () => {
+ failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyRecaptcha('secret', 'response'));
+ });
+ });
+
+ describe('verifyHcaptcha', () => {
+ test('success', async () => {
+ successMock({ success: true });
+ await service.verifyHcaptcha('secret', 'response');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyHcaptcha('secret', null));
+ });
+
+ test('requestFailed', async () => {
+ failureHttpMock();
+ await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyHcaptcha('secret', 'response'));
+ });
+
+ test('verificationFailed', async () => {
+ failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyHcaptcha('secret', 'response'));
+ });
+ });
+
+ describe('verifyMcaptcha', () => {
+ const host = 'https://localhost';
+
+ test('success', async () => {
+ successMock({ valid: true });
+ await service.verifyMcaptcha('secret', 'sitekey', host, 'response');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyMcaptcha('secret', 'sitekey', host, null));
+ });
+
+ test('requestFailed', async () => {
+ failureHttpMock();
+ await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyMcaptcha('secret', 'sitekey', host, 'response'));
+ });
+
+ test('verificationFailed', async () => {
+ failureVerificationMock({ valid: false });
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyMcaptcha('secret', 'sitekey', host, 'response'));
+ });
+ });
+
+ describe('verifyTurnstile', () => {
+ test('success', async () => {
+ successMock({ success: true });
+ await service.verifyTurnstile('secret', 'response');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyTurnstile('secret', null));
+ });
+
+ test('requestFailed', async () => {
+ failureHttpMock();
+ await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyTurnstile('secret', 'response'));
+ });
+
+ test('verificationFailed', async () => {
+ failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] });
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyTurnstile('secret', 'response'));
+ });
+ });
+
+ describe('verifyTestcaptcha', () => {
+ test('success', async () => {
+ await service.verifyTestcaptcha('testcaptcha-passed');
+ });
+
+ test('noResponseProvided', async () => {
+ await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyTestcaptcha(null));
+ });
+
+ test('verificationFailed', async () => {
+ await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyTestcaptcha('testcaptcha-failed'));
+ });
+ });
+
+ describe('get', () => {
+ function setupMeta(meta: Partial<MiMeta>) {
+ metaService.fetch.mockResolvedValue(meta as MiMeta);
+ }
+
+ test('values', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ hcaptchaSiteKey: 'hcaptcha-sitekey',
+ hcaptchaSecretKey: 'hcaptcha-secret',
+ mcaptchaSitekey: 'mcaptcha-sitekey',
+ mcaptchaSecretKey: 'mcaptcha-secret',
+ mcaptchaInstanceUrl: 'https://localhost',
+ recaptchaSiteKey: 'recaptcha-sitekey',
+ recaptchaSecretKey: 'recaptcha-secret',
+ turnstileSiteKey: 'turnstile-sitekey',
+ turnstileSecretKey: 'turnstile-secret',
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('none');
+ expect(result.hcaptcha.siteKey).toBe('hcaptcha-sitekey');
+ expect(result.hcaptcha.secretKey).toBe('hcaptcha-secret');
+ expect(result.mcaptcha.siteKey).toBe('mcaptcha-sitekey');
+ expect(result.mcaptcha.secretKey).toBe('mcaptcha-secret');
+ expect(result.mcaptcha.instanceUrl).toBe('https://localhost');
+ expect(result.recaptcha.siteKey).toBe('recaptcha-sitekey');
+ expect(result.recaptcha.secretKey).toBe('recaptcha-secret');
+ expect(result.turnstile.siteKey).toBe('turnstile-sitekey');
+ expect(result.turnstile.secretKey).toBe('turnstile-secret');
+ });
+
+ describe('provider', () => {
+ test('none', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('none');
+ });
+
+ test('hcaptcha', async () => {
+ setupMeta({
+ enableHcaptcha: true,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('hcaptcha');
+ });
+
+ test('mcaptcha', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: true,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('mcaptcha');
+ });
+
+ test('recaptcha', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: true,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('recaptcha');
+ });
+
+ test('turnstile', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: true,
+ enableTestcaptcha: false,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('turnstile');
+ });
+
+ test('testcaptcha', async () => {
+ setupMeta({
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: true,
+ });
+
+ const result = await service.get();
+ expect(result.provider).toBe('testcaptcha');
+ });
+ });
+ });
+
+ describe('save', () => {
+ const host = 'https://localhost';
+
+ describe('[success] æ¤œè¨¼ã«æˆåŠŸã—ãŸæ™‚ã ã‘ä¿å­˜ã§ãる+他ã®ãƒ—ロãƒã‚¤ãƒ€ã®è¨­å®šå€¤ã‚’誤ã£ã¦æ›´æ–°ã—ãªã„', () => {
+ beforeEach(() => {
+ successMock({ success: true, valid: true });
+ });
+
+ async function assertSuccess(promise: Promise<CaptchaSaveResult>, expectMeta: Partial<MiMeta>) {
+ await expect(promise)
+ .resolves
+ .toStrictEqual({ success: true });
+ const partialParams = metaService.update.mock.calls[0][0];
+ expect(partialParams).toStrictEqual(expectMeta);
+ }
+
+ test('none', async () => {
+ await assertSuccess(
+ service.save('none'),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ },
+ );
+ });
+
+ test('hcaptcha', async () => {
+ await assertSuccess(
+ service.save('hcaptcha', {
+ sitekey: 'hcaptcha-sitekey',
+ secret: 'hcaptcha-secret',
+ captchaResult: 'hcaptcha-passed',
+ }),
+ {
+ enableHcaptcha: true,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ hcaptchaSiteKey: 'hcaptcha-sitekey',
+ hcaptchaSecretKey: 'hcaptcha-secret',
+ },
+ );
+ });
+
+ test('mcaptcha', async () => {
+ await assertSuccess(
+ service.save('mcaptcha', {
+ sitekey: 'mcaptcha-sitekey',
+ secret: 'mcaptcha-secret',
+ instanceUrl: host,
+ captchaResult: 'mcaptcha-passed',
+ }),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: true,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ mcaptchaSitekey: 'mcaptcha-sitekey',
+ mcaptchaSecretKey: 'mcaptcha-secret',
+ mcaptchaInstanceUrl: host,
+ },
+ );
+ });
+
+ test('recaptcha', async () => {
+ await assertSuccess(
+ service.save('recaptcha', {
+ sitekey: 'recaptcha-sitekey',
+ secret: 'recaptcha-secret',
+ captchaResult: 'recaptcha-passed',
+ }),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: true,
+ enableTurnstile: false,
+ enableTestcaptcha: false,
+ recaptchaSiteKey: 'recaptcha-sitekey',
+ recaptchaSecretKey: 'recaptcha-secret',
+ },
+ );
+ });
+
+ test('turnstile', async () => {
+ await assertSuccess(
+ service.save('turnstile', {
+ sitekey: 'turnstile-sitekey',
+ secret: 'turnstile-secret',
+ captchaResult: 'turnstile-passed',
+ }),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: true,
+ enableTestcaptcha: false,
+ turnstileSiteKey: 'turnstile-sitekey',
+ turnstileSecretKey: 'turnstile-secret',
+ },
+ );
+ });
+
+ test('testcaptcha', async () => {
+ await assertSuccess(
+ service.save('testcaptcha', {
+ sitekey: 'testcaptcha-sitekey',
+ secret: 'testcaptcha-secret',
+ captchaResult: 'testcaptcha-passed',
+ }),
+ {
+ enableHcaptcha: false,
+ enableMcaptcha: false,
+ enableRecaptcha: false,
+ enableTurnstile: false,
+ enableTestcaptcha: true,
+ },
+ );
+ });
+ });
+
+ describe('[failure] 検証ã«å¤±æ•—ã—ãŸå ´åˆã¯ä¿å­˜ã§ããªã„ï¼‹è¨­å®šå€¤ã®æ›´æ–°ãã®ã‚‚ã®ãŒç™ºç”Ÿã—ãªã„', () => {
+ async function assertFailure(code: CaptchaErrorCode, promise: Promise<CaptchaSaveResult>) {
+ const res = await promise;
+ expect(res.success).toBe(false);
+ if (!res.success) {
+ expect(res.error.code).toBe(code);
+ }
+ expect(metaService.update).not.toBeCalled();
+ }
+
+ describe('invalidParameters', () => {
+ test('hcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('hcaptcha', {
+ sitekey: 'hcaptcha-sitekey',
+ secret: 'hcaptcha-secret',
+ captchaResult: null,
+ }),
+ );
+ });
+
+ test('mcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('mcaptcha', {
+ sitekey: 'mcaptcha-sitekey',
+ secret: 'mcaptcha-secret',
+ instanceUrl: host,
+ captchaResult: null,
+ }),
+ );
+ });
+
+ test('recaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('recaptcha', {
+ sitekey: 'recaptcha-sitekey',
+ secret: 'recaptcha-secret',
+ captchaResult: null,
+ }),
+ );
+ });
+
+ test('turnstile', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('turnstile', {
+ sitekey: 'turnstile-sitekey',
+ secret: 'turnstile-secret',
+ captchaResult: null,
+ }),
+ );
+ });
+
+ test('testcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.invalidParameters,
+ service.save('testcaptcha', {
+ captchaResult: null,
+ }),
+ );
+ });
+ });
+
+ describe('requestFailed', () => {
+ beforeEach(() => {
+ failureHttpMock();
+ });
+
+ test('hcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.requestFailed,
+ service.save('hcaptcha', {
+ sitekey: 'hcaptcha-sitekey',
+ secret: 'hcaptcha-secret',
+ captchaResult: 'hcaptcha-passed',
+ }),
+ );
+ });
+
+ test('mcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.requestFailed,
+ service.save('mcaptcha', {
+ sitekey: 'mcaptcha-sitekey',
+ secret: 'mcaptcha-secret',
+ instanceUrl: host,
+ captchaResult: 'mcaptcha-passed',
+ }),
+ );
+ });
+
+ test('recaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.requestFailed,
+ service.save('recaptcha', {
+ sitekey: 'recaptcha-sitekey',
+ secret: 'recaptcha-secret',
+ captchaResult: 'recaptcha-passed',
+ }),
+ );
+ });
+
+ test('turnstile', async () => {
+ await assertFailure(
+ captchaErrorCodes.requestFailed,
+ service.save('turnstile', {
+ sitekey: 'turnstile-sitekey',
+ secret: 'turnstile-secret',
+ captchaResult: 'turnstile-passed',
+ }),
+ );
+ });
+
+ // testchapchaã¯requestFailedãŒãªã„
+ });
+
+ describe('verificationFailed', () => {
+ beforeEach(() => {
+ failureVerificationMock({ success: false, valid: false, 'error-codes': ['code01', 'code02'] });
+ });
+
+ test('hcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('hcaptcha', {
+ sitekey: 'hcaptcha-sitekey',
+ secret: 'hcaptcha-secret',
+ captchaResult: 'hccaptcha-passed',
+ }),
+ );
+ });
+
+ test('mcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('mcaptcha', {
+ sitekey: 'mcaptcha-sitekey',
+ secret: 'mcaptcha-secret',
+ instanceUrl: host,
+ captchaResult: 'mcaptcha-passed',
+ }),
+ );
+ });
+
+ test('recaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('recaptcha', {
+ sitekey: 'recaptcha-sitekey',
+ secret: 'recaptcha-secret',
+ captchaResult: 'recaptcha-passed',
+ }),
+ );
+ });
+
+ test('turnstile', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('turnstile', {
+ sitekey: 'turnstile-sitekey',
+ secret: 'turnstile-secret',
+ captchaResult: 'turnstile-passed',
+ }),
+ );
+ });
+
+ test('testcaptcha', async () => {
+ await assertFailure(
+ captchaErrorCodes.verificationFailed,
+ service.save('testcaptcha', {
+ captchaResult: 'testcaptcha-failed',
+ }),
+ );
+ });
+ });
+ });
+ });
+});
diff --git a/packages/backend/test/unit/CustomEmojiService.ts b/packages/backend/test/unit/CustomEmojiService.ts
new file mode 100644
index 0000000000..10b687c6a0
--- /dev/null
+++ b/packages/backend/test/unit/CustomEmojiService.ts
@@ -0,0 +1,817 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { afterEach, beforeAll, describe, test } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { IdService } from '@/core/IdService.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { EmojisRepository } from '@/models/_.js';
+import { MiEmoji } from '@/models/Emoji.js';
+
+describe('CustomEmojiService', () => {
+ let app: TestingModule;
+ let service: CustomEmojiService;
+
+ let emojisRepository: EmojisRepository;
+ let idService: IdService;
+
+ beforeAll(async () => {
+ app = await Test
+ .createTestingModule({
+ imports: [
+ GlobalModule,
+ ],
+ providers: [
+ CustomEmojiService,
+ UtilityService,
+ IdService,
+ EmojiEntityService,
+ ModerationLogService,
+ GlobalEventService,
+ ],
+ })
+ .compile();
+ app.enableShutdownHooks();
+
+ service = app.get<CustomEmojiService>(CustomEmojiService);
+ emojisRepository = app.get<EmojisRepository>(DI.emojisRepository);
+ idService = app.get<IdService>(IdService);
+ });
+
+ describe('fetchEmojis', () => {
+ async function insert(data: Partial<MiEmoji>[]) {
+ for (const d of data) {
+ const id = idService.gen();
+ await emojisRepository.insert({
+ id: id,
+ updatedAt: new Date(),
+ ...d,
+ });
+ }
+ }
+
+ function call(params: Parameters<CustomEmojiService['fetchEmojis']>['0']) {
+ return service.fetchEmojis(
+ params,
+ {
+ // テストå‘ã‘ã«
+ sortKeys: ['+id'],
+ },
+ );
+ }
+
+ function defaultData(suffix: string, override?: Partial<MiEmoji>): Partial<MiEmoji> {
+ return {
+ name: `emoji${suffix}`,
+ host: null,
+ category: 'default',
+ originalUrl: `https://example.com/emoji${suffix}.png`,
+ publicUrl: `https://example.com/emoji${suffix}.png`,
+ type: 'image/png',
+ aliases: [`emoji${suffix}`],
+ license: 'CC0',
+ isSensitive: false,
+ localOnly: false,
+ roleIdsThatCanBeUsedThisEmojiAsReaction: [],
+ ...override,
+ };
+ }
+
+ afterEach(async () => {
+ await emojisRepository.delete({});
+ });
+
+ describe('å˜ç‹¬', () => {
+ test('updatedAtFrom', async () => {
+ await insert([
+ defaultData('001', { updatedAt: new Date('2021-01-01T00:00:00.000Z') }),
+ defaultData('002', { updatedAt: new Date('2021-01-02T00:00:00.000Z') }),
+ defaultData('003', { updatedAt: new Date('2021-01-03T00:00:00.000Z') }),
+ ]);
+
+ const actual = await call({
+ query: {
+ updatedAtFrom: '2021-01-02T00:00:00.000Z',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('updatedAtTo', async () => {
+ await insert([
+ defaultData('001', { updatedAt: new Date('2021-01-01T00:00:00.000Z') }),
+ defaultData('002', { updatedAt: new Date('2021-01-02T00:00:00.000Z') }),
+ defaultData('003', { updatedAt: new Date('2021-01-03T00:00:00.000Z') }),
+ ]);
+
+ const actual = await call({
+ query: {
+ updatedAtTo: '2021-01-02T00:00:00.000Z',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ });
+
+ describe('name', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001'),
+ defaultData('002'),
+ ]);
+
+ const actual = await call({
+ query: {
+ name: 'emoji001',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001'),
+ defaultData('002'),
+ ]);
+
+ const actual = await call({
+ query: {
+ name: 'emoji001 emoji002',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001'),
+ defaultData('002'),
+ defaultData('003', { name: 'em003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ name: 'oji',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001'),
+ ]);
+
+ const actual = await call({
+ query: {
+ name: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('host', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { host: 'example.com' }),
+ defaultData('002', { host: 'example.com' }),
+ defaultData('003', { host: '1.example.com' }),
+ defaultData('004', { host: '2.example.com' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ host: 'example.com',
+ hostType: 'remote',
+ },
+ });
+
+ expect(actual.allCount).toBe(4);
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { host: 'example.com' }),
+ defaultData('002', { host: 'example.com' }),
+ defaultData('003', { host: '1.example.com' }),
+ defaultData('004', { host: '2.example.com' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ host: '1.example.com 2.example.com',
+ hostType: 'remote',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji003');
+ expect(actual.emojis[1].name).toBe('emoji004');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { host: 'example.com' }),
+ defaultData('002', { host: 'example.com' }),
+ defaultData('003', { host: '1.example.com' }),
+ defaultData('004', { host: '2.example.com' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ host: 'example',
+ hostType: 'remote',
+ },
+ });
+
+ expect(actual.allCount).toBe(4);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { host: 'example.com' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ host: '%',
+ hostType: 'remote',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('uri', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { uri: 'uri001' }),
+ defaultData('002', { uri: 'uri002' }),
+ defaultData('003', { uri: 'uri003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ uri: 'uri002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { uri: 'uri001' }),
+ defaultData('002', { uri: 'uri002' }),
+ defaultData('003', { uri: 'uri003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ uri: 'uri001 uri003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { uri: 'uri001' }),
+ defaultData('002', { uri: 'uri002' }),
+ defaultData('003', { uri: 'uri003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ uri: 'ri',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { uri: 'uri001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ uri: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('publicUrl', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { publicUrl: 'publicUrl001' }),
+ defaultData('002', { publicUrl: 'publicUrl002' }),
+ defaultData('003', { publicUrl: 'publicUrl003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ publicUrl: 'publicUrl002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { publicUrl: 'publicUrl001' }),
+ defaultData('002', { publicUrl: 'publicUrl002' }),
+ defaultData('003', { publicUrl: 'publicUrl003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ publicUrl: 'publicUrl001 publicUrl003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { publicUrl: 'publicUrl001' }),
+ defaultData('002', { publicUrl: 'publicUrl002' }),
+ defaultData('003', { publicUrl: 'publicUrl003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ publicUrl: 'Url',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { publicUrl: 'publicUrl001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ publicUrl: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('type', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { type: 'type001' }),
+ defaultData('002', { type: 'type002' }),
+ defaultData('003', { type: 'type003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ type: 'type002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { type: 'type001' }),
+ defaultData('002', { type: 'type002' }),
+ defaultData('003', { type: 'type003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ type: 'type001 type003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { type: 'type001' }),
+ defaultData('002', { type: 'type002' }),
+ defaultData('003', { type: 'type003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ type: 'pe',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { type: 'type001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ type: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('aliases', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { aliases: ['alias001', 'alias002'] }),
+ defaultData('002', { aliases: ['alias002'] }),
+ defaultData('003', { aliases: ['alias003'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ aliases: 'alias002',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { aliases: ['alias001', 'alias002'] }),
+ defaultData('002', { aliases: ['alias002', 'alias004'] }),
+ defaultData('003', { aliases: ['alias003'] }),
+ defaultData('004', { aliases: ['alias004'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ aliases: 'alias001 alias004',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ expect(actual.emojis[2].name).toBe('emoji004');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { aliases: ['alias001', 'alias002'] }),
+ defaultData('002', { aliases: ['alias002', 'alias004'] }),
+ defaultData('003', { aliases: ['alias003'] }),
+ defaultData('004', { aliases: ['alias004'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ aliases: 'ias',
+ },
+ });
+
+ expect(actual.allCount).toBe(4);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { aliases: ['alias001', 'alias002'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ aliases: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('category', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { category: 'category001' }),
+ defaultData('002', { category: 'category002' }),
+ defaultData('003', { category: 'category003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ category: 'category002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { category: 'category001' }),
+ defaultData('002', { category: 'category002' }),
+ defaultData('003', { category: 'category003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ category: 'category001 category003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { category: 'category001' }),
+ defaultData('002', { category: 'category002' }),
+ defaultData('003', { category: 'category003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ category: 'egory',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { category: 'category001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ category: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('license', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { license: 'license001' }),
+ defaultData('002', { license: 'license002' }),
+ defaultData('003', { license: 'license003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ license: 'license002',
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { license: 'license001' }),
+ defaultData('002', { license: 'license002' }),
+ defaultData('003', { license: 'license003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ license: 'license001 license003',
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('keyword', async () => {
+ await insert([
+ defaultData('001', { license: 'license001' }),
+ defaultData('002', { license: 'license002' }),
+ defaultData('003', { license: 'license003' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ license: 'cense',
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+
+ test('escape', async () => {
+ await insert([
+ defaultData('001', { license: 'license001' }),
+ ]);
+
+ const actual = await call({
+ query: {
+ license: '%',
+ },
+ });
+
+ expect(actual.allCount).toBe(0);
+ });
+ });
+
+ describe('isSensitive', () => {
+ test('true', async () => {
+ await insert([
+ defaultData('001', { isSensitive: true }),
+ defaultData('002', { isSensitive: false }),
+ defaultData('003', { isSensitive: true }),
+ ]);
+
+ const actual = await call({
+ query: {
+ isSensitive: true,
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('false', async () => {
+ await insert([
+ defaultData('001', { isSensitive: true }),
+ defaultData('002', { isSensitive: false }),
+ defaultData('003', { isSensitive: true }),
+ ]);
+
+ const actual = await call({
+ query: {
+ isSensitive: false,
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('null', async () => {
+ await insert([
+ defaultData('001', { isSensitive: true }),
+ defaultData('002', { isSensitive: false }),
+ defaultData('003', { isSensitive: true }),
+ ]);
+
+ const actual = await call({
+ query: {},
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+ });
+
+ describe('localOnly', () => {
+ test('true', async () => {
+ await insert([
+ defaultData('001', { localOnly: true }),
+ defaultData('002', { localOnly: false }),
+ defaultData('003', { localOnly: true }),
+ ]);
+
+ const actual = await call({
+ query: {
+ localOnly: true,
+ },
+ });
+
+ expect(actual.allCount).toBe(2);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji003');
+ });
+
+ test('false', async () => {
+ await insert([
+ defaultData('001', { localOnly: true }),
+ defaultData('002', { localOnly: false }),
+ defaultData('003', { localOnly: true }),
+ ]);
+
+ const actual = await call({
+ query: {
+ localOnly: false,
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('null', async () => {
+ await insert([
+ defaultData('001', { localOnly: true }),
+ defaultData('002', { localOnly: false }),
+ defaultData('003', { localOnly: true }),
+ ]);
+
+ const actual = await call({
+ query: {},
+ });
+
+ expect(actual.allCount).toBe(3);
+ });
+ });
+
+ describe('roleId', () => {
+ test('single', async () => {
+ await insert([
+ defaultData('001', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role001'] }),
+ defaultData('002', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role002'] }),
+ defaultData('003', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role003'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ roleIds: ['role002'],
+ },
+ });
+
+ expect(actual.allCount).toBe(1);
+ expect(actual.emojis[0].name).toBe('emoji002');
+ });
+
+ test('multi', async () => {
+ await insert([
+ defaultData('001', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role001'] }),
+ defaultData('002', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role002', 'role003'] }),
+ defaultData('003', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role003'] }),
+ defaultData('004', { roleIdsThatCanBeUsedThisEmojiAsReaction: ['role004'] }),
+ ]);
+
+ const actual = await call({
+ query: {
+ roleIds: ['role001', 'role003'],
+ },
+ });
+
+ expect(actual.allCount).toBe(3);
+ expect(actual.emojis[0].name).toBe('emoji001');
+ expect(actual.emojis[1].name).toBe('emoji002');
+ expect(actual.emojis[2].name).toBe('emoji003');
+ });
+ });
+ });
+ });
+});
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index 8d5683329f..73d9c7b60c 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -108,6 +108,24 @@ describe('MfmService', () => {
assert.deepStrictEqual(mfmService.fromHtml('<p>a <a></a> d</p>'), 'a d');
});
+ test('ruby', () => {
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp></ruby> b</p>'), 'a $[ruby Misskey ミスキー] b');
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp></ruby> b</p>'), 'a $[ruby Misskey ミスキー]$[ruby Misskey ミスキー] b');
+ });
+
+ test('ruby with spaces', () => {
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Miss key<rp>(</rp><rt>ミスキー</rt><rp>)</rp> b</ruby> c</p>'), 'a Miss key(ミスキー) b c');
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミス キー</rt><rp>)</rp> b</ruby> c</p>'), 'a Misskey(ミス キー) b c');
+ assert.deepStrictEqual(
+ mfmService.fromHtml('<p>a <ruby>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp>Misskey<rp>(</rp><rt>ミス キー</rt><rp>)</rp>Misskey<rp>(</rp><rt>ミスキー</rt><rp>)</rp></ruby> b</p>'),
+ 'a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b'
+ );
+ });
+
+ test('ruby with other inline tags', () => {
+ assert.deepStrictEqual(mfmService.fromHtml('<p>a <ruby><strong>Misskey</strong><rp>(</rp><rt>ミスキー</rt><rp>)</rp> b</ruby> c</p>'), 'a **Misskey**(ミスキー) b c');
+ });
+
test('mention', () => {
assert.deepStrictEqual(mfmService.fromHtml('<p>a <a href="https://example.com/@user" class="u-url mention">@user</a> d</p>'), 'a @user@example.com d');
});
diff --git a/packages/backend/test/unit/SystemWebhookService.ts b/packages/backend/test/unit/SystemWebhookService.ts
index 5401dd74d8..fee4acb305 100644
--- a/packages/backend/test/unit/SystemWebhookService.ts
+++ b/packages/backend/test/unit/SystemWebhookService.ts
@@ -314,9 +314,10 @@ describe('SystemWebhookService', () => {
isActive: true,
on: ['abuseReport'],
});
- await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any);
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
- expect(queueService.systemWebhookDeliver).toHaveBeenCalled();
+ expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook);
});
test('éžã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªWebhookã¯ã‚­ãƒ¥ãƒ¼ã«è¿½åŠ ã•れãªã„', async () => {
@@ -324,7 +325,7 @@ describe('SystemWebhookService', () => {
isActive: false,
on: ['abuseReport'],
});
- await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any);
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
});
@@ -338,11 +339,49 @@ describe('SystemWebhookService', () => {
isActive: true,
on: ['abuseReportResolved'],
});
- await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' } as any);
- await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' } as any);
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
});
+
+ test('混在ã—ãŸæ™‚ã€æœ‰åйã‹ã¤è¨±å¯ã•れãŸã‚¤ãƒ™ãƒ³ãƒˆç¨®åˆ¥ã®ã¿', async () => {
+ const webhook1 = await createWebhook({
+ isActive: true,
+ on: ['abuseReport'],
+ });
+ const webhook2 = await createWebhook({
+ isActive: true,
+ on: ['abuseReportResolved'],
+ });
+ const webhook3 = await createWebhook({
+ isActive: false,
+ on: ['abuseReport'],
+ });
+ const webhook4 = await createWebhook({
+ isActive: false,
+ on: ['abuseReportResolved'],
+ });
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any);
+
+ expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1);
+ });
+
+ test('除外指定ã—ãŸå ´åˆã¯é€ä¿¡ã•れãªã„', async () => {
+ const webhook1 = await createWebhook({
+ isActive: true,
+ on: ['abuseReport'],
+ });
+ const webhook2 = await createWebhook({
+ isActive: true,
+ on: ['abuseReport'],
+ });
+
+ await service.enqueueSystemWebhook('abuseReport', { foo: 'bar' } as any, { excludes: [webhook2.id] });
+
+ expect(queueService.systemWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.systemWebhookDeliver.mock.calls[0][0] as MiSystemWebhook).toEqual(webhook1);
+ });
});
describe('fetchActiveSystemWebhooks', () => {
diff --git a/packages/backend/test/unit/UserWebhookService.ts b/packages/backend/test/unit/UserWebhookService.ts
index 0e88835a02..db8f96df28 100644
--- a/packages/backend/test/unit/UserWebhookService.ts
+++ b/packages/backend/test/unit/UserWebhookService.ts
@@ -1,4 +1,3 @@
-
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
@@ -71,7 +70,7 @@ describe('UserWebhookService', () => {
LoggerService,
GlobalEventService,
{
- provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn() }),
+ provide: QueueService, useFactory: () => ({ userWebhookDeliver: jest.fn() }),
},
],
})
@@ -242,4 +241,92 @@ describe('UserWebhookService', () => {
});
});
});
+
+ describe('アプリを毎回作り直ã™å¿…è¦ãŒã‚るグループ', () => {
+ beforeEach(async () => {
+ await beforeAllImpl();
+ await beforeEachImpl();
+ });
+
+ afterEach(async () => {
+ await afterEachImpl();
+ await afterAllImpl();
+ });
+
+ describe('enqueueUserWebhook', () => {
+ test('キューã«è¿½åŠ æˆåŠŸ', async () => {
+ const webhook = await createWebhook({
+ active: true,
+ on: ['note'],
+ });
+ await service.enqueueUserWebhook(webhook.userId, 'note', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.userWebhookDeliver.mock.calls[0][0] as MiWebhook).toEqual(webhook);
+ });
+
+ test('éžã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªWebhookã¯ã‚­ãƒ¥ãƒ¼ã«è¿½åŠ ã•れãªã„', async () => {
+ const webhook = await createWebhook({
+ active: false,
+ on: ['note'],
+ });
+ await service.enqueueUserWebhook(webhook.userId, 'note', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
+ });
+
+ test('未許å¯ã®ã‚¤ãƒ™ãƒ³ãƒˆç¨®åˆ¥ãŒæ¸¡ã•れãŸå ´åˆã¯Webhookã¯ã‚­ãƒ¥ãƒ¼ã«è¿½åŠ ã•れãªã„', async () => {
+ const webhook1 = await createWebhook({
+ active: true,
+ on: [],
+ });
+ const webhook2 = await createWebhook({
+ active: true,
+ on: ['note'],
+ });
+ await service.enqueueUserWebhook(webhook1.userId, 'renote', { foo: 'bar' } as any);
+ await service.enqueueUserWebhook(webhook2.userId, 'renote', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
+ });
+
+ test('ユーザIDãŒç•°ãªã‚‹Webhookã¯ã‚­ãƒ¥ãƒ¼ã«è¿½åŠ ã•れãªã„', async () => {
+ const webhook = await createWebhook({
+ active: true,
+ on: ['note'],
+ });
+ await service.enqueueUserWebhook(idService.gen(), 'note', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).not.toHaveBeenCalled();
+ });
+
+ test('混在ã—ãŸæ™‚ã€æœ‰åйã‹ã¤è¨±å¯ã•れãŸã‚¤ãƒ™ãƒ³ãƒˆç¨®åˆ¥ã®ã¿', async () => {
+ const userId = root.id;
+ const webhook1 = await createWebhook({
+ userId,
+ active: true,
+ on: ['note'],
+ });
+ const webhook2 = await createWebhook({
+ userId,
+ active: true,
+ on: ['renote'],
+ });
+ const webhook3 = await createWebhook({
+ userId,
+ active: false,
+ on: ['note'],
+ });
+ const webhook4 = await createWebhook({
+ userId,
+ active: false,
+ on: ['renote'],
+ });
+ await service.enqueueUserWebhook(userId, 'note', { foo: 'bar' } as any);
+
+ expect(queueService.userWebhookDeliver).toHaveBeenCalledTimes(1);
+ expect(queueService.userWebhookDeliver.mock.calls[0][0] as MiWebhook).toEqual(webhook1);
+ });
+ });
+ });
});
diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
index 1506283a3c..d96e6b916a 100644
--- a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
+++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -18,6 +18,7 @@ import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
import { EmailService } from '@/core/EmailService.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
+import { SystemWebhookEventType } from '@/models/SystemWebhook.js';
const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0));
@@ -334,9 +335,10 @@ describe('CheckModeratorsActivityProcessorService', () => {
mockModeratorRole([user1]);
await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 });
- expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(2);
- expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook1);
- expect(systemWebhookService.enqueueSystemWebhook.mock.calls[1][0]).toEqual(systemWebhook2);
+ // typeã¨activeã«ã‚ˆã‚‹çµžã‚Šè¾¼ã¿ãŒæ©Ÿèƒ½ã—ã¦ã„ã‚‹ã‹ã¯SystemWebhookServiceã®ãƒ†ã‚¹ãƒˆã§ç¢ºèªã™ã‚‹.
+ // ã“ã“ã§ã¯å‘¼ã³å‡ºã•れã¦ã„ã‚‹ã‹ã€typeãŒæ­£ã—ã„ã‹ã®ã¿ã‚’確èªã™ã‚‹
+ expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
+ expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0] as SystemWebhookEventType).toEqual('inactiveModeratorsWarning');
});
});
@@ -372,8 +374,10 @@ describe('CheckModeratorsActivityProcessorService', () => {
mockModeratorRole([user1]);
await service.notifyChangeToInvitationOnly();
+ // typeã¨activeã«ã‚ˆã‚‹çµžã‚Šè¾¼ã¿ãŒæ©Ÿèƒ½ã—ã¦ã„ã‚‹ã‹ã¯SystemWebhookServiceã®ãƒ†ã‚¹ãƒˆã§ç¢ºèªã™ã‚‹.
+ // ã“ã“ã§ã¯å‘¼ã³å‡ºã•れã¦ã„ã‚‹ã‹ã€typeãŒæ­£ã—ã„ã‹ã®ã¿ã‚’確èªã™ã‚‹
expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
- expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook2);
+ expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0] as SystemWebhookEventType).toEqual('inactiveModeratorsInvitationOnlyChanged');
});
});
});
diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json
index 528faf0a85..163e6096f8 100644
--- a/packages/frontend-embed/package.json
+++ b/packages/frontend-embed/package.json
@@ -4,7 +4,6 @@
"type": "module",
"scripts": {
"watch": "vite",
- "dev": "vite --config vite.config.local-dev.ts --debug hmr",
"build": "vite build",
"typecheck": "vue-tsc --noEmit",
"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
@@ -25,7 +24,7 @@
"estree-walker": "3.0.3",
"misskey-js": "workspace:*",
"frontend-shared": "workspace:*",
- "punycode": "2.3.1",
+ "punycode.js": "2.3.1",
"rollup": "4.26.0",
"sass": "1.79.4",
"shiki": "1.22.2",
@@ -44,7 +43,7 @@
"@types/estree": "1.0.6",
"@types/micromatch": "4.0.9",
"@types/node": "22.9.0",
- "@types/punycode": "2.1.4",
+ "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6",
"@types/uuid": "10.0.0",
"@types/ws": "8.5.13",
diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts
index 71a3156311..c5072caba4 100644
--- a/packages/frontend-embed/src/boot.ts
+++ b/packages/frontend-embed/src/boot.ts
@@ -15,11 +15,11 @@ import { applyTheme, assertIsTheme } from '@/theme.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
import { DI } from '@/di.js';
import { serverMetadata } from '@/server-metadata.js';
-import { url } from '@@/js/config.js';
+import { url, version, locale, lang, updateLocale } from '@@/js/config.js';
import { parseEmbedParams } from '@@/js/embed-page.js';
import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
import { serverContext } from '@/server-context.js';
-import { i18n } from '@/i18n.js';
+import { i18n, updateI18n } from '@/i18n.js';
import type { Theme } from '@/theme.js';
@@ -69,6 +69,22 @@ if (embedParams.colorMode === 'dark') {
}
//#endregion
+//#region Detect language & fetch translations
+const localeVersion = localStorage.getItem('localeVersion');
+const localeOutdated = (localeVersion == null || localeVersion !== version || locale == null);
+if (localeOutdated) {
+ const res = await window.fetch(`/assets/locales/${lang}.${version}.json`);
+ if (res.status === 200) {
+ const newLocale = await res.text();
+ const parsedNewLocale = JSON.parse(newLocale);
+ localStorage.setItem('locale', newLocale);
+ localStorage.setItem('localeVersion', version);
+ updateLocale(parsedNewLocale);
+ updateI18n(parsedNewLocale);
+ }
+}
+//#endregion
+
// サイズã®åˆ¶é™
document.documentElement.style.maxWidth = '500px';
diff --git a/packages/frontend-embed/src/components/EmAcct.vue b/packages/frontend-embed/src/components/EmAcct.vue
index 6856b8272e..ff794d9b6e 100644
--- a/packages/frontend-embed/src/components/EmAcct.vue
+++ b/packages/frontend-embed/src/components/EmAcct.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { toUnicode } from 'punycode/';
+import { toUnicode } from 'punycode.js';
import { host as hostRaw } from '@@/js/config.js';
defineProps<{
diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue
index a71364237d..b5aaa95894 100644
--- a/packages/frontend-embed/src/components/EmMention.vue
+++ b/packages/frontend-embed/src/components/EmMention.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { toUnicode } from 'punycode';
+import { toUnicode } from 'punycode.js';
import { } from 'vue';
import tinycolor from 'tinycolor2';
import { host as localHost } from '@@/js/config.js';
diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue
index 4211261e19..4e0ae005df 100644
--- a/packages/frontend-embed/src/components/EmNotes.vue
+++ b/packages/frontend-embed/src/components/EmNotes.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #default="{ items: notes }">
<div :class="[$style.root]">
- <EmNote v-for="note in notes" :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/>
+ <EmNote v-for="note in notes" :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note as Misskey.entities.Note"/>
</div>
</template>
</EmPagination>
@@ -24,6 +24,7 @@ import { useTemplateRef } from 'vue';
import EmNote from '@/components/EmNote.vue';
import EmPagination, { Paging } from '@/components/EmPagination.vue';
import { i18n } from '@/i18n.js';
+import * as Misskey from 'misskey-js';
withDefaults(defineProps<{
pagination: Paging;
diff --git a/packages/frontend-embed/src/components/EmUrl.vue b/packages/frontend-embed/src/components/EmUrl.vue
index 94424cab28..2dbbe90858 100644
--- a/packages/frontend-embed/src/components/EmUrl.vue
+++ b/packages/frontend-embed/src/components/EmUrl.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { toUnicode as decodePunycode } from 'punycode/';
+import { toUnicode as decodePunycode } from 'punycode.js';
import EmA from './EmA.vue';
import { url as local } from '@@/js/config.js';
diff --git a/packages/frontend-embed/src/index.html b/packages/frontend-embed/src/index.html
index d94ada5ea8..e69de29bb2 100644
--- a/packages/frontend-embed/src/index.html
+++ b/packages/frontend-embed/src/index.html
@@ -1,38 +0,0 @@
-<!--
- SPDX-FileCopyrightText: syuilo and misskey-project
- SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<!--
- 開発モードã®viteã¯ã“ã®ãƒ•ァイルを起点ã«ã‚µãƒ¼ãƒãƒ¼ã‚’èµ·å‹•ã—ã¾ã™ã€‚
- ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã«æ›¸ã‹ã‚ŒãŸ [t]js ã®ãƒªãƒ³ã‚¯ã¨ (s)cssã®ãƒªãƒ³ã‚¯ã¨ã€ãã®ä¾å­˜é–¢ä¿‚ã«ã‚るファイルã¯ãƒ“ルドã•れã¾ã™
--->
-
-<!DOCTYPE html>
-<html>
-<head>
- <meta charset="UTF-8" />
- <title>[DEV] Loading...</title>
- <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
- <meta
- http-equiv="Content-Security-Policy"
- content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
- worker-src 'self';
- script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh https://cdn.jsdelivr.net;
- style-src 'self' 'unsafe-inline';
- img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
- media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
- connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com;
- frame-src *;"
- />
- <meta property="og:site_name" content="[DEV BUILD] Sharkey" />
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <link rel='stylesheet' href='/assets/phosphor-icons/bold/style.css'>
- <link rel='stylesheet' href='/static-assets/fonts/sharkey-icons/style.css'>
-</head>
-
-<body>
-<div id="sharkey_app"></div>
-<script type="module" src="./boot.ts"></script>
-</body>
-</html>
diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts
index 4664ad4880..680ab80167 100644
--- a/packages/frontend-embed/src/theme.ts
+++ b/packages/frontend-embed/src/theme.ts
@@ -75,16 +75,21 @@ function compile(theme: Theme): Record<string, string> {
return getColor(theme.props[val]);
} else if (val[0] === ':') { // func
const parts = val.split('<');
- const func = parts.shift().substring(1);
- const arg = parseFloat(parts.shift());
- const color = getColor(parts.join('<'));
+ const funcTxt = parts.shift();
+ const argTxt = parts.shift();
- switch (func) {
- case 'darken': return color.darken(arg);
- case 'lighten': return color.lighten(arg);
- case 'alpha': return color.setAlpha(arg);
- case 'hue': return color.spin(arg);
- case 'saturate': return color.saturate(arg);
+ if (funcTxt && argTxt) {
+ const func = funcTxt.substring(1);
+ const arg = parseFloat(argTxt);
+ const color = getColor(parts.join('<'));
+
+ switch (func) {
+ case 'darken': return color.darken(arg);
+ case 'lighten': return color.lighten(arg);
+ case 'alpha': return color.setAlpha(arg);
+ case 'hue': return color.spin(arg);
+ case 'saturate': return color.saturate(arg);
+ }
}
}
diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json
index 1af34f378c..ff04a689bf 100644
--- a/packages/frontend-embed/tsconfig.json
+++ b/packages/frontend-embed/tsconfig.json
@@ -10,8 +10,8 @@
"declaration": false,
"sourceMap": false,
"target": "ES2022",
- "module": "nodenext",
- "moduleResolution": "nodenext",
+ "module": "ES2022",
+ "moduleResolution": "Bundler",
"removeComments": false,
"noLib": false,
"strict": true,
diff --git a/packages/frontend-embed/vite.config.local-dev.ts b/packages/frontend-embed/vite.config.local-dev.ts
deleted file mode 100644
index bf2f478887..0000000000
--- a/packages/frontend-embed/vite.config.local-dev.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import dns from 'dns';
-import { readFile } from 'node:fs/promises';
-import type { IncomingMessage } from 'node:http';
-import { defineConfig } from 'vite';
-import type { UserConfig } from 'vite';
-import * as yaml from 'js-yaml';
-import locales from '../../locales/index.js';
-import { getConfig } from './vite.config.js';
-
-dns.setDefaultResultOrder('ipv4first');
-
-const defaultConfig = getConfig();
-
-const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8'));
-
-const httpUrl = `http://localhost:${port}/`;
-const websocketUrl = `ws://localhost:${port}/`;
-
-// activitypubリクエストã¯Proxyを通ã—ã€ãれ以外ã¯Viteã®é–‹ç™ºã‚µãƒ¼ãƒãƒ¼ã‚’è¿”ã™
-function varyHandler(req: IncomingMessage) {
- if (req.headers.accept?.includes('application/activity+json')) {
- return null;
- }
- return '/index.html';
-}
-
-const devConfig: UserConfig = {
- // 基本ã®è¨­å®šã¯ vite.config.js ã‹ã‚‰å¼•ãç¶™ã
- ...defaultConfig,
- root: 'src',
- publicDir: '../assets',
- base: '/embed',
- server: {
- host: 'localhost',
- port: 5174,
- proxy: {
- '/api': {
- changeOrigin: true,
- target: httpUrl,
- },
- '/assets': httpUrl,
- '/static-assets': httpUrl,
- '/client-assets': httpUrl,
- '/files': httpUrl,
- '/twemoji': httpUrl,
- '/fluent-emoji': httpUrl,
- '/sw.js': httpUrl,
- '/streaming': {
- target: websocketUrl,
- ws: true,
- },
- '/favicon.ico': httpUrl,
- '/robots.txt': httpUrl,
- '/embed.js': httpUrl,
- '/identicon': {
- target: httpUrl,
- rewrite(path) {
- return path.replace('@localhost:5173', '');
- },
- },
- '/url': httpUrl,
- '/proxy': httpUrl,
- '/_info_card_': httpUrl,
- '/bios': httpUrl,
- '/cli': httpUrl,
- '/inbox': httpUrl,
- '/emoji/': httpUrl,
- '/notes': {
- target: httpUrl,
- bypass: varyHandler,
- },
- '/users': {
- target: httpUrl,
- bypass: varyHandler,
- },
- '/.well-known': {
- target: httpUrl,
- },
- },
- },
- build: {
- ...defaultConfig.build,
- rollupOptions: {
- ...defaultConfig.build?.rollupOptions,
- input: 'index.html',
- },
- },
-
- define: {
- ...defaultConfig.define,
- _LANGS_FULL_: JSON.stringify(Object.entries(locales)),
- },
-};
-
-export default defineConfig(({ command, mode }) => devConfig);
-
diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts
index bf6f558893..13f272612c 100644
--- a/packages/frontend-embed/vite.config.ts
+++ b/packages/frontend-embed/vite.config.ts
@@ -2,12 +2,18 @@ import path from 'path';
import pluginVue from '@vitejs/plugin-vue';
import { type UserConfig, defineConfig } from 'vite';
import { localesVersion } from '../../locales/version.js';
+import * as yaml from 'js-yaml';
+import { promises as fsp } from 'fs';
+
import locales from '../../locales/index.js';
import meta from '../../package.json';
import packageInfo from './package.json' with { type: 'json' };
import pluginJson5 from './vite.json5.js';
import { pluginReplaceIcons } from '../frontend/vite.replaceIcons.js';
+const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null;
+const host = url ? (new URL(url)).hostname : undefined;
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
/**
@@ -63,7 +69,14 @@ export function getConfig(): UserConfig {
base: '/embed_vite/',
server: {
+ host,
port: 5174,
+ hmr: {
+ // ãƒãƒƒã‚¯ã‚¨ãƒ³ãƒ‰çµŒç”±ã§ã®èµ·å‹•時ã€Viteã¯5174経由ã§ã‚¢ã‚»ãƒƒãƒˆã‚’å‚ç…§ã—ã¦ã„ã‚‹ã¨æ€ã„込んã§ã„ã‚‹ãŒå®Ÿéš›ã¯3000ã‹ã‚‰é…ä¿¡ã•れる
+ // ãã®ãŸã‚ã€ãƒãƒƒã‚¯ã‚¨ãƒ³ãƒ‰ã®WSサーãƒãƒ¼ã«HMRã®WSリクエストãŒå¸åŽã•れã¦ã—ã¾ã„ã€æ­£ã—ãHMRãŒæ©Ÿèƒ½ã—ãªã„
+ // クライアントå´ã®WSãƒãƒ¼ãƒˆã‚’Viteサーãƒãƒ¼ã®ãƒãƒ¼ãƒˆã«å¼·åˆ¶ã•ã›ã‚‹ã“ã¨ã§ã€æ­£ã—ãHMRãŒæ©Ÿèƒ½ã™ã‚‹ã‚ˆã†ã«ãªã‚‹
+ clientPort: 5174,
+ },
},
plugins: [
diff --git a/packages/frontend-shared/build.js b/packages/frontend-shared/build.js
index 17b6da8d30..9941114757 100644
--- a/packages/frontend-shared/build.js
+++ b/packages/frontend-shared/build.js
@@ -23,10 +23,14 @@ const options = {
sourcemap: 'linked',
};
+const args = process.argv.slice(2).map(arg => arg.toLowerCase());
+
// js-builté…下をã™ã¹ã¦å‰Šé™¤ã™ã‚‹
-fs.rmSync('./js-built', { recursive: true, force: true });
+if (!args.includes('--no-clean')) {
+ fs.rmSync('./js-built', { recursive: true, force: true });
+}
-if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) {
+if (args.includes('--watch')) {
await watchSrc();
} else {
await buildSrc();
diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json
index 8bf25da161..7537974f1d 100644
--- a/packages/frontend-shared/package.json
+++ b/packages/frontend-shared/package.json
@@ -26,6 +26,7 @@
"@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"
},
diff --git a/packages/frontend/.storybook/fake-utils.ts b/packages/frontend/.storybook/fake-utils.ts
new file mode 100644
index 0000000000..c777cbbe72
--- /dev/null
+++ b/packages/frontend/.storybook/fake-utils.ts
@@ -0,0 +1,154 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import seedrandom from 'seedrandom';
+
+/**
+ * AIã§ç”Ÿæˆã—ãŸç„¡ä½œç‚ºãªãƒ•ァーストãƒãƒ¼ãƒ 
+ */
+export const firstNameDict = [
+ 'Ethan', 'Olivia', 'Jackson', 'Emma', 'Liam', 'Ava', 'Aiden', 'Sophia', 'Mason', 'Isabella',
+ 'Noah', 'Mia', 'Lucas', 'Harper', 'Caleb', 'Abigail', 'Samuel', 'Emily', 'Logan',
+ 'Madison', 'Benjamin', 'Chloe', 'Elijah', 'Grace', 'Alexander', 'Scarlett', 'William', 'Zoey', 'James', 'Lily',
+]
+
+/**
+ * AIã§ç”Ÿæˆã—ãŸç„¡ä½œç‚ºãªãƒ©ã‚¹ãƒˆãƒãƒ¼ãƒ 
+ */
+export const lastNameDict = [
+ 'Anderson', 'Johnson', 'Thompson', 'Davis', 'Rodriguez', 'Smith', 'Patel', 'Williams', 'Lee', 'Brown',
+ 'Garcia', 'Jackson', 'Martinez', 'Taylor', 'Harris', 'Nguyen', 'Miller', 'Jones', 'Wilson',
+ 'White', 'Thomas', 'Garcia', 'Martinez', 'Robinson', 'Turner', 'Lewis', 'Hall', 'King', 'Baker', 'Cooper',
+]
+
+/**
+ * AIã§ç”Ÿæˆã—ãŸç„¡ä½œç‚ºãªå›½å
+ */
+export const countryDict = [
+ 'Japan', 'Canada', 'Brazil', 'Australia', 'Italy', 'SouthAfrica', 'Mexico', 'Sweden', 'Russia', 'India',
+ 'Germany', 'Argentina', 'South Korea', 'France', 'Nigeria', 'Turkey', 'Spain', 'Egypt', 'Thailand',
+ 'Vietnam', 'Kenya', 'Saudi Arabia', 'Netherlands', 'Colombia', 'Poland', 'Chile', 'Malaysia', 'Ukraine', 'New Zealand', 'Peru',
+]
+
+export function text(length: number = 10, seed?: string): string {
+ let result = "";
+
+ // シード値を使ã†å ´åˆã€åŒã˜æ•°å€¤ãŒç¾…列ã•れるãŒã€ãƒ©ãƒ³ãƒ€ãƒ æ–‡å­—列ã¨ã„ã†æ„味ã§ã¯æº€ãŸã›ã¦ã„ã‚‹ã¨æ€ã†ã®ã§ã“ã®ã¾ã¾ä½¿ã£ã¦ãŠã
+ const rand = seed ? seedrandom(seed)() : Math.random();
+ while (result.length < length) {
+ result += rand.toString(36).substring(2);
+ }
+
+ return result.substring(0, length);
+}
+
+export function integer(min: number = 0, max: number = 9999, seed?: string): number {
+ const rand = seed ? seedrandom(seed)() : Math.random();
+ return Math.floor(rand * (max - min)) + min;
+}
+
+export function date(params?: {
+ yearMin?: number,
+ yearMax?: number,
+ monthMin?: number,
+ monthMax?: number,
+ dayMin?: number,
+ dayMax?: number,
+ hourMin?: number,
+ hourMax?: number,
+ minuteMin?: number,
+ minuteMax?: number,
+ secondMin?: number,
+ secondMax?: number,
+ millisecondMin?: number,
+ millisecondMax?: number,
+}, seed?: string): Date {
+ const year = integer(params?.yearMin ?? 1970, params?.yearMax ?? (new Date()).getFullYear(), seed);
+ const month = integer(params?.monthMin ?? 1, params?.monthMax ?? 12, seed);
+ let day = integer(params?.dayMin ?? 1, params?.dayMax ?? 31, seed);
+ if (month === 2) {
+ day = Math.min(day, 28);
+ } else if ([4, 6, 9, 11].includes(month)) {
+ day = Math.min(day, 30);
+ } else {
+ day = Math.min(day, 31);
+ }
+
+ const hour = integer(params?.hourMin ?? 0, params?.hourMax ?? 23, seed);
+ const minute = integer(params?.minuteMin ?? 0, params?.minuteMax ?? 59, seed);
+ const second = integer(params?.secondMin ?? 0, params?.secondMax ?? 59, seed);
+ const millisecond = integer(params?.millisecondMin ?? 0, params?.millisecondMax ?? 999, seed);
+
+ return new Date(year, month - 1, day, hour, minute, second, millisecond);
+}
+
+export function boolean(seed?: string): boolean {
+ const rand = seed ? seedrandom(seed)() : Math.random();
+ return rand < 0.5;
+}
+
+export function choose<T>(array: T[], seed?: string): T {
+ const rand = seed ? seedrandom(seed)() : Math.random();
+ return array[Math.floor(rand * array.length)];
+}
+
+export function firstName(seed?: string): string {
+ return choose(firstNameDict, seed);
+}
+
+export function lastName(seed?: string): string {
+ return choose(lastNameDict, seed);
+}
+
+export function country(seed?: string): string {
+ return choose(countryDict, seed);
+}
+
+const TIME2000 = 946684800000;
+export function fakeId(seed?: string): string {
+ let time = new Date().getTime();
+
+ time = time - TIME2000;
+ if (time < 0) time = 0;
+
+ const timeStr = time.toString(36).padStart(8, '0');
+ const noiseStr = text(2, seed);
+
+ return timeStr + noiseStr;
+}
+
+export function imageDataUrl(options?: {
+ size?: {
+ width?: number,
+ height?: number,
+ },
+ color?: {
+ red?: number,
+ green?: number,
+ blue?: number,
+ alpha?: number,
+ }
+}, seed?: string): string {
+ const canvas = document.createElement('canvas');
+ canvas.width = options?.size?.width ?? 100;
+ canvas.height = options?.size?.height ?? 100;
+
+ const ctx = canvas.getContext('2d');
+ if (!ctx) {
+ throw new Error('Failed to get 2d context');
+ }
+
+ ctx.beginPath()
+
+ const red = options?.color?.red ?? integer(0, 255, seed);
+ const green = options?.color?.green ?? integer(0, 255, seed);
+ const blue = options?.color?.blue ?? integer(0, 255, seed);
+ const alpha = options?.color?.alpha ?? 1;
+ ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, Math.PI * 2, true);
+ ctx.fillStyle = `rgba(${red}, ${green}, ${blue}, ${alpha})`;
+ ctx.fill();
+
+ return canvas.toDataURL('image/png', 1.0);
+}
diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts
index 758827c196..377d26d6a3 100644
--- a/packages/frontend/.storybook/fakes.ts
+++ b/packages/frontend/.storybook/fakes.ts
@@ -5,6 +5,7 @@
import { AISCRIPT_VERSION } from '@syuilo/aiscript';
import type { entities } from 'misskey-js'
+import { date, imageDataUrl, text } from "./fake-utils.js";
export function abuseUserReport() {
return {
@@ -302,3 +303,93 @@ export function inviteCode(isUsed = false, hasExpiration = false, isExpired = fa
used: isUsed,
}
}
+
+export function role(params: {
+ id?: string,
+ name?: string,
+ color?: string | null,
+ iconUrl?: string | null,
+ description?: string,
+ isModerator?: boolean,
+ isAdministrator?: boolean,
+ displayOrder?: number,
+ createdAt?: string,
+ updatedAt?: string,
+ target?: 'manual' | 'conditional',
+ isPublic?: boolean,
+ isExplorable?: boolean,
+ asBadge?: boolean,
+ canEditMembersByModerator?: boolean,
+ usersCount?: number,
+}, seed?: string): entities.Role {
+ const prefix = params.displayOrder ? params.displayOrder.toString().padStart(3, '0') + '-' : '';
+ const genId = text(36, seed);
+ const createdAt = params.createdAt ?? date({}, seed).toISOString();
+ const updatedAt = params.updatedAt ?? date({}, seed).toISOString();
+
+ return {
+ id: params.id ?? genId,
+ name: params.name ?? `${prefix}TestRole-${genId}`,
+ color: params.color ?? '#445566',
+ iconUrl: params.iconUrl ?? null,
+ description: params.description ?? '',
+ isModerator: params.isModerator ?? false,
+ isAdministrator: params.isAdministrator ?? false,
+ displayOrder: params.displayOrder ?? 0,
+ createdAt: createdAt,
+ updatedAt: updatedAt,
+ target: params.target ?? 'manual',
+ isPublic: params.isPublic ?? true,
+ isExplorable: params.isExplorable ?? true,
+ asBadge: params.asBadge ?? true,
+ canEditMembersByModerator: params.canEditMembersByModerator ?? false,
+ usersCount: params.usersCount ?? 10,
+ condFormula: {
+ id: '',
+ type: 'or',
+ values: []
+ },
+ policies: {},
+ }
+}
+
+export function emoji(params?: {
+ id?: string,
+ name?: string,
+ host?: string,
+ uri?: string,
+ publicUrl?: string,
+ originalUrl?: string,
+ type?: string,
+ aliases?: string[],
+ category?: string,
+ license?: string,
+ isSensitive?: boolean,
+ localOnly?: boolean,
+ roleIdsThatCanBeUsedThisEmojiAsReaction?: {id:string, name:string}[],
+ updatedAt?: string,
+}, seed?: string): entities.EmojiDetailedAdmin {
+ const _seed = seed ?? (params?.id ?? "DEFAULT_SEED");
+ const id = params?.id ?? text(32, _seed);
+ const name = params?.name ?? text(8, _seed);
+ const updatedAt = params?.updatedAt ?? date({}, _seed).toISOString();
+
+ const image = imageDataUrl({}, _seed)
+
+ return {
+ id: id,
+ name: name,
+ host: params?.host ?? null,
+ uri: params?.uri ?? null,
+ publicUrl: params?.publicUrl ?? image,
+ originalUrl: params?.originalUrl ?? image,
+ type: params?.type ?? 'image/png',
+ aliases: params?.aliases ?? [`alias1-${name}`, `alias2-${name}`],
+ category: params?.category ?? null,
+ license: params?.license ?? null,
+ isSensitive: params?.isSensitive ?? false,
+ localOnly: params?.localOnly ?? false,
+ roleIdsThatCanBeUsedThisEmojiAsReaction: params?.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
+ updatedAt: updatedAt,
+ }
+}
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index f2bdc631d2..8830523810 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -416,6 +416,10 @@ function toStories(component: string): Promise<string> {
glob('src/components/MkUserSetupDialog.*.vue'),
glob('src/components/MkInstanceCardMini.vue'),
glob('src/components/MkInviteCode.vue'),
+ glob('src/components/MkTagItem.vue'),
+ glob('src/components/MkRoleSelectDialog.vue'),
+ glob('src/components/grid/MkGrid.vue'),
+ glob('src/pages/admin/custom-emojis-manager2.vue'),
glob('src/pages/admin/overview.ap-requests.vue'),
glob('src/pages/user/home.vue'),
glob('src/pages/search.vue'),
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 63eaded968..528a1aef34 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -4,7 +4,6 @@
"type": "module",
"scripts": {
"watch": "vite",
- "dev": "vite --config vite.config.local-dev.ts --debug hmr",
"build": "vite build",
"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",
@@ -31,7 +30,7 @@
"@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.0",
"@vue/compiler-sfc": "3.5.12",
- "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11",
+ "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
"astring": "1.9.0",
"broadcast-channel": "7.0.0",
"buraha": "0.0.1",
@@ -59,7 +58,7 @@
"misskey-reversi": "workspace:*",
"moment": "^2.30.1",
"photoswipe": "5.4.4",
- "punycode": "2.3.1",
+ "punycode.js": "2.3.1",
"rollup": "4.26.0",
"sanitize-html": "2.13.1",
"sass": "1.79.3",
@@ -108,7 +107,7 @@
"@types/matter-js": "0.19.7",
"@types/micromatch": "4.0.9",
"@types/node": "22.9.0",
- "@types/punycode": "2.1.4",
+ "@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",
diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts
deleted file mode 100644
index f312765dcf..0000000000
--- a/packages/frontend/src/_dev_boot_.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-await main();
-
-import('@/_boot_.js');
-
-/**
- * backend/src/server/web/boot.jsã§å·®ã—è¾¼ã¾ã‚Œã¦ã„る起動処ç†ã®ã†ã¡ã€æœ€ä½Žé™å¿…è¦ãªã‚‚ã®ã‚’模倣ã™ã‚‹ãŸã‚ã®å‡¦ç†
- */
-async function main() {
- const forceError = localStorage.getItem('forceError');
- if (forceError != null) {
- renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
- }
-
- //#region Detect language & fetch translations
-
- // dev-modeã®å ´åˆã¯å¸¸ã«å–り直ã™
- const supportedLangs = _LANGS_.map(it => it[0]);
- let lang: string | null | undefined = localStorage.getItem('lang');
- if (lang == null || !supportedLangs.includes(lang)) {
- if (supportedLangs.includes(navigator.language)) {
- lang = navigator.language;
- } else {
- lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
-
- // Fallback
- if (lang == null) lang = 'en-US';
- }
- }
-
- // TODO:今ã®ã¾ã¾ã ã¨è¨€èªžãƒ•ァイル変更後ã¯pnpm devをリスタートã™ã‚‹å¿…è¦ãŒã‚ã‚‹ã®ã§ã€chokidarを使ã£ãŸã‚Šç­‰ã§å¯¾å¿œã§ãるよã†ã«ã™ã‚‹
- const locale = _LANGS_FULL_.find(it => it[0] === lang);
- localStorage.setItem('lang', lang);
- localStorage.setItem('locale', JSON.stringify(locale[1]));
- localStorage.setItem('localeVersion', _VERSION_);
- //#endregion
-
- //#region Theme
- const theme = localStorage.getItem('theme');
- if (theme) {
- for (const [k, v] of Object.entries(JSON.parse(theme))) {
- document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
-
- // HTMLã® theme-color é©ç”¨
- if (k === 'htmlThemeColor') {
- for (const tag of document.head.children) {
- if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
- tag.setAttribute('content', v);
- break;
- }
- }
- }
- }
- }
- const colorScheme = localStorage.getItem('colorScheme');
- if (colorScheme) {
- document.documentElement.style.setProperty('color-scheme', colorScheme);
- }
- //#endregion
-
- const fontSize = localStorage.getItem('fontSize');
- if (fontSize) {
- document.documentElement.classList.add('f-' + fontSize);
- }
-
- const useSystemFont = localStorage.getItem('useSystemFont');
- if (useSystemFont) {
- document.documentElement.classList.add('useSystemFont');
- }
-
- const wallpaper = localStorage.getItem('wallpaper');
- if (wallpaper) {
- document.documentElement.style.backgroundImage = `url(${wallpaper})`;
- }
-
- const customCss = localStorage.getItem('customCss');
- if (customCss && customCss.length > 0) {
- const style = document.createElement('style');
- style.innerHTML = customCss;
- document.head.appendChild(style);
- }
-}
-
-function renderError(code: string, details?: string) {
- console.log(code, details);
-}
diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index 366345b5b3..5005b17b3c 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -102,6 +102,9 @@ export async function removeAccount(idOrToken: Account['id']) {
}
function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise<Account> {
+ document.cookie = `token=; path=/; max-age=0${ location.protocol === 'https:' ? '; Secure' : ''}`;
+ document.cookie = `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',
@@ -150,9 +153,9 @@ function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Pr
} else if (res.error.id === 'd5826d14-3982-4d2e-8011-b9e9f02499ef') {
// rate limited
const timeToWait = res.error.info?.resetMs ?? 1000;
- window.setTimeout(timeToWait, () => {
+ window.setTimeout(() => {
fetchAccount(token, id, forceShowDialog).then(done, fail);
- });
+ }, timeToWait);
return;
} else {
await alert({
@@ -221,7 +224,6 @@ export async function login(token: Account['token'], redirect?: string) {
throw reason;
});
miLocalStorage.setItem('account', JSON.stringify(me));
- document.cookie = `token=${token}; path=/; max-age=31536000${ location.protocol === 'https:' ? '; Secure' : ''}`; // bull dashboardã®èªè¨¼ã¨ã‹ã§ä½¿ã†
await addAccount(me.id, token);
if (redirect) {
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index d43a2b0799..46ec4533ec 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -99,6 +99,11 @@ export async function common(createVue: () => App<Element>) {
// タッãƒãƒ‡ãƒã‚¤ã‚¹ã§CSSã®:hoverを機能ã•ã›ã‚‹
document.addEventListener('touchend', () => {}, { passive: true });
+ // URLã«#pswpã‚’å«ã‚€å ´åˆã¯å–り除ã
+ if (location.hash === '#pswp') {
+ history.replaceState(null, '', location.href.replace('#pswp', ''));
+ }
+
// 一斉リロード
reloadChannel.addEventListener('message', path => {
if (path !== null) location.href = path;
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index eb8a4d30d2..6c544feb2a 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -7,6 +7,7 @@ import { createApp, defineAsyncComponent, markRaw } from 'vue';
import { ui } from '@@/js/config.js';
import { common } from './common.js';
import type * as Misskey from 'misskey-js';
+import type { Component } from 'vue';
import { i18n } from '@/i18n.js';
import { alert, confirm, popup, post, toast } from '@/os.js';
import { useStream } from '@/stream.js';
@@ -26,13 +27,38 @@ import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
export async function mainBoot() {
- const { isClientUpdated } = await common(() => createApp(
- new URLSearchParams(window.location.search).has('zen') || (ui === 'deck' && deckStore.state.useSimpleUiForNonRootPages && location.pathname !== '/') ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
- !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
- ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) :
- ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) :
- defineAsyncComponent(() => import('@/ui/universal.vue')),
- ));
+ const { isClientUpdated } = 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 (searchParams.has('ui')) uiStyle = searchParams.get('ui');
+
+ let rootComponent: Component;
+ switch (uiStyle) {
+ case 'zen':
+ rootComponent = defineAsyncComponent(() => import('@/ui/zen.vue'));
+ break;
+ case 'deck':
+ rootComponent = defineAsyncComponent(() => import('@/ui/deck.vue'));
+ break;
+ case 'visitor':
+ rootComponent = defineAsyncComponent(() => import('@/ui/visitor.vue'));
+ break;
+ case 'classic':
+ rootComponent = defineAsyncComponent(() => import('@/ui/classic.vue'));
+ break;
+ default:
+ rootComponent = defineAsyncComponent(() => import('@/ui/universal.vue'));
+ break;
+ }
+
+ return createApp(rootComponent);
+ });
reactionPicker.init();
emojiPicker.init();
diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index c28dbc7ffa..564d1fe7e3 100644
--- a/packages/frontend/src/components/MkAsUi.vue
+++ b/packages/frontend/src/components/MkAsUi.vue
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-if="c.label" #label>{{ c.label }}</template>
<template v-if="c.caption" #caption>{{ c.caption }}</template>
</MkInput>
- <MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :modelValue="c.default ?? null" @update:modelValue="c.onChange">
+ <MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :modelValue="valueForSelect" @update:modelValue="onSelectUpdate">
<template v-if="c.label" #label>{{ c.label }}</template>
<template v-if="c.caption" #caption>{{ c.caption }}</template>
<option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option>
@@ -77,8 +77,8 @@ import MkPostForm from '@/components/MkPostForm.vue';
const props = withDefaults(defineProps<{
component: AsUiComponent;
components: Ref<AsUiComponent>[];
- size: 'small' | 'medium' | 'large';
- align: 'left' | 'center' | 'right';
+ size?: 'small' | 'medium' | 'large';
+ align?: 'left' | 'center' | 'right';
}>(), {
size: 'medium',
align: 'left',
@@ -86,7 +86,7 @@ const props = withDefaults(defineProps<{
const c = props.component;
-function g(id) {
+function g(id: string) {
const v = props.components.find(x => x.value.id === id)?.value;
if (v) return v;
@@ -122,13 +122,22 @@ const containerStyle = computed(() => {
const valueForSwitch = ref('default' in c && typeof c.default === 'boolean' ? c.default : false);
-function onSwitchUpdate(v) {
+function onSwitchUpdate(v: boolean) {
valueForSwitch.value = v;
if ('onChange' in c && c.onChange) {
c.onChange(v as never);
}
}
+const valueForSelect = ref('default' in c && typeof c.default !== 'boolean' ? c.default ?? null : null);
+
+function onSelectUpdate(v) {
+ valueForSelect.value = v;
+ if ('onChange' in c && c.onChange) {
+ c.onChange(v as never);
+ }
+}
+
function openPostForm() {
const form = (c as AsUiPostFormButton).form;
if (!form) return;
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index e9493edbd1..aeed90722f 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -30,6 +30,9 @@ import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmount
import { defaultStore } from '@/store.js';
// APIs provided by Captcha services
+// see: https://docs.hcaptcha.com/configuration/#javascript-api
+// see: https://developers.google.com/recaptcha/docs/display?hl=ja
+// see: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#explicitly-render-the-turnstile-widget
export type Captcha = {
render(container: string | Node, options: {
readonly [_ in 'sitekey' | 'theme' | 'type' | 'size' | 'tabindex' | 'callback' | 'expired' | 'expired-callback' | 'error-callback' | 'endpoint']?: unknown;
@@ -56,6 +59,7 @@ declare global {
const props = defineProps<{
provider: CaptchaProvider;
sitekey: string | null; // null will show error on request
+ secretKey?: string | null;
instanceUrl?: string | null;
modelValue?: string | null;
}>();
@@ -67,7 +71,7 @@ const emit = defineEmits<{
const available = ref(false);
const captchaEl = shallowRef<HTMLDivElement | undefined>();
-
+const captchaWidgetId = ref<string | undefined>(undefined);
const testcaptchaInput = ref('');
const testcaptchaPassed = ref(false);
@@ -99,6 +103,15 @@ const scriptId = computed(() => `script-${props.provider}`);
const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
+watch(() => [props.instanceUrl, props.sitekey, props.secretKey], async () => {
+ // 変更ãŒã‚ã£ãŸã¨ãã¯ãƒªãƒ•レッシュã¨å†ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ã‚’ã—ã¦ãŠã‹ãªã„ã¨ã€å¤‰æ›´å¾Œã®å€¤ã§å†æ¤œè¨¼ãŒå‡ºæ¥ãªã„
+ if (available.value) {
+ callback(undefined);
+ clearWidget();
+ await requestRender();
+ }
+});
+
if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') {
available.value = true;
} else if (src.value !== null) {
@@ -111,14 +124,38 @@ if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha')
}
function reset() {
- if (captcha.value.reset) captcha.value.reset();
+ if (captcha.value.reset && captchaWidgetId.value !== undefined) {
+ try {
+ captcha.value.reset(captchaWidgetId.value);
+ } catch (error: unknown) {
+ // ignore
+ if (_DEV_) console.warn(error);
+ }
+ }
testcaptchaPassed.value = false;
testcaptchaInput.value = '';
}
+function remove() {
+ if (captcha.value.remove && captchaWidgetId.value) {
+ try {
+ if (_DEV_) console.log('remove', props.provider, captchaWidgetId.value);
+ captcha.value.remove(captchaWidgetId.value);
+ } catch (error: unknown) {
+ // ignore
+ if (_DEV_) console.warn(error);
+ }
+ }
+}
+
async function requestRender() {
- if (captcha.value.render && captchaEl.value instanceof Element) {
- captcha.value.render(captchaEl.value, {
+ if (captcha.value.render && captchaEl.value instanceof Element && props.sitekey) {
+ // reCAPTCHAã®ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°é‡è¤‡åˆ¤å®šã‚’回é¿ã™ã‚‹ãŸã‚ã€captchaElé…下ã«ä»®ã®divを用æ„ã™ã‚‹.
+ // (åŒã˜divã«å¯¾ã—ã¦è¤‡æ•°å›žrenderを呼ã³å‡ºã™ã¨reCAPTCHAã¯ã‚¨ãƒ©ãƒ¼ã‚’è¿”ã™ã®ã§ï¼‰
+ const elem = document.createElement('div');
+ captchaEl.value.appendChild(elem);
+
+ captchaWidgetId.value = captcha.value.render(elem, {
sitekey: props.sitekey,
theme: defaultStore.state.darkMode ? 'dark' : 'light',
callback: callback,
@@ -146,6 +183,23 @@ async function requestRender() {
}
}
+function clearWidget() {
+ if (props.provider === 'mcaptcha') {
+ const container = document.getElementById('mcaptcha__widget-container');
+ if (container) {
+ container.innerHTML = '';
+ }
+ } else {
+ reset();
+ remove();
+
+ if (captchaEl.value) {
+ // レンダリング先ã®ã‚³ãƒ³ãƒ†ãƒŠã®ä¸­èº«ã‚’掃除ã—ã€ãƒ•ォームãŒå¢—æ®–ã™ã‚‹ã®ã‚’抑止
+ captchaEl.value.innerHTML = '';
+ }
+ }
+}
+
function callback(response?: string) {
emit('update:modelValue', typeof response === 'string' ? response : null);
}
@@ -178,7 +232,7 @@ onUnmounted(() => {
});
onBeforeUnmount(() => {
- reset();
+ clearWidget();
});
defineExpose({
diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue
index e036fec528..7ff9da1ced 100644
--- a/packages/frontend/src/components/MkChannelPreview.vue
+++ b/packages/frontend/src/components/MkChannelPreview.vue
@@ -125,7 +125,9 @@ const bannerStyle = computed(() => {
position: absolute;
top: 16px;
left: 16px;
+ max-width: calc(100% - 32px);
padding: 12px 16px;
+ box-sizing: border-box;
background: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 1.2em;
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index f4d20c7d8c..30a9b26bef 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<div v-show="showBody" ref="contentEl" :class="[$style.content, { [$style.omitted]: omitted }]">
<slot></slot>
- <button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }">
+ <button v-if="omitted" :class="$style.fade" class="_button" @click="showMore">
<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
</button>
</div>
@@ -48,6 +48,7 @@ const props = withDefaults(defineProps<{
thin?: boolean;
naked?: boolean;
foldable?: boolean;
+ onUnfold?: () => boolean; // return false to prevent unfolding
scrollable?: boolean;
expanded?: boolean;
maxHeight?: number | null;
@@ -101,6 +102,13 @@ const omitObserver = new ResizeObserver((entries, observer) => {
calcOmit();
});
+function showMore() {
+ if (props.onUnfold && !props.onUnfold()) return;
+
+ ignoreOmit.value = true;
+ omitted.value = false;
+}
+
onMounted(() => {
watch(showBody, v => {
if (!rootEl.value) return;
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
index ecbee864dc..e6ab17417d 100644
--- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { defineProps, shallowRef } from 'vue';
+import { shallowRef } from 'vue';
import MkLink from '@/components/MkLink.vue';
import { i18n } from '@/i18n.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue
index 1079e52030..5ba5de0c4a 100644
--- a/packages/frontend/src/components/MkDriveFileThumbnail.vue
+++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue
@@ -5,13 +5,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div
- ref="thumbnail"
- :class="[
- $style.root,
- { [$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive },
- ]"
+ v-panel
+ :class="[$style.root, {
+ [$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive,
+ [$style.large]: large,
+ }]"
>
- <ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/>
+ <ImgWithBlurhash
+ v-if="isThumbnailAvailable"
+ :hash="file.blurhash"
+ :src="file.thumbnailUrl"
+ :alt="file.name"
+ :title="file.name"
+ :cover="fit !== 'contain'"
+ :forceBlurhash="forceBlurhash"
+ />
<i v-else-if="is === 'image'" class="ti ti-photo" :class="$style.icon"></i>
<i v-else-if="is === 'video'" class="ti ti-video" :class="$style.icon"></i>
<i v-else-if="is === 'audio' || is === 'midi'" class="ti ti-file-music" :class="$style.icon"></i>
@@ -34,6 +42,8 @@ const props = defineProps<{
file: Misskey.entities.DriveFile;
fit: 'cover' | 'contain';
highlightWhenSensitive?: boolean;
+ forceBlurhash?: boolean;
+ large?: boolean;
}>();
const is = computed(() => {
@@ -60,7 +70,7 @@ const is = computed(() => {
const isThumbnailAvailable = computed(() => {
return props.file.thumbnailUrl
- ? (is.value === 'image' as const || is.value === 'video')
+ ? (is.value === 'image' || is.value === 'video')
: false;
});
</script>
@@ -101,4 +111,8 @@ const isThumbnailAvailable = computed(() => {
font-size: 32px;
color: #777;
}
+
+.large .icon {
+ font-size: 40px;
+}
</style>
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 0b4114d252..084c81bb52 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<KeepAlive>
<div v-show="opened">
- <MkSpacer v-if="withSpacer" :marginMin="14" :marginMax="22">
+ <MkSpacer v-if="withSpacer" :marginMin="spacerMin" :marginMax="spacerMax">
<slot></slot>
</MkSpacer>
<div v-else>
@@ -64,10 +64,14 @@ const props = withDefaults(defineProps<{
defaultOpen?: boolean;
maxHeight?: number | null;
withSpacer?: boolean;
+ spacerMin?: number;
+ spacerMax?: number;
}>(), {
defaultOpen: false,
maxHeight: null,
withSpacer: true,
+ spacerMin: 14,
+ spacerMax: 22,
});
const rootEl = shallowRef<HTMLElement>();
diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue
index f409f6ce50..96214a9542 100644
--- a/packages/frontend/src/components/MkFormFooter.vue
+++ b/packages/frontend/src/components/MkFormFooter.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.text">{{ i18n.tsx.thereAreNChanges({ n: form.modifiedCount.value }) }}</div>
<div style="margin-left: auto;" class="_buttons">
<MkButton danger rounded @click="form.discard"><i class="ti ti-x"></i> {{ i18n.ts.discard }}</MkButton>
- <MkButton primary rounded @click="form.save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+ <MkButton primary rounded :disabled="!canSaving" @click="form.save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
</div>
</div>
</template>
@@ -18,7 +18,7 @@ import { } from 'vue';
import MkButton from './MkButton.vue';
import { i18n } from '@/i18n.js';
-const props = defineProps<{
+const props = withDefaults(defineProps<{
form: {
modifiedCount: {
value: number;
@@ -26,7 +26,10 @@ const props = defineProps<{
discard: () => void;
save: () => void;
};
-}>();
+ canSaving?: boolean;
+}>(), {
+ canSaving: true,
+});
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index 8ccbf61e48..d8066857fe 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.chart">
<div class="selects">
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
- <optgroup :label="i18n.ts.federation">
+ <optgroup v-if="shouldShowFederation" :label="i18n.ts.federation">
<option value="federation">{{ i18n.ts._charts.federation }}</option>
<option value="ap-request">{{ i18n.ts._charts.apRequest }}</option>
</optgroup>
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<optgroup :label="i18n.ts.notes">
<option value="notes">{{ i18n.ts._charts.notesIncDec }}</option>
<option value="local-notes">{{ i18n.ts._charts.localNotesIncDec }}</option>
- <option value="remote-notes">{{ i18n.ts._charts.remoteNotesIncDec }}</option>
+ <option v-if="shouldShowFederation" value="remote-notes">{{ i18n.ts._charts.remoteNotesIncDec }}</option>
<option value="notes-total">{{ i18n.ts._charts.notesTotal }}</option>
</optgroup>
<optgroup :label="i18n.ts.drive">
@@ -46,9 +46,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0;">
<option value="active-users">Active users</option>
<option value="notes">Notes</option>
- <option value="ap-requests-inbox-received">AP Requests: inboxReceived</option>
- <option value="ap-requests-deliver-succeeded">AP Requests: deliverSucceeded</option>
- <option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
+ <option v-if="shouldShowFederation" value="ap-requests-inbox-received">AP Requests: inboxReceived</option>
+ <option v-if="shouldShowFederation" value="ap-requests-deliver-succeeded">AP Requests: deliverSucceeded</option>
+ <option v-if="shouldShowFederation" value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
</MkSelect>
<div class="_panel" :class="$style.heatmap">
<MkHeatmap :src="heatmapSrc" :label="'Read & Write'"/>
@@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkFoldableSection>
- <MkFoldableSection class="item">
+ <MkFoldableSection v-if="shouldShowFederation" class="item">
<template #header>Federation</template>
<div :class="$style.federation">
<div class="pies">
@@ -84,13 +84,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, shallowRef } from 'vue';
+import { onMounted, ref, computed, shallowRef } from 'vue';
import { Chart } from 'chart.js';
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 * as os from '@/os.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import MkHeatmap, { type HeatmapSource } from '@/components/MkHeatmap.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
@@ -100,6 +102,8 @@ import { initChart } from '@/scripts/init-chart.js';
initChart();
+const shouldShowFederation = computed(() => instance.federation !== 'none' || $i?.isModerator);
+
const chartLimit = 500;
const chartSpan = ref<'hour' | 'day'>('hour');
const chartSrc = ref('active-users');
diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue
index 2a8d5c9f71..9d9cc76822 100644
--- a/packages/frontend/src/components/MkInstanceTicker.vue
+++ b/packages/frontend/src/components/MkInstanceTicker.vue
@@ -4,19 +4,20 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div :class="$style.root" :style="bg">
+<div :class="$style.root" :style="themeColorStyle">
<img v-if="faviconUrl" :class="$style.icon" :src="faviconUrl"/>
- <div :class="$style.name">{{ instance.name }}</div>
+ <div :class="$style.name">{{ instanceName }}</div>
</div>
</template>
<script lang="ts" setup>
-import { computed } from 'vue';
-import { instanceName } from '@@/js/config.js';
-import { instance as Instance } from '@/instance.js';
+import { computed, 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';
const props = defineProps<{
+ host: string | null;
instance?: {
faviconUrl?: string | null
name?: string | null
@@ -25,18 +26,28 @@ const props = defineProps<{
}>();
// if no instance data is given, this is for the local instance
-const instance = props.instance ?? {
- name: instanceName,
- themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content,
-};
+const instanceName = computed(() => props.host == null ? localInstanceName : props.instance?.name ?? props.host);
-const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? '/favicon.ico');
-
-const themeColor = instance.themeColor ?? '#777777';
+const faviconUrl = computed(() => {
+ let imageSrc: string | null = null;
+ if (props.host == null) {
+ if (localInstance.iconUrl == null) {
+ return '/favicon.ico';
+ } else {
+ imageSrc = localInstance.iconUrl;
+ }
+ } else {
+ imageSrc = props.instance?.faviconUrl ?? null;
+ }
+ return getProxiedImageUrlNullable(imageSrc);
+});
-const bg = {
- background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`,
-};
+const themeColorStyle = computed<CSSProperties>(() => {
+ const themeColor = (props.host == null ? localInstance.themeColor : props.instance?.themeColor) ?? '#777777';
+ return {
+ background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`,
+ };
+});
</script>
<style lang="scss" module>
diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue
index f64ca4bc77..ac50d82a63 100644
--- a/packages/frontend/src/components/MkMention.vue
+++ b/packages/frontend/src/components/MkMention.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { toUnicode } from 'punycode';
+import { toUnicode } from 'punycode.js';
import { computed } from 'vue';
import { host as localHost } from '@@/js/config.js';
import { $i } from '@/account.js';
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index c766a33823..a446dad0ab 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -288,20 +288,23 @@ const align = () => {
const onOpened = () => {
emit('opened');
- // NOTE: Chromatic テストã®éš›ã« undefined ã«ãªã‚‹å ´åˆãŒã‚ã‚‹
- if (content.value == null) return;
+ // contentã®å­è¦ç´ ã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ãŸã‚レンダリングã®å®Œäº†ã‚’å¾…ã¤å¿…è¦ãŒã‚る(nextTickãŒå¿…è¦ï¼‰
+ nextTick(() => {
+ // NOTE: Chromatic テストã®éš›ã« undefined ã«ãªã‚‹å ´åˆãŒã‚ã‚‹
+ if (content.value == null) return;
- // モーダルコンテンツã«ãƒžã‚¦ã‚¹ãƒœã‚¿ãƒ³ãŒæŠ¼ã•れã€ã‚³ãƒ³ãƒ†ãƒ³ãƒ„外ã§ãƒžã‚¦ã‚¹ãƒœã‚¿ãƒ³ãŒé›¢ã•れãŸã¨ãã«ãƒ¢ãƒ¼ãƒ€ãƒ«ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã‚¯ãƒªãƒƒã‚¯ã¨åˆ¤å®šã•ã›ãªã„ãŸã‚ã«ãƒžã‚¦ã‚¹ã‚¤ãƒ™ãƒ³ãƒˆã‚’監視ã—フラグ管ç†ã™ã‚‹
- const el = content.value.children[0];
- el.addEventListener('mousedown', ev => {
- contentClicking = true;
- window.addEventListener('mouseup', ev => {
- // click イベントより先㫠mouseup イベントãŒç™ºç”Ÿã™ã‚‹ã‹ã‚‚ã—れãªã„ã®ã§ã¡ã‚‡ã£ã¨å¾…ã¤
- window.setTimeout(() => {
- contentClicking = false;
- }, 100);
- }, { passive: true, once: true });
- }, { passive: true });
+ // モーダルコンテンツã«ãƒžã‚¦ã‚¹ãƒœã‚¿ãƒ³ãŒæŠ¼ã•れã€ã‚³ãƒ³ãƒ†ãƒ³ãƒ„外ã§ãƒžã‚¦ã‚¹ãƒœã‚¿ãƒ³ãŒé›¢ã•れãŸã¨ãã«ãƒ¢ãƒ¼ãƒ€ãƒ«ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã‚¯ãƒªãƒƒã‚¯ã¨åˆ¤å®šã•ã›ãªã„ãŸã‚ã«ãƒžã‚¦ã‚¹ã‚¤ãƒ™ãƒ³ãƒˆã‚’監視ã—フラグ管ç†ã™ã‚‹
+ const el = content.value.children[0];
+ el.addEventListener('mousedown', ev => {
+ contentClicking = true;
+ window.addEventListener('mouseup', ev => {
+ // click イベントより先㫠mouseup イベントãŒç™ºç”Ÿã™ã‚‹ã‹ã‚‚ã—れãªã„ã®ã§ã¡ã‚‡ã£ã¨å¾…ã¤
+ window.setTimeout(() => {
+ contentClicking = false;
+ }, 100);
+ }, { passive: true, once: true });
+ }, { passive: true });
+ });
};
const onClosed = () => {
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 5c6c6f45bb..c5e552b8f0 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
<div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
<MkNoteHeader :note="appearNote" :mini="true" @click.stop/>
- <MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
+ <MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
<div style="container-type: inline-size;">
<bdi>
<p v-if="appearNote.cw != null" :class="$style.cw">
@@ -100,7 +100,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="appearNote.files && appearNote.files.length > 0">
<MkMediaList ref="galleryEl" :mediaList="appearNote.files" @click.stop/>
</div>
- <MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :class="$style.poll" @click.stop/>
+ <MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :author="appearNote.user" :emojiUrls="appearNote.emojis" :class="$style.poll" @click.stop/>
<div v-if="isEnabledUrlPreview">
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :showAsQuote="true" :class="$style.urlPreview" @click.stop/>
</div>
@@ -179,13 +179,23 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkA>
</template>
</I18n>
- <I18n v-else :src="i18n.ts.userSaysSomething" tag="small">
+ <I18n v-else-if="showSoftWordMutedWord !== true" :src="i18n.ts.userSaysSomething" tag="small">
<template #name>
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
<MkUserName :user="appearNote.user"/>
</MkA>
</template>
</I18n>
+ <I18n v-else :src="i18n.ts.userSaysSomethingAbout" tag="small">
+ <template #name>
+ <MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
+ <MkUserName :user="appearNote.user"/>
+ </MkA>
+ </template>
+ <template #word>
+ {{ Array.isArray(muted) ? muted.map(words => Array.isArray(words) ? words.join() : words).slice(0, 3).join(' ') : muted }}
+ </template>
+ </I18n>
</div>
<div v-else>
<!--
@@ -319,6 +329,7 @@ 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 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);
@@ -343,13 +354,18 @@ const renoteTooltip = computeRenoteTooltip(renoted);
/* Overload Functionã«LintãŒå¯¾å¿œã—ã¦ã„ãªã„ã®ã§ã‚³ãƒ¡ãƒ³ãƒˆã‚¢ã‚¦ãƒˆ
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
-function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): Array<string | string[]> | false | 'sensitiveMute';
*/
-function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): Array<string | string[]> | false | 'sensitiveMute' {
if (mutedWords != null) {
- if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
- if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
- if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
+ const result = checkWordMute(noteToCheck, $i, mutedWords);
+ if (Array.isArray(result)) return result;
+
+ const replyResult = noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords);
+ if (Array.isArray(replyResult)) return replyResult;
+
+ const renoteResult = noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords);
+ if (Array.isArray(renoteResult)) return renoteResult;
}
if (checkOnly) return false;
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 93a543dff1..e33c574900 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<img v-for="(role, i) in appearNote.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.noteHeaderBadgeRole" :src="role.iconUrl!"/>
</div>
</div>
- <MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
+ <MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
</div>
</header>
<div :class="$style.noteContent">
@@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="appearNote.files && appearNote.files.length > 0">
<MkMediaList ref="galleryEl" :mediaList="appearNote.files"/>
</div>
- <MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :class="$style.poll"/>
+ <MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :class="$style.poll" :author="appearNote.user" :emojiUrls="appearNote.emojis"/>
<div v-if="isEnabledUrlPreview">
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" :showAsQuote="true" style="margin-top: 6px;"/>
</div>
diff --git a/packages/frontend/src/components/MkNoteMediaGrid.vue b/packages/frontend/src/components/MkNoteMediaGrid.vue
new file mode 100644
index 0000000000..bf105c3c27
--- /dev/null
+++ b/packages/frontend/src/components/MkNoteMediaGrid.vue
@@ -0,0 +1,109 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+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>
+ </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>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { notePage } from '@/filters/note.js';
+import { i18n } from '@/i18n.js';
+import * as Misskey from 'misskey-js';
+import { defaultStore } from '@/store.js';
+import bytes from '@/filters/bytes.js';
+
+import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
+
+defineProps<{
+ note: Misskey.entities.Note;
+ square?: boolean;
+}>();
+
+const showingFiles = ref<Set<string>>(new Set());
+</script>
+
+<style lang="scss" module>
+.square {
+ width: 100%;
+ height: auto;
+ aspect-ratio: 1;
+}
+
+.filePreview {
+ position: relative;
+ height: 128px;
+ border-radius: calc(var(--MI-radius) / 2);
+ overflow: clip;
+
+ &:hover {
+ text-decoration: none;
+ }
+
+ &.square {
+ height: 100%;
+ }
+}
+
+.file {
+ width: 100%;
+ height: 100%;
+ border-radius: calc(var(--MI-radius) / 2);
+}
+
+.sensitive {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: grid;
+ place-items: center;
+ font-size: 0.8em;
+ text-align: center;
+ padding: 8px;
+ box-sizing: border-box;
+ color: #fff;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(5px);
+ cursor: pointer;
+}
+</style>
diff --git a/packages/frontend/src/components/MkPagingButtons.vue b/packages/frontend/src/components/MkPagingButtons.vue
new file mode 100644
index 0000000000..fe59efd83a
--- /dev/null
+++ b/packages/frontend/src/components/MkPagingButtons.vue
@@ -0,0 +1,124 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root">
+ <MkButton primary :disabled="min === current" @click="onToPrevButtonClicked">&lt;</MkButton>
+
+ <div :class="$style.buttons">
+ <div v-if="prevDotVisible" :class="$style.headTailButtons">
+ <MkButton @click="onToHeadButtonClicked">{{ min }}</MkButton>
+ <span class="ti ti-dots"/>
+ </div>
+
+ <MkButton
+ v-for="i in buttonRanges" :key="i"
+ :disabled="current === i"
+ @click="onNumberButtonClicked(i)"
+ >
+ {{ i }}
+ </MkButton>
+
+ <div v-if="nextDotVisible" :class="$style.headTailButtons">
+ <span class="ti ti-dots"/>
+ <MkButton @click="onToTailButtonClicked">{{ max }}</MkButton>
+ </div>
+ </div>
+
+ <MkButton primary :disabled="max === current" @click="onToNextButtonClicked">&gt;</MkButton>
+</div>
+</template>
+
+<script setup lang="ts">
+
+import { computed, toRefs } from 'vue';
+import MkButton from '@/components/MkButton.vue';
+
+const min = 1;
+
+const emit = defineEmits<{
+ (ev: 'pageChanged', pageNumber: number): void;
+}>();
+
+const props = defineProps<{
+ current: number;
+ max: number;
+ buttonCount: number;
+}>();
+
+const { current, max } = toRefs(props);
+
+const buttonCount = computed(() => Math.min(max.value, props.buttonCount));
+const buttonCountHalf = computed(() => Math.floor(buttonCount.value / 2));
+const buttonCountStart = computed(() => Math.min(Math.max(min, current.value - buttonCountHalf.value), max.value - buttonCount.value + 1));
+const buttonRanges = computed(() => Array.from({ length: buttonCount.value }, (_, i) => buttonCountStart.value + i));
+
+const prevDotVisible = computed(() => (current.value - 1 > buttonCountHalf.value) && (max.value > buttonCount.value));
+const nextDotVisible = computed(() => (current.value < max.value - buttonCountHalf.value) && (max.value > buttonCount.value));
+
+if (_DEV_) {
+ console.log('[MkPagingButtons]', current.value, max.value, buttonCount.value, buttonCountHalf.value);
+ console.log('[MkPagingButtons]', current.value < max.value - buttonCountHalf.value);
+ console.log('[MkPagingButtons]', max.value > buttonCount.value);
+}
+
+function onNumberButtonClicked(pageNumber: number) {
+ emit('pageChanged', pageNumber);
+}
+
+function onToHeadButtonClicked() {
+ emit('pageChanged', min);
+}
+
+function onToPrevButtonClicked() {
+ const newPageNumber = current.value <= min ? min : current.value - 1;
+ emit('pageChanged', newPageNumber);
+}
+
+function onToNextButtonClicked() {
+ const newPageNumber = current.value >= max.value ? max.value : current.value + 1;
+ emit('pageChanged', newPageNumber);
+}
+
+function onToTailButtonClicked() {
+ emit('pageChanged', max.value);
+}
+</script>
+
+<style module lang="scss">
+.root {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 24px;
+
+ button {
+ border-radius: 9999px;
+ min-width: 2.5em;
+ min-height: 2.5em;
+ max-width: 2.5em;
+ max-height: 2.5em;
+ padding: 4px;
+ }
+}
+
+.buttons {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+}
+
+.headTailButtons {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+
+ span {
+ font-size: 0.75em;
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index a414676bda..f6218de4c8 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.bg" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
<span :class="$style.fg">
<template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--MI_THEME-accent);"></i></template>
- <Mfm :text="choice.text" :plain="true"/>
+ <Mfm :text="choice.text" :plain="true" :author="author" :emojiUrls="emojiUrls"/>
<span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span>
</span>
</li>
@@ -48,6 +48,8 @@ const props = defineProps<{
poll: NonNullable<Misskey.entities.Note['poll']>;
readOnly?: boolean;
local?: boolean;
+ emojiUrls?: Record<string, string>;
+ author?: Misskey.entities.UserLite;
}>();
const remaining = ref(-1);
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 11ae6dbd6a..41d443a388 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -46,14 +46,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-if="posted"></template>
<template v-else-if="posting"><MkEllipsis/></template>
<template v-else>{{ submitText }}</template>
- <i style="margin-left: 6px;" :class="posted ? 'ti ti-check' : reply ? 'ti ti-arrow-back-up' : renote ? 'ti ti-quote' : 'ti ti-send'"></i>
+ <i style="margin-left: 6px;" :class="posted ? 'ti ti-check' : reply ? 'ti ti-arrow-back-up' : renoteTargetNote ? 'ti ti-quote' : 'ti ti-send'"></i>
</div>
</button>
</div>
</header>
<MkNoteSimple v-if="reply" :class="$style.targetNote" :hideFiles="true" :note="reply"/>
- <MkNoteSimple v-if="renote" :class="$style.targetNote" :hideFiles="true" :note="renote"/>
- <div v-if="quoteId" :class="$style.withQuote"><i class="ti ti-quote"></i> {{ i18n.ts.quoteAttached }}<button @click="quoteId = null"><i class="ti ti-x"></i></button></div>
+ <MkNoteSimple v-if="renoteTargetNote" :class="$style.targetNote" :hideFiles="true" :note="renoteTargetNote"/>
+ <div v-if="quoteId" :class="$style.withQuote"><i class="ti ti-quote"></i> {{ i18n.ts.quoteAttached }}<button @click="quoteId = null; renoteTargetNote = null;"><i class="ti ti-x"></i></button></div>
<div v-if="visibility === 'specified'" :class="$style.toSpecified">
<span style="margin-right: 8px;">{{ i18n.ts.recipient }}</span>
<div :class="$style.visibleUsers">
@@ -106,13 +106,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, toRaw } from 'vue';
+import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, toRaw, type ShallowRef } 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/';
+import { toASCII } from 'punycode.js';
import { host, url } from '@@/js/config.js';
import type { MenuItem } from '@/types/menu.js';
+import type { PostFormProps } from '@/types/post-form.js';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
import MkNotePreview from '@/components/MkNotePreview.vue';
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
@@ -136,7 +137,6 @@ import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
-import type { PostFormProps } from '@/types/post-form.js';
import MkScheduleEditor from '@/components/MkScheduleEditor.vue';
const $i = signinRequired();
@@ -202,12 +202,13 @@ const justEndedComposition = ref(false);
const scheduleNote = ref<{
scheduledAt: number | null;
} | null>(null);
+const renoteTargetNote: ShallowRef<PostFormProps['renote'] | null> = shallowRef(props.renote);
const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
- if (props.renote) {
- key += `renote:${props.renote.id}`;
+ if (renoteTargetNote.value) {
+ key += `renote:${renoteTargetNote.value.id}`;
} else if (props.reply) {
key += `reply:${props.reply.id}`;
} else {
@@ -218,7 +219,7 @@ const draftKey = computed((): string => {
});
const placeholder = computed((): string => {
- if (props.renote) {
+ if (renoteTargetNote.value) {
return i18n.ts._postForm.quotePlaceholder;
} else if (props.reply) {
return i18n.ts._postForm.replyPlaceholder;
@@ -238,7 +239,7 @@ const placeholder = computed((): string => {
});
const submitText = computed((): string => {
- return props.renote
+ return renoteTargetNote.value
? i18n.ts.quote
: props.reply
? i18n.ts.reply
@@ -262,11 +263,12 @@ const canPost = computed((): boolean => {
1 <= textLength.value ||
1 <= files.value.length ||
poll.value != null ||
- props.renote != null ||
+ renoteTargetNote.value != null ||
quoteId.value != null
) &&
(textLength.value <= maxTextLength.value) &&
(cwLength.value <= maxCwLength.value) &&
+ (files.value.length <= 16) &&
(!poll.value || poll.value.choices.length >= 2);
});
@@ -624,7 +626,7 @@ async function onPaste(ev: ClipboardEvent) {
const paste = ev.clipboardData.getData('text');
- if (!props.renote && !quoteId.value && paste.startsWith(url + '/notes/')) {
+ if (!renoteTargetNote.value && !quoteId.value && paste.startsWith(url + '/notes/')) {
ev.preventDefault();
os.confirm({
@@ -840,7 +842,7 @@ async function post(ev?: MouseEvent) {
text: text.value === '' ? null : text.value,
fileIds: files.value.length > 0 ? files.value.map(f => f.id) : undefined,
replyId: props.reply ? props.reply.id : undefined,
- renoteId: props.renote ? props.renote.id : quoteId.value ? quoteId.value : undefined,
+ renoteId: renoteTargetNote.value ? renoteTargetNote.value.id : quoteId.value ? quoteId.value : undefined,
channelId: props.channel ? props.channel.id : undefined,
poll: poll.value,
cw: useCw.value ? cw.value ?? '' : null,
@@ -930,7 +932,7 @@ async function post(ev?: MouseEvent) {
claimAchievement('brainDiver');
}
- if (props.renote && (props.renote.userId === $i.id) && text.length > 0) {
+ if (renoteTargetNote.value && (renoteTargetNote.value.userId === $i.id) && text.length > 0) {
claimAchievement('selfQuote');
}
@@ -1140,7 +1142,7 @@ onMounted(() => {
users.forEach(u => pushVisibleUser(u));
});
}
- quoteId.value = init.renote ? init.renote.id : null;
+ quoteId.value = renoteTargetNote.value ? renoteTargetNote.value.id : null;
reactionAcceptance.value = init.reactionAcceptance;
if (init.isSchedule) {
scheduleNote.value = {
diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue
index 11444d8d78..bab7d22112 100644
--- a/packages/frontend/src/components/MkPostFormAttaches.vue
+++ b/packages/frontend/src/components/MkPostFormAttaches.vue
@@ -22,7 +22,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
</Sortable>
- <p :class="$style.remain">{{ 16 - props.modelValue.length }}/16</p>
+ <p :class="[$style.remain, {
+ [$style.exceeded]: props.modelValue.length > 16,
+ }]">{{ 16 - props.modelValue.length }}/16</p>
</div>
</template>
@@ -239,5 +241,9 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
margin: 0;
padding: 0;
font-size: 90%;
+
+ &.exceeded {
+ color: var(--MI_THEME-error);
+ }
}
</style>
diff --git a/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue b/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue
new file mode 100644
index 0000000000..873b276b3d
--- /dev/null
+++ b/packages/frontend/src/components/MkRemoteEmojiEditDialog.vue
@@ -0,0 +1,132 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkWindow
+ ref="windowEl"
+ :initialWidth="400"
+ :initialHeight="500"
+ :canResize="true"
+ @close="windowEl?.close()"
+ @closed="emit('closed')"
+>
+ <template #header>:{{ name }}:</template>
+
+ <div style="display: flex; flex-direction: column; min-height: 100%;">
+ <MkSpacer :marginMin="20" :marginMax="28" style="flex-grow: 1;">
+ <div class="_gaps_m">
+ <div v-if="imgUrl != null" :class="$style.imgs">
+ <div style="background: #000;" :class="$style.imgContainer">
+ <img :src="imgUrl" :class="$style.img" :alt="name"/>
+ </div>
+ <div style="background: #222;" :class="$style.imgContainer">
+ <img :src="imgUrl" :class="$style.img" :alt="name"/>
+ </div>
+ <div style="background: #ddd;" :class="$style.imgContainer">
+ <img :src="imgUrl" :class="$style.img" :alt="name"/>
+ </div>
+ <div style="background: #fff;" :class="$style.imgContainer">
+ <img :src="imgUrl" :class="$style.img" :alt="name"/>
+ </div>
+ </div>
+
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.id }}</template>
+ <template #value>{{ name }}</template>
+ </MkKeyValue>
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.host }}</template>
+ <template #value>{{ host }}</template>
+ </MkKeyValue>
+ <MkKeyValue>
+ <template #key>{{ i18n.ts.license }}</template>
+ <template #value>{{ license }}</template>
+ </MkKeyValue>
+ </div>
+ </MkSpacer>
+ <div :class="$style.footer">
+ <MkButton primary rounded style="margin: 0 auto;" @click="done">
+ <i class="ti ti-plus"></i> {{ i18n.ts.import }}
+ </MkButton>
+ </div>
+ </div>
+</MkWindow>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
+import MkKeyValue from '@/components/MkKeyValue.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkTextarea from '@/components/MkTextarea.vue';
+import MkWindow from '@/components/MkWindow.vue';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+
+const props = defineProps<{
+ emoji: {
+ id: string,
+ name: string,
+ host: string,
+ license: string | null,
+ url: string
+ },
+}>();
+
+const emit = defineEmits<{
+ // å¿…è¦ãªã‚‰æˆ»ã‚Šå€¤ã‚’増やã™
+ (ev: 'done'): void,
+ (ev: 'closed'): void
+}>();
+
+const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
+
+const name = computed(() => props.emoji.name);
+const host = computed(() => props.emoji.host);
+const license = computed(() => props.emoji.license);
+const imgUrl = computed(() => props.emoji.url);
+
+async function done() {
+ await os.apiWithDialog('admin/emoji/copy', {
+ emojiId: props.emoji.id,
+ });
+
+ emit('done');
+ windowEl.value?.close();
+}
+</script>
+
+<style lang="scss" module>
+.imgs {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.imgContainer {
+ padding: 8px;
+ border-radius: 6px;
+}
+
+.img {
+ display: block;
+ height: 64px;
+ width: 64px;
+ object-fit: contain;
+}
+
+.footer {
+ position: sticky;
+ z-index: 10000;
+ bottom: 0;
+ left: 0;
+ padding: 12px;
+ border-top: solid 0.5px var(--MI_THEME-divider);
+ background: var(--MI_THEME-acrylicBg);
+ -webkit-backdrop-filter: var(--MI-blur, blur(15px));
+ backdrop-filter: var(--MI-blur, blur(15px));
+}
+</style>
diff --git a/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts
new file mode 100644
index 0000000000..411d62edf9
--- /dev/null
+++ b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts
@@ -0,0 +1,106 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { http, HttpResponse } from 'msw';
+import { role } from '../../.storybook/fakes.js';
+import { commonHandlers } from '../../.storybook/mocks.js';
+import MkRoleSelectDialog from '@/components/MkRoleSelectDialog.vue';
+
+const roles = [
+ role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'),
+ role({ displayOrder: 2 }, '2'), role({ displayOrder: 2 }, '2'), role({ displayOrder: 3 }, '3'), role({ displayOrder: 3 }, '3'),
+ role({ displayOrder: 4 }, '4'), role({ displayOrder: 5 }, '5'), role({ displayOrder: 6 }, '6'), role({ displayOrder: 7 }, '7'),
+ role({ displayOrder: 999, name: 'privateRole', isPublic: false }, '999'),
+];
+
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkRoleSelectDialog,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '<MkRoleSelectDialog v-bind="props" />',
+ };
+ },
+ args: {
+ initialRoleIds: undefined,
+ infoMessage: undefined,
+ title: undefined,
+ publicOnly: true,
+ },
+ parameters: {
+ layout: 'centered',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ http.post('/api/admin/roles/list', ({ params }) => {
+ return HttpResponse.json(roles);
+ }),
+ ],
+ },
+ },
+ decorators: [() => ({
+ template: '<div style="width:100cqmin"><story/></div>',
+ })],
+} satisfies StoryObj<typeof MkRoleSelectDialog>;
+
+export const InitialIds = {
+ ...Default,
+ args: {
+ ...Default.args,
+ initialRoleIds: [roles[0].id, roles[1].id, roles[4].id, roles[6].id, roles[8].id, roles[10].id],
+ },
+} satisfies StoryObj<typeof MkRoleSelectDialog>;
+
+export const InfoMessage = {
+ ...Default,
+ args: {
+ ...Default.args,
+ infoMessage: 'This is a message.',
+ },
+} satisfies StoryObj<typeof MkRoleSelectDialog>;
+
+export const Title = {
+ ...Default,
+ args: {
+ ...Default.args,
+ title: 'Select roles',
+ },
+} satisfies StoryObj<typeof MkRoleSelectDialog>;
+
+export const Full = {
+ ...Default,
+ args: {
+ ...Default.args,
+ initialRoleIds: roles.map(it => it.id),
+ infoMessage: InfoMessage.args.infoMessage,
+ title: Title.args.title,
+ },
+} satisfies StoryObj<typeof MkRoleSelectDialog>;
+
+export const FullWithPrivate = {
+ ...Default,
+ args: {
+ ...Default.args,
+ initialRoleIds: roles.map(it => it.id),
+ infoMessage: InfoMessage.args.infoMessage,
+ title: Title.args.title,
+ publicOnly: false,
+ },
+} satisfies StoryObj<typeof MkRoleSelectDialog>;
diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue
new file mode 100644
index 0000000000..32f35ed5ad
--- /dev/null
+++ b/packages/frontend/src/components/MkRoleSelectDialog.vue
@@ -0,0 +1,200 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkModalWindow
+ ref="windowEl"
+ :withOkButton="false"
+ :okButtonDisabled="false"
+ :width="400"
+ :height="500"
+ @close="onCloseModalWindow"
+ @closed="console.log('MkRoleSelectDialog: closed') ; $emit('dispose')"
+>
+ <template #header>{{ title }}</template>
+ <MkSpacer :marginMin="20" :marginMax="28">
+ <MkLoading v-if="fetching"/>
+ <div v-else class="_gaps" :class="$style.root">
+ <div :class="$style.header">
+ <MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+ </div>
+
+ <div v-if="selectedRoles.length > 0" class="_gaps" :class="$style.roleItemArea">
+ <div v-for="role in selectedRoles" :key="role.id" :class="$style.roleItem">
+ <MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
+ <button class="_button" :class="$style.roleUnAssign" @click="removeRole(role.id)"><i class="ti ti-x"></i></button>
+ </div>
+ </div>
+ <div v-else :class="$style.roleItemArea" style="text-align: center">
+ {{ i18n.ts._roleSelectDialog.notSelected }}
+ </div>
+
+ <MkInfo v-if="infoMessage">{{ infoMessage }}</MkInfo>
+
+ <div :class="$style.buttons">
+ <MkButton primary @click="onOkClicked">{{ i18n.ts.ok }}</MkButton>
+ <MkButton @click="onCancelClicked">{{ i18n.ts.cancel }}</MkButton>
+ </div>
+ </div>
+ </MkSpacer>
+</MkModalWindow>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, toRefs } from 'vue';
+import * as Misskey from 'misskey-js';
+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 * as os from '@/os.js';
+import MkSpacer from '@/components/global/MkSpacer.vue';
+import MkModalWindow from '@/components/MkModalWindow.vue';
+import MkLoading from '@/components/global/MkLoading.vue';
+
+const emit = defineEmits<{
+ (ev: 'done', value: Misskey.entities.Role[]),
+ (ev: 'close'),
+ (ev: 'dispose'),
+}>();
+
+const props = withDefaults(defineProps<{
+ initialRoleIds?: string[],
+ infoMessage?: string,
+ title?: string,
+ publicOnly: boolean,
+}>(), {
+ initialRoleIds: undefined,
+ infoMessage: undefined,
+ title: undefined,
+ publicOnly: true,
+});
+
+const { initialRoleIds, infoMessage, title, publicOnly } = toRefs(props);
+
+const windowEl = ref<InstanceType<typeof MkModalWindow>>();
+const roles = ref<Misskey.entities.Role[]>([]);
+const selectedRoleIds = ref<string[]>(initialRoleIds.value ?? []);
+const fetching = ref(false);
+
+const selectedRoles = computed(() => {
+ const r = roles.value.filter(role => selectedRoleIds.value.includes(role.id));
+ r.sort((a, b) => {
+ if (a.displayOrder !== b.displayOrder) {
+ return b.displayOrder - a.displayOrder;
+ }
+
+ return a.id.localeCompare(b.id);
+ });
+ return r;
+});
+
+async function fetchRoles() {
+ fetching.value = true;
+ const result = await misskeyApi('admin/roles/list', {});
+ roles.value = result.filter(it => publicOnly.value ? it.isPublic : true);
+ fetching.value = false;
+}
+
+async function addRole() {
+ const items = roles.value
+ .filter(r => r.isPublic)
+ .filter(r => !selectedRoleIds.value.includes(r.id))
+ .map(r => ({ text: r.name, value: r }));
+
+ const { canceled, result: role } = await os.select({ items });
+ if (canceled) {
+ return;
+ }
+
+ selectedRoleIds.value.push(role.id);
+}
+
+async function removeRole(roleId: string) {
+ selectedRoleIds.value = selectedRoleIds.value.filter(x => x !== roleId);
+}
+
+function onOkClicked() {
+ emit('done', selectedRoles.value);
+ windowEl.value?.close();
+}
+
+function onCancelClicked() {
+ emit('close');
+ windowEl.value?.close();
+}
+
+function onCloseModalWindow() {
+ emit('close');
+ windowEl.value?.close();
+}
+
+fetchRoles();
+</script>
+
+<style module lang="scss">
+.root {
+ max-height: 410px;
+ height: 410px;
+ display: flex;
+ flex-direction: column;
+}
+
+.roleItemArea {
+ background-color: var(--MI_THEME-acrylicBg);
+ border-radius: var(--MI-radius);
+ padding: 12px;
+ overflow-y: auto;
+}
+
+.roleItem {
+ display: flex;
+}
+
+.role {
+ flex: 1;
+}
+
+.roleUnAssign {
+ width: 32px;
+ height: 32px;
+ margin-left: 8px;
+ align-self: center;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+.title {
+ flex: 1;
+}
+
+.addRoleButton {
+ min-width: 32px;
+ min-height: 32px;
+ max-width: 32px;
+ max-height: 32px;
+ margin-left: 8px;
+ align-self: center;
+ padding: 0;
+}
+
+.buttons {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ margin-top: auto;
+}
+
+.divider {
+ border-top: solid 0.5px var(--MI_THEME-divider);
+}
+
+</style>
diff --git a/packages/frontend/src/components/MkSignin.input.vue b/packages/frontend/src/components/MkSignin.input.vue
index 34c22abc31..e98ac9cfd2 100644
--- a/packages/frontend/src/components/MkSignin.input.vue
+++ b/packages/frontend/src/components/MkSignin.input.vue
@@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { ref } from 'vue';
-import { toUnicode } from 'punycode/';
+import { toUnicode } from 'punycode.js';
import { query, extractDomain } from '@@/js/url.js';
import { host as configHost } from '@@/js/config.js';
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index e636712389..3560bebace 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -85,7 +85,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
-import { toUnicode } from 'punycode/';
+import { toUnicode } from 'punycode.js';
import * as Misskey from 'misskey-js';
import * as config from '@@/js/config.js';
import MkButton from './MkButton.vue';
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue
index 06481b808c..d1685c6990 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.vue
+++ b/packages/frontend/src/components/MkSignupDialog.rules.vue
@@ -10,8 +10,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<MkSpacer :marginMin="20" :marginMax="28">
<div class="_gaps_m">
- <div v-if="instance.disableRegistration">
- <MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
+ <div v-if="instance.disableRegistration || instance.federation !== 'all'" class="_gaps_s">
+ <MkInfo v-if="instance.disableRegistration" warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
+ <MkInfo v-if="instance.federation === 'specified'" warn>{{ i18n.ts.federationSpecified }}</MkInfo>
+ <MkInfo v-else-if="instance.federation === 'none'" warn>{{ i18n.ts.federationDisabled }}</MkInfo>
</div>
<div style="text-align: center;">
diff --git a/packages/frontend/src/components/MkSortOrderEditor.define.ts b/packages/frontend/src/components/MkSortOrderEditor.define.ts
new file mode 100644
index 0000000000..f023b5d72b
--- /dev/null
+++ b/packages/frontend/src/components/MkSortOrderEditor.define.ts
@@ -0,0 +1,11 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+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
new file mode 100644
index 0000000000..9decacc5f5
--- /dev/null
+++ b/packages/frontend/src/components/MkSortOrderEditor.vue
@@ -0,0 +1,118 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.sortOrderArea">
+ <div :class="$style.sortOrderAreaTags">
+ <MkTagItem
+ v-for="order in currentOrders"
+ :key="order.key"
+ :iconClass="order.direction === '+' ? 'ti ti-arrow-up' : 'ti ti-arrow-down'"
+ :exButtonIconClass="'ti ti-x'"
+ :content="order.key"
+ :class="$style.sortOrderTag"
+ @click="onToggleSortOrderButtonClicked(order)"
+ @exButtonClick="onRemoveSortOrderButtonClicked(order)"
+ />
+ </div>
+ <MkButton :class="$style.sortOrderAddButton" @click="onAddSortOrderButtonClicked">
+ <span class="ti ti-plus"></span>
+ </MkButton>
+</div>
+</template>
+
+<script setup lang="ts" generic="T extends string">
+import { toRefs } from 'vue';
+import MkTagItem from '@/components/MkTagItem.vue';
+import MkButton from '@/components/MkButton.vue';
+import { MenuItem } from '@/types/menu.js';
+import * as os from '@/os.js';
+import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
+
+const emit = defineEmits<{
+ (ev: 'update', sortOrders: SortOrder<T>[]): void;
+}>();
+
+const props = defineProps<{
+ baseOrderKeyNames: T[];
+ currentOrders: SortOrder<T>[];
+}>();
+
+const { currentOrders } = toRefs(props);
+
+function onToggleSortOrderButtonClicked(order: SortOrder<T>) {
+ switch (order.direction) {
+ case '+':
+ order.direction = '-';
+ break;
+ case '-':
+ order.direction = '+';
+ break;
+ }
+
+ emitOrder(currentOrders.value);
+}
+
+function onAddSortOrderButtonClicked(ev: MouseEvent) {
+ const menuItems: MenuItem[] = props.baseOrderKeyNames
+ .filter(baseKey => !currentOrders.value.map(it => it.key).includes(baseKey))
+ .map(it => {
+ return {
+ text: it,
+ action: () => {
+ emitOrder([...currentOrders.value, { key: it, direction: '+' }]);
+ },
+ };
+ });
+ os.contextMenu(menuItems, ev);
+}
+
+function onRemoveSortOrderButtonClicked(order: SortOrder<T>) {
+ emitOrder(currentOrders.value.filter(it => it.key !== order.key));
+}
+
+function emitOrder(sortOrders: SortOrder<T>[]) {
+ emit('update', sortOrders);
+}
+
+</script>
+
+<style module lang="scss">
+.sortOrderArea {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: flex-start;
+}
+
+.sortOrderAreaTags {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.sortOrderAddButton {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+ min-width: 2.0em;
+ min-height: 2.0em;
+ max-width: 2.0em;
+ max-height: 2.0em;
+ padding: 8px;
+ margin-left: auto;
+ border-radius: 9999px;
+ background-color: var(--MI_THEME-buttonBg);
+}
+
+.sortOrderTag {
+ user-select: none;
+ cursor: pointer;
+}
+</style>
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index a32fd53c51..145de3b9d3 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</details>
<details v-if="note.poll">
<summary>{{ i18n.ts.poll }}</summary>
- <MkPoll :noteId="note.id" :poll="note.poll"/>
+ <MkPoll :noteId="note.id" :poll="note.poll" :author="note.user" :emojiUrls="note.emojis"/>
</details>
<button v-if="isLong && collapsed" :class="$style.fade" class="_button" @click.stop="collapsed = false">
<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
@@ -42,11 +42,11 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, computed, watch } from 'vue';
import * as Misskey from 'misskey-js';
import * as mfm from '@transfem-org/sfm-js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
import MkMediaList from '@/components/MkMediaList.vue';
import MkPoll from '@/components/MkPoll.vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { shouldCollapsed } from '@@/js/collapsed.js';
import { defaultStore } from '@/store.js';
import { useRouter } from '@/router/supplier.js';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index c9c173aa35..56e8fcfa37 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -47,7 +47,7 @@ export type SuperMenuDef = {
active?: boolean;
action: (ev: MouseEvent) => void;
} | {
- type: 'link';
+ type?: 'link';
to: string;
icon?: string;
text: string;
diff --git a/packages/frontend/src/components/MkTagItem.stories.impl.ts b/packages/frontend/src/components/MkTagItem.stories.impl.ts
new file mode 100644
index 0000000000..3f243ff651
--- /dev/null
+++ b/packages/frontend/src/components/MkTagItem.stories.impl.ts
@@ -0,0 +1,70 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/* 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 MkTagItem from './MkTagItem.vue';
+
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkTagItem: MkTagItem,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ events() {
+ return {
+ click: action('click'),
+ exButtonClick: action('exButtonClick'),
+ };
+ },
+ },
+ template: '<MkTagItem v-bind="props" v-on="events"></MkTagItem>',
+ };
+ },
+ args: {
+ content: 'name',
+ },
+ parameters: {
+ layout: 'centered',
+ },
+} satisfies StoryObj<typeof MkTagItem>;
+
+export const Icon = {
+ ...Default,
+ args: {
+ ...Default.args,
+ iconClass: 'ti ti-arrow-up',
+ },
+} satisfies StoryObj<typeof MkTagItem>;
+
+export const ExButton = {
+ ...Default,
+ args: {
+ ...Default.args,
+ exButtonIconClass: 'ti ti-x',
+ },
+} satisfies StoryObj<typeof MkTagItem>;
+
+export const IconExButton = {
+ ...Default,
+ args: {
+ ...Default.args,
+ iconClass: 'ti ti-arrow-up',
+ exButtonIconClass: 'ti ti-x',
+ },
+} satisfies StoryObj<typeof MkTagItem>;
diff --git a/packages/frontend/src/components/MkTagItem.vue b/packages/frontend/src/components/MkTagItem.vue
new file mode 100644
index 0000000000..8b7460f3a3
--- /dev/null
+++ b/packages/frontend/src/components/MkTagItem.vue
@@ -0,0 +1,76 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root" @click="(ev) => emit('click', ev)">
+ <span v-if="iconClass" :class="[$style.icon, iconClass]"></span>
+ <span :class="$style.content">{{ content }}</span>
+ <MkButton v-if="exButtonIconClass" :class="$style.exButton" @click="(ev) => emit('exButtonClick', ev)">
+ <span :class="[$style.exButtonIcon, exButtonIconClass]"></span>
+ </MkButton>
+</div>
+</template>
+
+<script setup lang="ts">
+import MkButton from '@/components/MkButton.vue';
+
+const emit = defineEmits<{
+ (ev: 'click', payload: MouseEvent): void;
+ (ev: 'exButtonClick', payload: MouseEvent): void;
+}>();
+
+defineProps<{
+ iconClass?: string;
+ content: string;
+ exButtonIconClass?: string
+}>();
+</script>
+
+<style module lang="scss">
+$buttonSize : 1.8em;
+
+.root {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 9999px;
+ padding: 4px 6px;
+ gap: 3px;
+
+ background-color: var(--MI_THEME-buttonBg);
+
+ &:hover {
+ background-color: var(--MI_THEME-buttonHoverBg);
+ }
+}
+
+.icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.70em;
+}
+
+.exButton {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 9999px;
+ max-height: $buttonSize;
+ max-width: $buttonSize;
+ min-height: $buttonSize;
+ min-width: $buttonSize;
+ padding: 0;
+ box-sizing: border-box;
+ font-size: 0.65em;
+}
+
+.exButtonIcon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.80em;
+}
+</style>
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index 85d4666172..420146f80a 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header>{{ i18n.ts.selectUser }}</template>
<div>
<div :class="$style.form">
- <MkInput v-if="localOnly" v-model="username" :autofocus="true" @update:modelValue="search">
+ <MkInput v-if="computedLocalOnly" v-model="username" :autofocus="true" @update:modelValue="search">
<template #label>{{ i18n.ts.username }}</template>
<template #prefix>@</template>
</MkInput>
@@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, shallowRef } from 'vue';
+import { onMounted, ref, computed, shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from '@/components/MkInput.vue';
import FormSplit from '@/components/form/split.vue';
@@ -70,6 +70,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
+import { instance } from '@/instance.js';
import { host as currentHost, hostname } from '@@/js/config.js';
const emit = defineEmits<{
@@ -86,6 +87,8 @@ const props = withDefaults(defineProps<{
localOnly: false,
});
+const computedLocalOnly = computed(() => props.localOnly || instance.federation === 'none');
+
const username = ref('');
const host = ref('');
const users = ref<Misskey.entities.UserLite[]>([]);
@@ -101,7 +104,7 @@ function search() {
misskeyApi('users/search-by-username-and-host', {
username: username.value,
- host: props.localOnly ? '.' : host.value,
+ host: computedLocalOnly.value ? '.' : host.value,
limit: 10,
detail: false,
}).then(_users => {
@@ -143,7 +146,7 @@ onMounted(() => {
}).then(foundUsers => {
let _users = foundUsers;
_users = _users.filter((u) => {
- if (props.localOnly) {
+ if (computedLocalOnly.value) {
return u.host == null;
} else {
return true;
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index 54f2ee655c..6d2a44e985 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -18,8 +18,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="sanitizeHtml(instance.description) || i18n.ts.headlineMisskey"></div>
</div>
- <div v-if="instance.disableRegistration" :class="$style.mainWarn">
- <MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
+ <div v-if="instance.disableRegistration || instance.federation !== 'all'" :class="$style.mainWarn" class="_gaps_s">
+ <MkInfo v-if="instance.disableRegistration" warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
+ <MkInfo v-if="instance.federation === 'specified'" warn>{{ i18n.ts.federationSpecified }}</MkInfo>
+ <MkInfo v-else-if="instance.federation === 'none'" warn>{{ i18n.ts.federationDisabled }}</MkInfo>
</div>
<div v-if="instance.approvalRequiredForSignup" :class="$style.mainWarn">
<MkInfo warn>{{ i18n.ts.approvalRequiredToRegister }}</MkInfo>
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index b987283a65..3446e3d6e2 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<header :class="$style.editHeader">
<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--MI-margin)" data-cy-widget-select>
<template #label>{{ i18n.ts.selectWidget }}</template>
- <option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
+ <option v-for="widget in _widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
</MkSelect>
<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
<MkButton inline @click="emit('exit')">{{ i18n.ts.close }}</MkButton>
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</Sortable>
</template>
- <component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
+ <component :is="`widget-${widget.name}`" v-for="widget in _widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
</div>
</template>
@@ -50,13 +50,14 @@ export type DefaultStoredWidget = {
</script>
<script lang="ts" setup>
-import { defineAsyncComponent, ref } from 'vue';
+import { defineAsyncComponent, ref, computed } from 'vue';
import { v4 as uuid } from 'uuid';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
-import { widgets as widgetDefs } from '@/widgets/index.js';
+import { widgets as widgetDefs, federationWidgets } from '@/widgets/index.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
+import { instance } from '@/instance.js';
import { isLink } from '@@/js/is-link.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@@ -66,6 +67,16 @@ const props = defineProps<{
edit: boolean;
}>();
+const _widgetDefs = computed(() => {
+ if (instance.federation === 'none') {
+ return widgetDefs.filter(x => !federationWidgets.includes(x));
+ } else {
+ return widgetDefs;
+ }
+});
+
+const _widgets = computed(() => props.widgets.filter(x => _widgetDefs.value.includes(x.name)));
+
const emit = defineEmits<{
(ev: 'updateWidgets', widgets: Widget[]): void;
(ev: 'addWidget', widget: Widget): void;
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index 9a1ac3aca2..2f4141b901 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
-import { toUnicode } from 'punycode/';
+import { toUnicode } from 'punycode.js';
import { host as hostRaw } from '@@/js/config.js';
import { defaultStore } from '@/store.js';
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 18c97b1bdb..1a424f349f 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -57,13 +57,16 @@ 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 { PageHeaderItem } from '@/types/page-header.js';
+import type { PageHeaderItem } from '@/types/page-header.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
const props = withDefaults(defineProps<{
+ overridePageMetadata?: PageMetadata;
tabs?: Tab[];
tab?: string;
actions?: PageHeaderItem[] | null;
thin?: boolean;
+ hideTitle?: boolean;
displayMyAvatar?: boolean;
displayBackButton?: boolean;
}>(), {
@@ -76,9 +79,10 @@ const emit = defineEmits<{
const displayBackButton = props.displayBackButton && history.state.key !== 'index' && history.length > 1 && inject('shouldBackButton', true);
-const pageMetadata = injectReactiveMetadata();
+const injectedPageMetadata = injectReactiveMetadata();
+const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);
-const hideTitle = inject('shouldOmitHeaderTitle', false);
+const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props.hideTitle);
const thin_ = props.thin || inject('shouldHeaderThin', false);
const el = shallowRef<HTMLElement | undefined>(undefined);
@@ -87,7 +91,7 @@ const narrow = ref(false);
const hasTabs = computed(() => props.tabs.length > 0);
const hasActions = computed(() => props.actions && props.actions.length > 0);
const show = computed(() => {
- return !hideTitle || hasTabs.value || hasActions.value;
+ return !hideTitle.value || hasTabs.value || hasActions.value;
});
const preventDrag = (ev: TouchEvent) => {
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 8cca47c1db..5196a63635 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue';
-import { toUnicode as decodePunycode } from 'punycode/';
+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';
diff --git a/packages/frontend/src/components/grid/MkCellTooltip.vue b/packages/frontend/src/components/grid/MkCellTooltip.vue
new file mode 100644
index 0000000000..fd289c6cd9
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkCellTooltip.vue
@@ -0,0 +1,35 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="250" @closed="emit('closed')">
+ <div :class="$style.root">
+ {{ content }}
+ </div>
+</MkTooltip>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import MkTooltip from '@/components/MkTooltip.vue';
+
+defineProps<{
+ showing: boolean;
+ content: string;
+ targetElement: HTMLElement;
+}>();
+
+const emit = defineEmits<{
+ (ev: 'closed'): void;
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+ font-size: 0.9em;
+ text-align: left;
+ text-wrap: normal;
+}
+</style>
diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue
new file mode 100644
index 0000000000..e473b7c1af
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkDataCell.vue
@@ -0,0 +1,418 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+ v-if="cell.row.using"
+ ref="rootEl"
+ class="mk_grid_td"
+ :class="$style.cell"
+ :style="{ maxWidth: cellWidth, minWidth: cellWidth }"
+ :tabindex="-1"
+ data-grid-cell
+ :data-grid-cell-row="cell.row.index"
+ :data-grid-cell-col="cell.column.index"
+ @keydown="onCellKeyDown"
+ @dblclick.prevent="onCellDoubleClick"
+>
+ <div
+ :class="[
+ $style.root,
+ [(cell.violation.valid || cell.selected) ? {} : $style.error],
+ [cell.selected ? $style.selected : {}],
+ // 行ãŒé¸æŠžã•れã¦ã„ã‚‹ã¨ãã¯ç¯„å›²é¸æŠžè‰²ã®é©ç”¨ã‚’行å´ã«ä»»ã›ã‚‹
+ [(cell.ranged && !cell.row.ranged) ? $style.ranged : {}],
+ [needsContentCentering ? $style.center : {}],
+ ]"
+ >
+ <div v-if="!editing" :class="[$style.contentArea]" :style="cellType === 'boolean' ? 'justify-content: center' : ''">
+ <div ref="contentAreaEl" :class="$style.content">
+ <div v-if="cellType === 'text'">
+ {{ cell.value }}
+ </div>
+ <div v-if="cellType === 'number'">
+ {{ cell.value }}
+ </div>
+ <div v-if="cellType === 'date'">
+ {{ 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>
+ <div v-else-if="cellType === 'image'">
+ <img
+ :src="cell.value"
+ :alt="cell.value"
+ :class="$style.viewImage"
+ @load="emitContentSizeChanged"
+ />
+ </div>
+ </div>
+ </div>
+ <div v-else ref="inputAreaEl" :class="$style.inputArea">
+ <input
+ v-if="cellType === 'text'"
+ type="text"
+ :class="$style.editingInput"
+ :value="editingValue"
+ @input="onInputText"
+ @mousedown.stop
+ @contextmenu.stop
+ />
+ <input
+ v-if="cellType === 'number'"
+ type="number"
+ :class="$style.editingInput"
+ :value="editingValue"
+ @input="onInputText"
+ @mousedown.stop
+ @contextmenu.stop
+ />
+ <input
+ v-if="cellType === 'date'"
+ type="date"
+ :class="$style.editingInput"
+ :value="editingValue"
+ @input="onInputText"
+ @mousedown.stop
+ @contextmenu.stop
+ />
+ </div>
+ </div>
+</div>
+</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 * 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;
+ (ev: 'operation:endEdit', sender: GridCell): void;
+ (ev: 'change:value', sender: GridCell, newValue: CellValue): void;
+ (ev: 'change:contentSize', sender: GridCell, newSize: Size): void;
+}>();
+const props = defineProps<{
+ cell: GridCell,
+ rowSetting: GridRowSetting,
+ bus: GridEventEmitter,
+}>();
+
+const { cell, bus } = toRefs(props);
+
+const rootEl = shallowRef<InstanceType<typeof HTMLTableCellElement>>();
+const contentAreaEl = shallowRef<InstanceType<typeof HTMLDivElement>>();
+const inputAreaEl = shallowRef<InstanceType<typeof HTMLDivElement>>();
+
+/** 値ãŒç·¨é›†ä¸­ã‹ã©ã†ã‹ */
+const editing = ref<boolean>(false);
+/** 編集中ã®å€¤. {@link beginEditing}ã¨{@link endEditing}内ã€ãŠã‚ˆã³å„inputã‚¿ã‚°ã‚„ãã®ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯ã‹ã‚‰ã®æ“作ã®ã¿ã‚’想定ã™ã‚‹ */
+const editingValue = ref<CellValue>(undefined);
+
+const cellWidth = computed(() => cell.value.column.width);
+const cellType = computed(() => cell.value.column.setting.type);
+const needsContentCentering = computed(() => {
+ switch (cellType.value) {
+ case 'boolean':
+ return true;
+ default:
+ return false;
+ }
+});
+
+watch(() => [cell.value.value], () => {
+ // 中身ãŒã‚»ãƒƒãƒˆã•れãŸç›´å¾Œã¯ã‚µã‚¤ã‚ºãŒåˆ†ã‹ã‚‰ãªã„ã®ã§ã€æ¬¡ã®ã‚¿ã‚¤ãƒŸãƒ³ã‚°ã§æ›´æ–°ã™ã‚‹
+ nextTick(emitContentSizeChanged);
+}, { immediate: true });
+
+watch(() => cell.value.selected, () => {
+ if (cell.value.selected) {
+ requestFocus();
+ }
+});
+
+function onCellDoubleClick(ev: MouseEvent) {
+ switch (ev.type) {
+ case 'dblclick': {
+ beginEditing(ev.target as HTMLElement);
+ break;
+ }
+ }
+}
+
+function onOutsideMouseDown(ev: MouseEvent) {
+ const isOutside = ev.target instanceof Node && !rootEl.value?.contains(ev.target);
+ if (isOutside || !equalCellAddress(cell.value.address, getCellAddress(ev.target as HTMLElement))) {
+ endEditing(true, false);
+ }
+}
+
+function onCellKeyDown(ev: KeyboardEvent) {
+ if (!editing.value) {
+ ev.preventDefault();
+ switch (ev.code) {
+ case 'NumpadEnter':
+ case 'Enter':
+ case 'F2': {
+ beginEditing(ev.target as HTMLElement);
+ break;
+ }
+ }
+ } else {
+ switch (ev.code) {
+ case 'Escape': {
+ endEditing(false, true);
+ break;
+ }
+ case 'NumpadEnter':
+ case 'Enter': {
+ if (!ev.isComposing) {
+ endEditing(true, true);
+ }
+ }
+ }
+ }
+}
+
+function onInputText(ev: Event) {
+ editingValue.value = (ev.target as HTMLInputElement).value;
+}
+
+function onForceRefreshContentSize() {
+ emitContentSizeChanged();
+}
+
+function registerOutsideMouseDown() {
+ unregisterOutsideMouseDown();
+ addEventListener('mousedown', onOutsideMouseDown);
+}
+
+function unregisterOutsideMouseDown() {
+ removeEventListener('mousedown', onOutsideMouseDown);
+}
+
+async function beginEditing(target: HTMLElement) {
+ if (editing.value || !cell.value.selected || !cell.value.column.setting.editable) {
+ return;
+ }
+
+ if (cell.value.column.setting.customValueEditor) {
+ emit('operation:beginEdit', cell.value);
+ const newValue = await cell.value.column.setting.customValueEditor(
+ cell.value.row,
+ cell.value.column,
+ cell.value.value,
+ target,
+ );
+ emit('operation:endEdit', cell.value);
+
+ if (newValue !== cell.value.value) {
+ emitValueChange(newValue);
+ }
+
+ requestFocus();
+ } else {
+ switch (cellType.value) {
+ case 'number':
+ case 'date':
+ case 'text': {
+ editingValue.value = cell.value.value;
+ editing.value = true;
+ registerOutsideMouseDown();
+ emit('operation:beginEdit', cell.value);
+
+ await nextTick(() => {
+ // inputã®å±•開後ã«ãƒ•ォーカスを当ã¦ãŸã„
+ if (inputAreaEl.value) {
+ (inputAreaEl.value.querySelector('*') as HTMLElement).focus();
+ }
+ });
+ break;
+ }
+ case 'boolean': {
+ // ã¨ãã«ç‰¹æ®ŠãªUIã¯è¨­ã‘ãšã€ãƒˆã‚°ãƒ«ã™ã‚‹ã ã‘
+ emitValueChange(!cell.value.value);
+ break;
+ }
+ }
+ }
+}
+
+function endEditing(applyValue: boolean, requireFocus: boolean) {
+ if (!editing.value) {
+ return;
+ }
+
+ const newValue = editingValue.value;
+ editingValue.value = undefined;
+
+ emit('operation:endEdit', cell.value);
+ unregisterOutsideMouseDown();
+
+ if (applyValue && newValue !== cell.value.value) {
+ emitValueChange(newValue);
+ }
+
+ editing.value = false;
+
+ if (requireFocus) {
+ requestFocus();
+ }
+}
+
+function requestFocus() {
+ nextTick(() => {
+ rootEl.value?.focus();
+ });
+}
+
+function emitValueChange(newValue: CellValue) {
+ const _cell = cell.value;
+ emit('change:value', _cell, newValue);
+}
+
+function emitContentSizeChanged() {
+ emit('change:contentSize', cell.value, {
+ width: contentAreaEl.value?.clientWidth ?? 0,
+ height: contentAreaEl.value?.clientHeight ?? 0,
+ });
+}
+
+useTooltip(rootEl, (showing) => {
+ if (cell.value.violation.valid) {
+ return;
+ }
+
+ const content = cell.value.violation.violations.filter(it => !it.valid).map(it => it.result.message).join('\n');
+ const result = os.popup(defineAsyncComponent(() => import('@/components/grid/MkCellTooltip.vue')), {
+ showing,
+ content,
+ targetElement: rootEl.value!,
+ }, {
+ closed: () => {
+ result.dispose();
+ },
+ });
+});
+
+onMounted(() => {
+ bus.value.on('forceRefreshContentSize', onForceRefreshContentSize);
+});
+
+onUnmounted(() => {
+ bus.value.off('forceRefreshContentSize', onForceRefreshContentSize);
+});
+
+</script>
+
+<style module lang="scss">
+$cellHeight: 28px;
+
+.cell {
+ overflow: hidden;
+ white-space: nowrap;
+ height: $cellHeight;
+ max-height: $cellHeight;
+ min-height: $cellHeight;
+ cursor: cell;
+
+ &:focus {
+ outline: none;
+ }
+}
+
+.root {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ box-sizing: border-box;
+ height: 100%;
+
+ // selectedé©ç”¨æ™‚ã«ä¸­èº«ãŒã‚ºãƒ¬ã¦ã—ã¾ã†ã®ã§ã€é€æ˜Žã®ç·šã‚’ã‚らã‹ã˜ã‚引ã„ã¦ãŠããŸã„
+ border: solid 0.5px transparent;
+
+ &.selected {
+ border: solid 0.5px var(--MI_THEME-accentLighten);
+ }
+
+ &.ranged {
+ background-color: var(--MI_THEME-accentedBg);
+ }
+
+ &.center {
+ justify-content: center;
+ }
+
+ &.error {
+ border: solid 0.5px var(--MI_THEME-error);
+ }
+}
+
+.contentArea, .inputArea {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ max-width: 100%;
+}
+
+.content {
+ display: inline-block;
+ padding: 0 8px;
+}
+
+.viewImage {
+ width: auto;
+ max-height: $cellHeight;
+ height: $cellHeight;
+ object-fit: cover;
+}
+
+.bool {
+ position: relative;
+ width: 18px;
+ height: 18px;
+ background: var(--MI_THEME-panel);
+ border: solid 2px var(--MI_THEME-divider);
+ border-radius: 4px;
+ box-sizing: border-box;
+
+ &.boolTrue {
+ border-color: var(--MI_THEME-accent);
+ background: var(--MI_THEME-accent);
+
+ &::before {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ color: var(--MI_THEME-fgOnAccent);
+ font-size: 12px;
+ line-height: 18px;
+ }
+ }
+}
+
+.editingInput {
+ padding: 0 8px;
+ width: 100%;
+ max-width: 100%;
+ box-sizing: border-box;
+ min-height: $cellHeight - 2;
+ max-height: $cellHeight - 2;
+ height: $cellHeight - 2;
+ outline: none;
+ border: none;
+ font-family: 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif;
+}
+
+</style>
diff --git a/packages/frontend/src/components/grid/MkDataRow.vue b/packages/frontend/src/components/grid/MkDataRow.vue
new file mode 100644
index 0000000000..280a14bc4a
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkDataRow.vue
@@ -0,0 +1,72 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+ class="mk_grid_tr"
+ :class="[
+ $style.row,
+ row.ranged ? $style.ranged : {},
+ ...(row.additionalStyles ?? []).map(it => it.className ?? {}),
+ ]"
+ :style="[
+ ...(row.additionalStyles ?? []).map(it => it.style ?? {}),
+ ]"
+ :data-grid-row="row.index"
+>
+ <MkNumberCell
+ v-if="setting.showNumber"
+ :content="(row.index + 1).toString()"
+ :row="row"
+ />
+ <MkDataCell
+ v-for="cell in cells"
+ :key="cell.address.col"
+ :vIf="cell.column.setting.type !== 'hidden'"
+ :cell="cell"
+ :rowSetting="setting"
+ :bus="bus"
+ @operation:beginEdit="(sender) => emit('operation:beginEdit', sender)"
+ @operation:endEdit="(sender) => emit('operation:endEdit', sender)"
+ @change:value="(sender, newValue) => emit('change:value', sender, newValue)"
+ @change:contentSize="(sender, newSize) => emit('change:contentSize', sender, newSize)"
+ />
+</div>
+</template>
+
+<script setup lang="ts">
+import { GridEventEmitter, Size } 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';
+
+const emit = defineEmits<{
+ (ev: 'operation:beginEdit', sender: GridCell): void;
+ (ev: 'operation:endEdit', sender: GridCell): void;
+ (ev: 'change:value', sender: GridCell, newValue: CellValue): void;
+ (ev: 'change:contentSize', sender: GridCell, newSize: Size): void;
+}>();
+defineProps<{
+ row: GridRow,
+ cells: GridCell[],
+ setting: GridRowSetting,
+ bus: GridEventEmitter,
+}>();
+
+</script>
+
+<style module lang="scss">
+.row {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ width: fit-content;
+
+ &.ranged {
+ background-color: var(--MI_THEME-accentedBg);
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/grid/MkGrid.stories.impl.ts b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts
new file mode 100644
index 0000000000..5801012f15
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts
@@ -0,0 +1,223 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
+import { 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';
+
+function d(p: {
+ check?: boolean,
+ name?: string,
+ email?: string,
+ age?: number,
+ birthday?: string,
+ gender?: string,
+ country?: string,
+ reportCount?: number,
+ createdAt?: string,
+}, seed: string) {
+ const prefix = text(10, seed);
+
+ return {
+ check: p.check ?? boolean(seed),
+ name: p.name ?? `${firstName(seed)} ${lastName(seed)}`,
+ email: p.email ?? `${prefix}@example.com`,
+ age: p.age ?? integer(20, 80, seed),
+ birthday: date({}, seed).toISOString(),
+ gender: p.gender ?? choose(['male', 'female', 'other', 'unknown'], seed),
+ country: p.country ?? country(seed),
+ reportCount: p.reportCount ?? integer(0, 9999, seed),
+ createdAt: p.createdAt ?? date({}, seed).toISOString(),
+ };
+}
+
+const defaultCols: GridColumnSetting[] = [
+ { bindTo: 'check', icon: 'ti-check', type: 'boolean', width: 50 },
+ { bindTo: 'name', title: 'Name', type: 'text', width: 'auto' },
+ { bindTo: 'email', title: 'Email', type: 'text', width: 'auto' },
+ { bindTo: 'age', title: 'Age', type: 'number', width: 50 },
+ { bindTo: 'birthday', title: 'Birthday', type: 'date', width: 'auto' },
+ { bindTo: 'gender', title: 'Gender', type: 'text', width: 80 },
+ { bindTo: 'country', title: 'Country', type: 'text', width: 120 },
+ { bindTo: 'reportCount', title: 'ReportCount', type: 'number', width: 'auto' },
+ { bindTo: 'createdAt', title: 'CreatedAt', type: 'date', width: 'auto' },
+];
+
+function createArgs(overrides?: { settings?: Partial<GridSetting>, data?: DataSource[] }) {
+ const refData = ref<ReturnType<typeof d>[]>([]);
+ for (let i = 0; i < 100; i++) {
+ refData.value.push(d({}, i.toString()));
+ }
+
+ return {
+ settings: {
+ row: overrides?.settings?.row,
+ cols: [
+ ...defaultCols.filter(col => overrides?.settings?.cols?.every(c => c.bindTo !== col.bindTo) ?? true),
+ ...overrides?.settings?.cols ?? [],
+ ],
+ cells: overrides?.settings?.cells,
+ },
+ data: refData.value,
+ };
+}
+
+function createRender(params: { settings: GridSetting, data: DataSource[] }) {
+ return {
+ render(args) {
+ return {
+ components: {
+ MkGrid,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ data() {
+ return {
+ data: args.data,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...args,
+ };
+ },
+ events() {
+ return {
+ event: (event: GridEvent, context: GridContext) => {
+ switch (event.type) {
+ case 'cell-value-change': {
+ args.data[event.row.index][event.column.setting.bindTo] = event.newValue;
+ }
+ }
+ },
+ };
+ },
+ },
+ template: '<div style="padding:20px"><MkGrid v-bind="props" v-on="events" /></div>',
+ };
+ },
+ args: {
+ ...params,
+ },
+ parameters: {
+ layout: 'fullscreen',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ ],
+ },
+ },
+ } satisfies StoryObj<typeof MkGrid>;
+}
+
+export const Default = createRender(createArgs());
+
+export const NoNumber = createRender(createArgs({
+ settings: {
+ row: {
+ showNumber: false,
+ },
+ },
+}));
+
+export const NoSelectable = createRender(createArgs({
+ settings: {
+ row: {
+ selectable: false,
+ },
+ },
+}));
+
+export const Editable = createRender(createArgs({
+ settings: {
+ cols: defaultCols.map(col => ({ ...col, editable: true })),
+ },
+}));
+
+export const AdditionalRowStyle = createRender(createArgs({
+ settings: {
+ cols: defaultCols.map(col => ({ ...col, editable: true })),
+ row: {
+ styleRules: [
+ {
+ condition: ({ row }) => AdditionalRowStyle.args.data[row.index].check as boolean,
+ applyStyle: {
+ style: {
+ backgroundColor: 'lightgray',
+ },
+ },
+ },
+ ],
+ },
+ },
+}));
+
+export const ContextMenu = createRender(createArgs({
+ settings: {
+ cols: [
+ {
+ bindTo: 'check', icon: 'ti-check', type: 'boolean', width: 50, contextMenuFactory: (col, context) => [
+ {
+ type: 'button',
+ text: 'Check All',
+ action: () => {
+ for (const d of ContextMenu.args.data) {
+ d.check = true;
+ }
+ },
+ },
+ {
+ type: 'button',
+ text: 'Uncheck All',
+ action: () => {
+ for (const d of ContextMenu.args.data) {
+ d.check = false;
+ }
+ },
+ },
+ ],
+ },
+ ],
+ row: {
+ contextMenuFactory: (row, context) => [
+ {
+ type: 'button',
+ text: 'Delete',
+ action: () => {
+ const idxes = context.rangedRows.map(r => r.index);
+ const newData = ContextMenu.args.data.filter((d, i) => !idxes.includes(i));
+
+ ContextMenu.args.data.splice(0);
+ ContextMenu.args.data.push(...newData);
+ },
+ },
+ ],
+ },
+ cells: {
+ contextMenuFactory: (col, row, value, context) => [
+ {
+ type: 'button',
+ text: 'Delete',
+ action: () => {
+ for (const cell of context.rangedCells) {
+ ContextMenu.args.data[cell.row.index][cell.column.setting.bindTo] = undefined;
+ }
+ },
+ },
+ ],
+ },
+ },
+}));
diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue
new file mode 100644
index 0000000000..4dbd4ebcae
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkGrid.vue
@@ -0,0 +1,1374 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+ ref="rootEl"
+ class="mk_grid_border"
+ :class="[$style.grid, {
+ [$style.noOverflowHandling]: rootSetting.noOverflowStyle,
+ 'mk_grid_root_rounded': rootSetting.rounded,
+ 'mk_grid_root_border': rootSetting.outerBorder,
+ }]"
+ @mousedown.prevent="onMouseDown"
+ @keydown="onKeyDown"
+ @contextmenu.prevent.stop="onContextMenu"
+>
+ <div class="mk_grid_thead">
+ <MkHeaderRow
+ :columns="columns"
+ :gridSetting="rowSetting"
+ :bus="bus"
+ @operation:beginWidthChange="onHeaderCellWidthBeginChange"
+ @operation:endWidthChange="onHeaderCellWidthEndChange"
+ @operation:widthLargest="onHeaderCellWidthLargest"
+ @change:width="onHeaderCellChangeWidth"
+ @change:contentSize="onHeaderCellChangeContentSize"
+ />
+ </div>
+ <div class="mk_grid_tbody">
+ <MkDataRow
+ v-for="row in rows"
+ v-show="row.using"
+ :key="row.index"
+ :row="row"
+ :cells="cells[row.index].cells"
+ :setting="rowSetting"
+ :bus="bus"
+ :using="row.using"
+ :class="[lastLine === row.index ? 'last_row' : '']"
+ @operation:beginEdit="onCellEditBegin"
+ @operation:endEdit="onCellEditEnd"
+ @change:value="onChangeCellValue"
+ @change:contentSize="onChangeCellContentSize"
+ />
+ </div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref, toRefs, watch } from 'vue';
+import { DataSource, GridEventEmitter, GridSetting, GridState, Size } 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 {
+ copyGridDataToClipboard,
+ equalCellAddress,
+ getCellAddress,
+ getCellElement,
+ 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';
+
+type RowHolder = {
+ row: GridRow,
+ cells: GridCell[],
+ origin: DataSource,
+}
+
+const emit = defineEmits<{
+ (ev: 'event', event: GridEvent, context: GridContext): void;
+}>();
+
+const props = defineProps<{
+ settings: GridSetting;
+ data: DataSource[];
+}>();
+
+const rootSetting: Required<GridSetting['root']> = {
+ noOverflowStyle: false,
+ rounded: true,
+ outerBorder: true,
+ ...props.settings.root,
+};
+
+// non-reactive
+// eslint-disable-next-line vue/no-setup-props-reactivity-loss
+const rowSetting: Required<GridRowSetting> = {
+ ...defaultGridRowSetting,
+ ...props.settings.row,
+};
+
+// non-reactive
+// eslint-disable-next-line vue/no-setup-props-reactivity-loss
+const columnSettings = props.settings.cols;
+
+// non-reactive
+const cellSettings = props.settings.cells ?? {};
+
+const { data } = toRefs(props);
+
+// #region Event Definitions
+// region Event Definitions
+
+/**
+ * grid -> å„å­ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã®ã‚¤ãƒ™ãƒ³ãƒˆçµŒè·¯ã‚’æ‹…ã†{@link GridEventEmitter}。ãŠã‚‚ã«propsã§ã®ä¼æ¬ãŒé›£ã—ã„ã‚¤ãƒ™ãƒ³ãƒˆã‚’ä¼æ¬ã™ã‚‹ãŸã‚ã«ä½¿ç”¨ã™ã‚‹ã€‚
+ * å­ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆ -> gridã®ã‚¤ãƒ™ãƒ³ãƒˆã§ã¯åŽŸå‰‡ä½¿ç”¨ã›ãšã€{@link emit}を使用ã™ã‚‹ã€‚
+ */
+const bus = new GridEventEmitter();
+/**
+ * テーブルコンãƒãƒ¼ãƒãƒ³ãƒˆã®ãƒªã‚µã‚¤ã‚ºã‚¤ãƒ™ãƒ³ãƒˆã‚’監視ã™ã‚‹ãŸã‚ã®{@link ResizeObserver}。
+ * 表示切替を検知ã—ã€ã‚µã‚¤ã‚ºã®å†è¨ˆç®—è¦æ±‚を発行ã™ã‚‹ãŸã‚ã«ä½¿ç”¨ã™ã‚‹ï¼ˆãƒžã‚¦ãƒ³ãƒˆæ™‚ã«ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ãŒè¡¨ç¤ºã•れã¦ã„ãªã„å ´åˆã€åˆæ‰‹ã®ã‚µã‚¤ã‚ºã®è‡ªå‹•è¨ˆç®—ãŒæ­£å¸¸ã«åƒã‹ãªã„ãŸã‚)
+ *
+ * {@link setTimeout}を経由ã—ã¦ã„ã‚‹ç†ç”±ã¯ã€{@link onResize}ã®ä¸­ã§ã‚µã‚¤ã‚ºå†è¨ˆç®—è¦æ±‚→サイズ変更ãŒç™ºç”Ÿã™ã‚‹ã¨ãƒ«ãƒ¼ãƒ—ã¨ã¿ãªã•れã€
+ * 「ResizeObserver loop completed with undelivered notifications.ã€ã¨ã„ã†è­¦å‘ŠãŒç™ºç”Ÿã™ã‚‹ãŸã‚(å†è¨ˆç®—ãŒå®Œå…¨ã«çµ‚ã‚れã°é€šçŸ¥ã¯ç™ºç”Ÿã—ãªããªã‚‹ã®ã§å®Ÿéš›ã«ã¯ãƒ«ãƒ¼ãƒ—ã—ãªã„)
+ *
+ * @see {@link onResize}
+ */
+const resizeObserver = new ResizeObserver((entries) => setTimeout(() => onResize(entries)));
+
+const rootEl = ref<InstanceType<typeof HTMLTableElement>>();
+/**
+ * ã‚°ãƒªãƒƒãƒ‰ã®æœ€ã‚‚上ä½ã«ã‚る状態。
+ */
+const state = ref<GridState>('normal');
+/**
+ * グリッドã®åˆ—定義。列定義ã®å…ƒã®è¨­å®šå€¤ã¯éžãƒªã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªã®ã§ã€åˆæœŸå€¤ã‚’生æˆã—ã¦ä»¥é™ã¯å¤‰æ›´ã—ãªã„。
+ */
+const columns = ref<GridColumn[]>(columnSettings.map(createColumn));
+/**
+ * グリッドã®è¡Œå®šç¾©ã€‚propsã§å—ã‘å–ã£ãŸ{@link data}ã‚’ã‚‚ã¨ã«ã€{@link refreshData}ã§å†è¨ˆç®—ã•れる。
+ */
+const rows = ref<GridRow[]>([]);
+/**
+ * グリッドã®ã‚»ãƒ«å®šç¾©ã€‚propsã§å—ã‘å–ã£ãŸ{@link data}ã‚’ã‚‚ã¨ã«ã€{@link refreshData}ã§å†è¨ˆç®—ã•れる。
+ */
+const cells = ref<RowHolder[]>([]);
+
+/**
+ * mousemoveイベントãŒç™ºç”Ÿã—ãŸéš›ã«ã€ã‚¤ãƒ™ãƒ³ãƒˆã‹ã‚‰å–å¾—ã—ãŸã‚»ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’ä¿æŒã™ã‚‹ãŸã‚ã®å¤‰æ•°ã€‚
+ * セルアドレスãŒå¤‰ã‚ã£ãŸçž¬é–“ã«ã‚¤ãƒ™ãƒ³ãƒˆã‚’èµ·ã“ã—ãŸã„時ã®ãŸã‚ã«å‰å›žå€¤ã¨ã—ã¦ä½¿ç”¨ã™ã‚‹ã€‚
+ */
+const previousCellAddress = ref<CellAddress>(CELL_ADDRESS_NONE);
+/**
+ * 編集中ã®ã‚»ãƒ«ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’ä¿æŒã™ã‚‹ãŸã‚ã®å¤‰æ•°ã€‚
+ */
+const editingCellAddress = ref<CellAddress>(CELL_ADDRESS_NONE);
+/**
+ * 列ã®ç¯„å›²é¸æŠžã‚’ã™ã‚‹éš›ã®é–‹å§‹åœ°ç‚¹ã¨ãªã‚‹ã‚¤ãƒ³ãƒ‡ãƒƒã‚¯ã‚¹ã‚’ä¿æŒã™ã‚‹ãŸã‚ã®å¤‰æ•°ã€‚
+ * ã“ã®é–‹å§‹åœ°ç‚¹ã‹ã‚‰ãƒžã‚¦ã‚¹ãŒå‹•ã„ãŸåœ°ç‚¹ã¾ã§ã®ç¯„å›²ã‚’é¸æŠžã™ã‚‹ã€‚
+ */
+const firstSelectionColumnIdx = ref<number>(CELL_ADDRESS_NONE.col);
+/**
+ * 行ã®ç¯„å›²é¸æŠžã‚’ã™ã‚‹éš›ã®é–‹å§‹åœ°ç‚¹ã¨ãªã‚‹ã‚¤ãƒ³ãƒ‡ãƒƒã‚¯ã‚¹ã‚’ä¿æŒã™ã‚‹ãŸã‚ã®å¤‰æ•°ã€‚
+ * ã“ã®é–‹å§‹åœ°ç‚¹ã‹ã‚‰ãƒžã‚¦ã‚¹ãŒå‹•ã„ãŸåœ°ç‚¹ã¾ã§ã®ç¯„å›²ã‚’é¸æŠžã™ã‚‹ã€‚
+ */
+const firstSelectionRowIdx = ref<number>(CELL_ADDRESS_NONE.row);
+
+/**
+ * é¸æŠžçŠ¶æ…‹ã®ã‚»ãƒ«ã‚’å–å¾—ã™ã‚‹ãŸã‚ã®è¨ˆç®—ãƒ—ãƒ­ãƒ‘ãƒ†ã‚£ã€‚é¸æŠžçŠ¶æ…‹ã¨ã¯{@link GridCell.selected}ãŒtrueã®ã‚»ãƒ«ã®ã“ã¨ã€‚
+ */
+const selectedCell = computed(() => {
+ const selected = cells.value.flatMap(it => it.cells).filter(it => it.selected);
+ return selected.length > 0 ? selected[0] : undefined;
+});
+/**
+ * ç¯„å›²é¸æŠžçŠ¶æ…‹ã®ã‚»ãƒ«ã‚’å–å¾—ã™ã‚‹ãŸã‚ã®è¨ˆç®—ãƒ—ãƒ­ãƒ‘ãƒ†ã‚£ã€‚ç¯„å›²é¸æŠžçŠ¶æ…‹ã¨ã¯{@link GridCell.ranged}ãŒtrueã®ã‚»ãƒ«ã®ã“ã¨ã€‚
+ */
+const rangedCells = computed(() => cells.value.flatMap(it => it.cells).filter(it => it.ranged));
+/**
+ * ç¯„å›²é¸æŠžçŠ¶æ…‹ã®ã‚»ãƒ«ã®ç¯„囲をå–å¾—ã™ã‚‹ãŸã‚ã®è¨ˆç®—プロパティ。左上ã®ã‚»ãƒ«ç•ªåœ°ã¨å³ä¸‹ã®ã‚»ãƒ«ç•ªåœ°ã‚’計算ã™ã‚‹ã€‚
+ */
+const rangedBounds = computed(() => {
+ const _cells = rangedCells.value;
+ const _cols = _cells.map(it => it.address.col);
+ const _rows = _cells.map(it => it.address.row);
+
+ const leftTop = {
+ col: Math.min(..._cols),
+ row: Math.min(..._rows),
+ };
+ const rightBottom = {
+ col: Math.max(..._cols),
+ row: Math.max(..._rows),
+ };
+
+ return {
+ leftTop,
+ rightBottom,
+ };
+});
+/**
+ * グリッドã®ä¸­ã§ä½¿ç”¨å¯èƒ½ãªã‚»ãƒ«ã®ç¯„囲をå–å¾—ã™ã‚‹ãŸã‚ã®è¨ˆç®—プロパティ。左上ã®ã‚»ãƒ«ç•ªåœ°ã¨å³ä¸‹ã®ã‚»ãƒ«ç•ªåœ°ã‚’計算ã™ã‚‹ã€‚
+ */
+const availableBounds = computed(() => {
+ const leftTop = {
+ col: 0,
+ row: 0,
+ };
+ const rightBottom = {
+ col: Math.max(...columns.value.map(it => it.index)),
+ row: Math.max(...rows.value.filter(it => it.using).map(it => it.index)),
+ };
+ return { leftTop, rightBottom };
+});
+/**
+ * ç¯„å›²é¸æŠžçŠ¶æ…‹ã®è¡Œã‚’å–å¾—ã™ã‚‹ãŸã‚ã®è¨ˆç®—ãƒ—ãƒ­ãƒ‘ãƒ†ã‚£ã€‚ç¯„å›²é¸æŠžçŠ¶æ…‹ã¨ã¯{@link GridRow.ranged}ãŒtrueã®è¡Œã®ã“ã¨ã€‚
+ */
+const rangedRows = computed(() => rows.value.filter(it => it.ranged));
+
+const lastLine = computed(() => rows.value.filter(it => it.using).length - 1);
+
+// endregion
+// #endregion
+
+watch(data, patchData, { deep: true });
+
+if (_DEV_) {
+ watch(state, (value, oldValue) => {
+ console.log(`[grid][state] ${oldValue} -> ${value}`);
+ });
+}
+
+// #region Event Handlers
+// region Event Handlers
+
+function onResize(entries: ResizeObserverEntry[]) {
+ if (entries.length !== 1 || entries[0].target !== rootEl.value) {
+ return;
+ }
+
+ const contentRect = entries[0].contentRect;
+ if (_DEV_) {
+ console.log(`[grid][resize] contentRect: ${contentRect.width}x${contentRect.height}`);
+ }
+
+ switch (state.value) {
+ case 'hidden': {
+ if (contentRect.width > 0 && contentRect.height > 0) {
+ // å…ˆã«çŠ¶æ…‹ã‚’å¤‰æ›´ã—ã¦ãŠãã€å†è¨ˆç®—è¦æ±‚ãŒè¤‡æ•°å›žèµ°ã‚‰ãªã„よã†ã«ã™ã‚‹
+ state.value = 'normal';
+
+ // é¸æŠžçŠ¶æ…‹ãŒç‹‚ã†ã‹ã‚‚ã—れãªã„ã®ã§è§£é™¤ã—ã¦ãŠã
+ unSelectionRangeAll();
+
+ // å†è¨ˆç®—è¦æ±‚を発行。å„セルå´ã§æœ€ä½Žé™å¿…è¦ãªæ¨ªå¹…を算出ã—ã€emitã§è¿”ã—ã¦ãるよã†ã«ãªã£ã¦ã„ã‚‹
+ bus.emit('forceRefreshContentSize');
+ }
+ break;
+ }
+ default: {
+ if (contentRect.width === 0 || contentRect.height === 0) {
+ state.value = 'hidden';
+ }
+ break;
+ }
+ }
+}
+
+function onKeyDown(ev: KeyboardEvent) {
+ const { ctrlKey, shiftKey, code } = ev;
+ if (_DEV_) {
+ console.log(`[grid][key] ctrl: ${ctrlKey}, shift: ${shiftKey}, code: ${code}`);
+ }
+
+ function updateSelectionRange(newBounds: { leftTop: CellAddress, rightBottom: CellAddress }) {
+ unSelectionOutOfRange(newBounds.leftTop, newBounds.rightBottom);
+ expandCellRange(newBounds.leftTop, newBounds.rightBottom);
+ }
+
+ switch (state.value) {
+ case 'normal': {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ const selectedCellAddress = selectedCell.value?.address ?? CELL_ADDRESS_NONE;
+ const max = availableBounds.value;
+ const bounds = rangedBounds.value;
+
+ handleKeyEvent(ev, [
+ {
+ code: 'Delete', handler: () => {
+ if (rangedRows.value.length > 0) {
+ if (rowSetting.events.delete) {
+ rowSetting.events.delete(rangedRows.value);
+ }
+ } else {
+ const context = createContext();
+ removeDataFromGrid(context, (cell) => {
+ emitCellValue(cell, undefined);
+ });
+ }
+ },
+ },
+ {
+ code: 'KeyC', modifiers: ['Control'], handler: () => {
+ const context = createContext();
+ copyGridDataToClipboard(data.value, context);
+ },
+ },
+ {
+ code: 'KeyV', modifiers: ['Control'], handler: async () => {
+ const _cells = cells.value;
+ const context = createContext();
+ await pasteToGridFromClipboard(context, (row, col, parsedValue) => {
+ emitCellValue(_cells[row.index].cells[col.index], parsedValue);
+ });
+ },
+ },
+ {
+ code: 'ArrowRight', modifiers: ['Control', 'Shift'], handler: () => {
+ updateSelectionRange({
+ leftTop: { col: selectedCellAddress.col, row: bounds.leftTop.row },
+ rightBottom: { col: max.rightBottom.col, row: bounds.rightBottom.row },
+ });
+ },
+ },
+ {
+ code: 'ArrowLeft', modifiers: ['Control', 'Shift'], handler: () => {
+ updateSelectionRange({
+ leftTop: { col: max.leftTop.col, row: bounds.leftTop.row },
+ rightBottom: { col: selectedCellAddress.col, row: bounds.rightBottom.row },
+ });
+ },
+ },
+ {
+ code: 'ArrowUp', modifiers: ['Control', 'Shift'], handler: () => {
+ updateSelectionRange({
+ leftTop: { col: bounds.leftTop.col, row: max.leftTop.row },
+ rightBottom: { col: bounds.rightBottom.col, row: selectedCellAddress.row },
+ });
+ },
+ },
+ {
+ code: 'ArrowDown', modifiers: ['Control', 'Shift'], handler: () => {
+ updateSelectionRange({
+ leftTop: { col: bounds.leftTop.col, row: selectedCellAddress.row },
+ rightBottom: { col: bounds.rightBottom.col, row: max.rightBottom.row },
+ });
+ },
+ },
+ {
+ code: 'ArrowRight', modifiers: ['Shift'], handler: () => {
+ updateSelectionRange({
+ leftTop: {
+ col: bounds.leftTop.col < selectedCellAddress.col
+ ? bounds.leftTop.col + 1
+ : selectedCellAddress.col,
+ row: bounds.leftTop.row,
+ },
+ rightBottom: {
+ col: (bounds.rightBottom.col > selectedCellAddress.col || bounds.leftTop.col === selectedCellAddress.col)
+ ? bounds.rightBottom.col + 1
+ : selectedCellAddress.col,
+ row: bounds.rightBottom.row,
+ },
+ });
+ },
+ },
+ {
+ code: 'ArrowLeft', modifiers: ['Shift'], handler: () => {
+ updateSelectionRange({
+ leftTop: {
+ col: (bounds.leftTop.col < selectedCellAddress.col || bounds.rightBottom.col === selectedCellAddress.col)
+ ? bounds.leftTop.col - 1
+ : selectedCellAddress.col,
+ row: bounds.leftTop.row,
+ },
+ rightBottom: {
+ col: bounds.rightBottom.col > selectedCellAddress.col
+ ? bounds.rightBottom.col - 1
+ : selectedCellAddress.col,
+ row: bounds.rightBottom.row,
+ },
+ });
+ },
+ },
+ {
+ code: 'ArrowUp', modifiers: ['Shift'], handler: () => {
+ updateSelectionRange({
+ leftTop: {
+ col: bounds.leftTop.col,
+ row: (bounds.leftTop.row < selectedCellAddress.row || bounds.rightBottom.row === selectedCellAddress.row)
+ ? bounds.leftTop.row - 1
+ : selectedCellAddress.row,
+ },
+ rightBottom: {
+ col: bounds.rightBottom.col,
+ row: bounds.rightBottom.row > selectedCellAddress.row
+ ? bounds.rightBottom.row - 1
+ : selectedCellAddress.row,
+ },
+ });
+ },
+ },
+ {
+ code: 'ArrowDown', modifiers: ['Shift'], handler: () => {
+ updateSelectionRange({
+ leftTop: {
+ col: bounds.leftTop.col,
+ row: bounds.leftTop.row < selectedCellAddress.row
+ ? bounds.leftTop.row + 1
+ : selectedCellAddress.row,
+ },
+ rightBottom: {
+ col: bounds.rightBottom.col,
+ row: (bounds.rightBottom.row > selectedCellAddress.row || bounds.leftTop.row === selectedCellAddress.row)
+ ? bounds.rightBottom.row + 1
+ : selectedCellAddress.row,
+ },
+ });
+ },
+ },
+ {
+ code: 'ArrowDown', handler: () => {
+ selectionCell({ col: selectedCellAddress.col, row: selectedCellAddress.row + 1 });
+ },
+ },
+ {
+ code: 'ArrowUp', handler: () => {
+ selectionCell({ col: selectedCellAddress.col, row: selectedCellAddress.row - 1 });
+ },
+ },
+ {
+ code: 'ArrowRight', handler: () => {
+ selectionCell({ col: selectedCellAddress.col + 1, row: selectedCellAddress.row });
+ },
+ },
+ {
+ code: 'ArrowLeft', handler: () => {
+ selectionCell({ col: selectedCellAddress.col - 1, row: selectedCellAddress.row });
+ },
+ },
+ ]);
+
+ break;
+ }
+ }
+}
+
+function onMouseDown(ev: MouseEvent) {
+ switch (ev.button) {
+ case 0: {
+ onLeftMouseDown(ev);
+ break;
+ }
+ case 2: {
+ onRightMouseDown(ev);
+ break;
+ }
+ }
+}
+
+function onLeftMouseDown(ev: MouseEvent) {
+ const cellAddress = getCellAddress(ev.target as HTMLElement);
+ if (_DEV_) {
+ console.log(`[grid][mouse-left] state:${state.value}, button: ${ev.button}, cell: ${cellAddress.row}x${cellAddress.col}`);
+ }
+
+ switch (state.value) {
+ case 'cellEditing': {
+ if (availableCellAddress(cellAddress) && !equalCellAddress(editingCellAddress.value, cellAddress)) {
+ selectionCell(cellAddress);
+ }
+ break;
+ }
+ case 'normal': {
+ if (availableCellAddress(cellAddress)) {
+ if (ev.shiftKey && selectedCell.value && !equalCellAddress(cellAddress, selectedCell.value.address)) {
+ const selectedCellAddress = selectedCell.value.address;
+
+ const leftTop = {
+ col: Math.min(selectedCellAddress.col, cellAddress.col),
+ row: Math.min(selectedCellAddress.row, cellAddress.row),
+ };
+
+ const rightBottom = {
+ col: Math.max(selectedCellAddress.col, cellAddress.col),
+ row: Math.max(selectedCellAddress.row, cellAddress.row),
+ };
+
+ unSelectionRangeAll();
+ expandCellRange(leftTop, rightBottom);
+
+ cells.value[selectedCellAddress.row].cells[selectedCellAddress.col].selected = true;
+ } else {
+ selectionCell(cellAddress);
+ }
+
+ previousCellAddress.value = cellAddress;
+
+ registerMouseUp();
+ registerMouseMove();
+ state.value = 'cellSelecting';
+ } else if (isColumnHeaderCellAddress(cellAddress)) {
+ if (ev.shiftKey) {
+ const rangedColumnIndexes = rangedCells.value.map(it => it.address.col);
+ const targetColumnIndexes = [cellAddress.col, ...rangedColumnIndexes];
+ unSelectionRangeAll();
+
+ const leftTop = {
+ col: Math.min(...targetColumnIndexes),
+ row: 0,
+ };
+
+ const rightBottom = {
+ col: Math.max(...targetColumnIndexes),
+ row: cells.value.length - 1,
+ };
+
+ expandCellRange(leftTop, rightBottom);
+
+ if (rangedColumnIndexes.length === 0) {
+ firstSelectionColumnIdx.value = cellAddress.col;
+ } else {
+ if (cellAddress.col > Math.min(...rangedColumnIndexes)) {
+ firstSelectionColumnIdx.value = Math.min(...rangedColumnIndexes);
+ } else {
+ firstSelectionColumnIdx.value = Math.max(...rangedColumnIndexes);
+ }
+ }
+ } else {
+ unSelectionRangeAll();
+
+ const colCells = cells.value.map(row => row.cells[cellAddress.col]);
+ selectionRange(...colCells.map(cell => cell.address));
+
+ firstSelectionColumnIdx.value = cellAddress.col;
+ }
+
+ registerMouseUp();
+ registerMouseMove();
+ previousCellAddress.value = cellAddress;
+ state.value = 'colSelecting';
+
+ // フォーカスを当ã¦ãªã„ã¨ã‚­ãƒ¼ã‚¤ãƒ™ãƒ³ãƒˆãŒæ‹¾ãˆãªã„ã®ã§
+ getCellElement(ev.target as HTMLElement)?.focus();
+ } else if (isRowNumberCellAddress(cellAddress)) {
+ if (ev.shiftKey) {
+ const rangedRowIndexes = rangedRows.value.map(it => it.index);
+ const targetRowIndexes = [cellAddress.row, ...rangedRowIndexes];
+ unSelectionRangeAll();
+
+ const leftTop = {
+ col: 0,
+ row: Math.min(...targetRowIndexes),
+ };
+
+ const rightBottom = {
+ col: Math.min(...cells.value.map(it => it.cells.length - 1)),
+ row: Math.max(...targetRowIndexes),
+ };
+
+ expandCellRange(leftTop, rightBottom);
+ expandRowRange(Math.min(...targetRowIndexes), Math.max(...targetRowIndexes));
+
+ if (rangedRowIndexes.length === 0) {
+ firstSelectionRowIdx.value = cellAddress.row;
+ } else {
+ if (cellAddress.col > Math.min(...rangedRowIndexes)) {
+ firstSelectionRowIdx.value = Math.min(...rangedRowIndexes);
+ } else {
+ firstSelectionRowIdx.value = Math.max(...rangedRowIndexes);
+ }
+ }
+ } else {
+ unSelectionRangeAll();
+ const rowCells = cells.value[cellAddress.row].cells;
+ selectionRange(...rowCells.map(cell => cell.address));
+ expandRowRange(cellAddress.row, cellAddress.row);
+
+ firstSelectionRowIdx.value = cellAddress.row;
+ }
+
+ registerMouseUp();
+ registerMouseMove();
+ previousCellAddress.value = cellAddress;
+ state.value = 'rowSelecting';
+
+ // フォーカスを当ã¦ãªã„ã¨ã‚­ãƒ¼ã‚¤ãƒ™ãƒ³ãƒˆãŒæ‹¾ãˆãªã„ã®ã§
+ getCellElement(ev.target as HTMLElement)?.focus();
+ }
+ break;
+ }
+ }
+}
+
+function onRightMouseDown(ev: MouseEvent) {
+ const cellAddress = getCellAddress(ev.target as HTMLElement);
+ if (_DEV_) {
+ console.log(`[grid][mouse-right] button: ${ev.button}, cell: ${cellAddress.row}x${cellAddress.col}`);
+ }
+
+ switch (state.value) {
+ case 'normal': {
+ if (!availableCellAddress(cellAddress)) {
+ return;
+ }
+
+ const _rangedCells = [...rangedCells.value];
+ if (!_rangedCells.some(it => equalCellAddress(it.address, cellAddress))) {
+ // ç¯„å›²é¸æŠžå¤–ã‚’å³ã‚¯ãƒªãƒƒã‚¯ã—ãŸå ´åˆã¯ã€ç¯„å›²é¸æŠžã‚’è§£é™¤ï¼ˆç¯„å›²é¸æŠžå†…ã§ã‚れã°ç¯„å›²é¸æŠžã‚’ç¶­æŒã™ã‚‹ï¼‰
+ selectionCell(cellAddress);
+ }
+
+ break;
+ }
+ }
+}
+
+function onMouseMove(ev: MouseEvent) {
+ ev.preventDefault();
+
+ const targetCellAddress = getCellAddress(ev.target as HTMLElement);
+ if (equalCellAddress(previousCellAddress.value, targetCellAddress)) {
+ // セルãŒå¤‰ã‚ã‚‹ã¾ã§ã‚¤ãƒ™ãƒ³ãƒˆã‚’èµ·ã“ã—ãŸããªã„
+ return;
+ }
+
+ if (_DEV_) {
+ console.log(`[grid][mouse-move] button: ${ev.button}, cell: ${targetCellAddress.row}x${targetCellAddress.col}`);
+ }
+
+ switch (state.value) {
+ case 'cellSelecting': {
+ const selectedCellAddress = selectedCell.value?.address;
+ if (!availableCellAddress(targetCellAddress) || !selectedCellAddress) {
+ // æ­£ã—ã„セル範囲ã§ã¯ãªã„
+ return;
+ }
+
+ const leftTop = {
+ col: Math.min(targetCellAddress.col, selectedCellAddress.col),
+ row: Math.min(targetCellAddress.row, selectedCellAddress.row),
+ };
+
+ const rightBottom = {
+ col: Math.max(targetCellAddress.col, selectedCellAddress.col),
+ row: Math.max(targetCellAddress.row, selectedCellAddress.row),
+ };
+
+ // 範囲外ã®ã‚»ãƒ«ã¯é¸æŠžè§£é™¤ã—ã€ç¯„囲内ã®ã‚»ãƒ«ã¯é¸æŠžçŠ¶æ…‹ã«ã™ã‚‹
+ unSelectionOutOfRange(leftTop, rightBottom);
+ expandCellRange(leftTop, rightBottom);
+ previousCellAddress.value = targetCellAddress;
+
+ break;
+ }
+ case 'colSelecting': {
+ if (!isColumnHeaderCellAddress(targetCellAddress) || previousCellAddress.value.col === targetCellAddress.col) {
+ // セルãŒå¤‰ã‚ã‚‹ã¾ã§ã‚¤ãƒ™ãƒ³ãƒˆã‚’èµ·ã“ã—ãŸããªã„
+ return;
+ }
+
+ const leftTop = {
+ col: Math.min(targetCellAddress.col, firstSelectionColumnIdx.value),
+ row: 0,
+ };
+
+ const rightBottom = {
+ col: Math.max(targetCellAddress.col, firstSelectionColumnIdx.value),
+ row: cells.value.length - 1,
+ };
+
+ // 範囲外ã®ã‚»ãƒ«ã¯é¸æŠžè§£é™¤ã—ã€ç¯„囲内ã®ã‚»ãƒ«ã¯é¸æŠžçŠ¶æ…‹ã«ã™ã‚‹
+ unSelectionOutOfRange(leftTop, rightBottom);
+ expandCellRange(leftTop, rightBottom);
+ previousCellAddress.value = targetCellAddress;
+
+ // フォーカスを当ã¦ãªã„ã¨ã‚­ãƒ¼ã‚¤ãƒ™ãƒ³ãƒˆãŒæ‹¾ãˆãªã„ã®ã§
+ getCellElement(ev.target as HTMLElement)?.focus();
+
+ break;
+ }
+ case 'rowSelecting': {
+ if (!isRowNumberCellAddress(targetCellAddress) || previousCellAddress.value.row === targetCellAddress.row) {
+ // セルãŒå¤‰ã‚ã‚‹ã¾ã§ã‚¤ãƒ™ãƒ³ãƒˆã‚’èµ·ã“ã—ãŸããªã„
+ return;
+ }
+
+ const leftTop = {
+ col: 0,
+ row: Math.min(targetCellAddress.row, firstSelectionRowIdx.value),
+ };
+
+ const rightBottom = {
+ col: Math.min(...cells.value.map(it => it.cells.length - 1)),
+ row: Math.max(targetCellAddress.row, firstSelectionRowIdx.value),
+ };
+
+ // 範囲外ã®ã‚»ãƒ«ã¯é¸æŠžè§£é™¤ã—ã€ç¯„囲内ã®ã‚»ãƒ«ã¯é¸æŠžçŠ¶æ…‹ã«ã™ã‚‹
+ unSelectionOutOfRange(leftTop, rightBottom);
+ expandCellRange(leftTop, rightBottom);
+
+ // è¡Œã‚‚åŒæ§˜ã«
+ const rangedRowIndexes = [rows.value[targetCellAddress.row].index, ...rangedRows.value.map(it => it.index)];
+ expandRowRange(Math.min(...rangedRowIndexes), Math.max(...rangedRowIndexes));
+
+ previousCellAddress.value = targetCellAddress;
+
+ // フォーカスを当ã¦ãªã„ã¨ã‚­ãƒ¼ã‚¤ãƒ™ãƒ³ãƒˆãŒæ‹¾ãˆãªã„ã®ã§
+ getCellElement(ev.target as HTMLElement)?.focus();
+
+ break;
+ }
+ }
+}
+
+function onMouseUp(ev: MouseEvent) {
+ ev.preventDefault();
+ switch (state.value) {
+ case 'rowSelecting':
+ case 'colSelecting':
+ case 'cellSelecting': {
+ unregisterMouseUp();
+ unregisterMouseMove();
+ state.value = 'normal';
+ previousCellAddress.value = CELL_ADDRESS_NONE;
+ break;
+ }
+ }
+}
+
+function onContextMenu(ev: MouseEvent) {
+ const cellAddress = getCellAddress(ev.target as HTMLElement);
+ if (_DEV_) {
+ console.log(`[grid][context-menu] button: ${ev.button}, cell: ${cellAddress.row}x${cellAddress.col}`);
+ }
+
+ const context = createContext();
+ const menuItems = Array.of<MenuItem>();
+ switch (true) {
+ // 通常セルã®ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼ä½œæˆ
+ case availableCellAddress(cellAddress): {
+ const cell = cells.value[cellAddress.row].cells[cellAddress.col];
+ if (cell.setting.contextMenuFactory) {
+ menuItems.push(...cell.setting.contextMenuFactory(cell.column, cell.row, cell.value, context));
+ }
+ break;
+ }
+ // 列ヘッダセルã®ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼ä½œæˆ
+ case isColumnHeaderCellAddress(cellAddress): {
+ const col = columns.value[cellAddress.col];
+ if (col.setting.contextMenuFactory) {
+ menuItems.push(...col.setting.contextMenuFactory(col, context));
+ }
+ break;
+ }
+ // 行ヘッダセルã®ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼ä½œæˆ
+ case isRowNumberCellAddress(cellAddress): {
+ const row = rows.value[cellAddress.row];
+ if (row.setting.contextMenuFactory) {
+ menuItems.push(...row.setting.contextMenuFactory(row, context));
+ }
+ break;
+ }
+ }
+
+ if (menuItems.length > 0) {
+ os.contextMenu(menuItems, ev);
+ }
+}
+
+function onCellEditBegin(sender: GridCell) {
+ state.value = 'cellEditing';
+ editingCellAddress.value = sender.address;
+ for (const cell of cells.value.flatMap(it => it.cells)) {
+ if (cell.address.col !== sender.address.col || cell.address.row !== sender.address.row) {
+ // 編集状態ã¨ãªã£ãŸã‚»ãƒ«ä»¥å¤–ã¯å…¨éƒ¨é¸æŠžè§£é™¤
+ cell.selected = false;
+ }
+ }
+}
+
+function onCellEditEnd() {
+ editingCellAddress.value = CELL_ADDRESS_NONE;
+ state.value = 'normal';
+}
+
+function onChangeCellValue(sender: GridCell, newValue: CellValue) {
+ applyRowRules([sender]);
+ emitCellValue(sender, newValue);
+}
+
+function onChangeCellContentSize(sender: GridCell, contentSize: Size) {
+ const _cells = cells.value;
+ if (_cells.length > sender.address.row && _cells[sender.address.row].cells.length > sender.address.col) {
+ const currentSize = _cells[sender.address.row].cells[sender.address.col].contentSize;
+ if (currentSize.width !== contentSize.width || currentSize.height !== contentSize.height) {
+ // 通常セルã®ã‚»ãƒ«å¹…ãŒç¢ºå®šã—ãŸã‚‰ã€ãã®ã‚µã‚¤ã‚ºã‚’ä¿æŒã—ã¦ãŠã(内容ã«å¼•ã£å¼µã‚‰ã‚Œã¦æƒ³å®šã‚ˆã‚Šã‚‚大ãã„セルサイズã«ãªã‚‰ãªã„よã†ã«ã™ã‚‹ãŸã‚ã®CSS作æˆã«ä½¿ç”¨ï¼‰
+ _cells[sender.address.row].cells[sender.address.col].contentSize = contentSize;
+
+ if (sender.column.setting.width === 'auto') {
+ calcLargestCellWidth(sender.column);
+ }
+ }
+ }
+}
+
+function onHeaderCellWidthBeginChange() {
+ switch (state.value) {
+ case 'normal': {
+ state.value = 'colResizing';
+ break;
+ }
+ }
+}
+
+function onHeaderCellWidthEndChange() {
+ switch (state.value) {
+ case 'colResizing': {
+ state.value = 'normal';
+ break;
+ }
+ }
+}
+
+function onHeaderCellChangeWidth(sender: GridColumn, width: string) {
+ switch (state.value) {
+ case 'colResizing': {
+ const column = columns.value[sender.index];
+ column.width = width;
+ break;
+ }
+ }
+}
+
+function onHeaderCellChangeContentSize(sender: GridColumn, newSize: Size) {
+ switch (state.value) {
+ case 'normal': {
+ const currentSize = columns.value[sender.index].contentSize;
+ if (currentSize.width !== newSize.width || currentSize.height !== newSize.height) {
+ // ヘッダセルã®ã‚»ãƒ«å¹…ãŒç¢ºå®šã—ãŸã‚‰ã€ãã®ã‚µã‚¤ã‚ºã‚’ä¿æŒã—ã¦ãŠã(内容ã«å¼•ã£å¼µã‚‰ã‚Œã¦æƒ³å®šã‚ˆã‚Šã‚‚大ãã„セルサイズã«ãªã‚‰ãªã„よã†ã«ã™ã‚‹ãŸã‚ã®CSS作æˆã«ä½¿ç”¨ï¼‰
+ columns.value[sender.index].contentSize = newSize;
+
+ if (sender.setting.width === 'auto') {
+ calcLargestCellWidth(sender);
+ }
+ }
+ break;
+ }
+ }
+}
+
+function onHeaderCellWidthLargest(sender: GridColumn) {
+ switch (state.value) {
+ case 'normal': {
+ calcLargestCellWidth(sender);
+ break;
+ }
+ }
+}
+
+// endregion
+// #endregion
+
+// #region Methods
+// region Methods
+
+/**
+ * カラム内ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を表示ã—ãã‚‹ãŸã‚ã«å¿…è¦ãªæ¨ªå¹…ã¨ã€å„セルã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を表示ã—ãã‚‹ãŸã‚ã«å¿…è¦ãªæ¨ªå¹…を比較ã—ã€å¤§ãã„æ–¹ã‚’åˆ—å…¨ä½“ã®æ¨ªå¹…ã¨ã—ã¦æŽ¡ç”¨ã™ã‚‹ã€‚
+ */
+function calcLargestCellWidth(column: GridColumn) {
+ const _cells = cells.value;
+ const largestColumnWidth = columns.value[column.index].contentSize.width;
+
+ const largestCellWidth = (_cells.length > 0)
+ ? _cells
+ .map(row => row.cells[column.index])
+ .reduce(
+ (acc, value) => Math.max(acc, value.contentSize.width),
+ 0,
+ )
+ : 0;
+
+ if (_DEV_) {
+ console.log(`[grid][calc-largest] idx:${column.setting.bindTo}, col:${largestColumnWidth}, cell:${largestCellWidth}`);
+ }
+
+ column.width = `${Math.max(largestColumnWidth, largestCellWidth)}px`;
+}
+
+/**
+ * {@link emit}を使用ã—ã¦ã‚¤ãƒ™ãƒ³ãƒˆã‚’発行ã™ã‚‹ã€‚
+ */
+function emitGridEvent(ev: GridEvent) {
+ const currentState: GridContext = {
+ selectedCell: selectedCell.value,
+ rangedCells: rangedCells.value,
+ rangedRows: rangedRows.value,
+ randedBounds: rangedBounds.value,
+ availableBounds: availableBounds.value,
+ state: state.value,
+ rows: rows.value,
+ columns: columns.value,
+ };
+
+ emit(
+ 'event',
+ ev,
+ currentState,
+ );
+}
+
+/**
+ * 親コンãƒãƒ¼ãƒãƒ³ãƒˆã«æ–°ã—ã„値を通知ã™ã‚‹ã€‚
+ * æ–°ã—ã„値ã¯ã€ã‚¤ãƒ™ãƒ³ãƒˆé€šçŸ¥â†’元データã¸ã®å映→å†è¨ˆç®—(ãƒãƒªãƒ‡ãƒ¼ã‚·ãƒ§ãƒ³å«ã‚€ï¼‰â†’å†æç”»ã®æµã‚Œã§å映ã•れる。
+ */
+function emitCellValue(sender: GridCell | CellAddress, newValue: CellValue) {
+ const cellAddress = 'address' in sender ? sender.address : sender;
+ const cell = cells.value[cellAddress.row].cells[cellAddress.col];
+
+ emitGridEvent({
+ type: 'cell-value-change',
+ column: cell.column,
+ row: cell.row,
+ oldValue: cell.value,
+ newValue: newValue,
+ });
+
+ if (_DEV_) {
+ console.log(`[grid][cell-value] row:${cell.row}, col:${cell.column.index}, value:${newValue}`);
+ }
+}
+
+/**
+ * {@link target}ã®ã‚»ãƒ«ã‚’é¸æŠžçŠ¶æ…‹ã«ã™ã‚‹ã€‚
+ * ãã®éš›ã€{@link target}以外ã®è¡ŒãŠã‚ˆã³ã‚»ãƒ«ã®ç¯„å›²é¸æŠžçŠ¶æ…‹ã‚’è§£é™¤ã™ã‚‹ã€‚
+ */
+function selectionCell(target: CellAddress) {
+ if (!availableCellAddress(target)) {
+ return;
+ }
+
+ unSelectionRangeAll();
+
+ const _cells = cells.value;
+ _cells[target.row].cells[target.col].selected = true;
+ _cells[target.row].cells[target.col].ranged = true;
+}
+
+/**
+ * {@link targets}ã®ã‚»ãƒ«ã‚’ç¯„å›²é¸æŠžçŠ¶æ…‹ã«ã™ã‚‹ã€‚
+ */
+function selectionRange(...targets: CellAddress[]) {
+ const _cells = cells.value;
+ for (const target of targets) {
+ const row = _cells[target.row];
+ if (row.row.using) {
+ row.cells[target.col].ranged = true;
+ }
+ }
+}
+
+/**
+ * 行ãŠã‚ˆã³ã‚»ãƒ«ã®ç¯„å›²é¸æŠžçŠ¶æ…‹ã‚’ã™ã¹ã¦è§£é™¤ã™ã‚‹ã€‚
+ */
+function unSelectionRangeAll() {
+ const _cells = rangedCells.value;
+ for (const cell of _cells) {
+ cell.selected = false;
+ cell.ranged = false;
+ }
+
+ const _rows = rows.value.filter(it => it.using);
+ for (const row of _rows) {
+ row.ranged = false;
+ }
+}
+
+/**
+ * {@link leftTop}ã‹ã‚‰{@link rightBottom}ã®ç¯„囲外ã«ã‚ã‚‹ã‚»ãƒ«ã‚’ç¯„å›²é¸æŠžçŠ¶æ…‹ã‹ã‚‰å¤–ã™ã€‚
+ */
+function unSelectionOutOfRange(leftTop: CellAddress, rightBottom: CellAddress) {
+ const safeBounds = getSafeAddressBounds({ leftTop, rightBottom });
+
+ const _cells = rangedCells.value;
+ for (const cell of _cells) {
+ const outOfRangeCol = cell.address.col < safeBounds.leftTop.col || cell.address.col > safeBounds.rightBottom.col;
+ const outOfRangeRow = cell.address.row < safeBounds.leftTop.row || cell.address.row > safeBounds.rightBottom.row;
+ if (outOfRangeCol || outOfRangeRow) {
+ cell.ranged = false;
+ }
+ }
+
+ const outOfRangeRows = rows.value.filter((_, index) => index < safeBounds.leftTop.row || index > safeBounds.rightBottom.row);
+ for (const row of outOfRangeRows) {
+ row.ranged = false;
+ }
+}
+
+/**
+ * {@link leftTop}ã‹ã‚‰{@link rightBottom}ã®ç¯„囲内ã«ã‚ã‚‹ã‚»ãƒ«ã‚’ç¯„å›²é¸æŠžçŠ¶æ…‹ã«ã™ã‚‹ã€‚
+ */
+function expandCellRange(leftTop: CellAddress, rightBottom: CellAddress) {
+ const safeBounds = getSafeAddressBounds({ leftTop, rightBottom });
+ const targetRows = cells.value.slice(safeBounds.leftTop.row, safeBounds.rightBottom.row + 1);
+ for (const row of targetRows) {
+ for (const cell of row.cells.slice(safeBounds.leftTop.col, safeBounds.rightBottom.col + 1)) {
+ cell.ranged = true;
+ }
+ }
+}
+
+/**
+ * {@link top}ã‹ã‚‰{@link bottom}ã¾ã§ã®è¡Œã‚’ç¯„å›²é¸æŠžçŠ¶æ…‹ã«ã™ã‚‹ã€‚
+ */
+function expandRowRange(top: number, bottom: number) {
+ if (!rowSetting.selectable) {
+ return;
+ }
+
+ const targetRows = rows.value.slice(top, bottom + 1);
+ for (const row of targetRows) {
+ row.ranged = true;
+ }
+}
+
+/**
+ * ç‰¹å®šã®æ¡ä»¶ä¸‹ã§ã®ã¿é©ç”¨ã•れるCSSã‚’åæ˜ ã™ã‚‹ã€‚
+ */
+function applyRowRules(targetCells: GridCell[]) {
+ const _rows = rows.value;
+ const targetRowIdxes = [...new Set(targetCells.map(it => it.address.row))];
+ const rowGroups = Array.of<{ row: GridRow, cells: GridCell[] }>();
+ for (const rowIdx of targetRowIdxes) {
+ const rowGroup = targetCells.filter(it => it.address.row === rowIdx);
+ rowGroups.push({ row: _rows[rowIdx], cells: rowGroup });
+ }
+
+ const _cells = cells.value;
+ for (const group of rowGroups.filter(it => it.row.using)) {
+ const row = group.row;
+ const targetCols = group.cells.map(it => it.column);
+ const rowCells = _cells[group.row.index].cells;
+
+ const newStyles = rowSetting.styleRules
+ .filter(it => it.condition({ row, targetCols, cells: rowCells }))
+ .map(it => it.applyStyle);
+
+ if (JSON.stringify(newStyles) !== JSON.stringify(row.additionalStyles)) {
+ row.additionalStyles = newStyles;
+ }
+ }
+}
+
+function availableCellAddress(cellAddress: CellAddress): boolean {
+ const safeBounds = availableBounds.value;
+ return cellAddress.row >= safeBounds.leftTop.row &&
+ cellAddress.col >= safeBounds.leftTop.col &&
+ cellAddress.row <= safeBounds.rightBottom.row &&
+ cellAddress.col <= safeBounds.rightBottom.col;
+}
+
+function isColumnHeaderCellAddress(cellAddress: CellAddress): boolean {
+ return cellAddress.row === -1 && cellAddress.col >= 0;
+}
+
+function isRowNumberCellAddress(cellAddress: CellAddress): boolean {
+ return cellAddress.row >= 0 && cellAddress.col === -1;
+}
+
+function getSafeAddressBounds(
+ bounds: { leftTop: CellAddress, rightBottom: CellAddress },
+): { leftTop: CellAddress, rightBottom: CellAddress } {
+ const available = availableBounds.value;
+
+ const safeLeftTop = {
+ col: Math.max(bounds.leftTop.col, available.leftTop.col),
+ row: Math.max(bounds.leftTop.row, available.leftTop.row),
+ };
+ const safeRightBottom = {
+ col: Math.min(bounds.rightBottom.col, available.rightBottom.col),
+ row: Math.min(bounds.rightBottom.row, available.rightBottom.row),
+ };
+
+ return { leftTop: safeLeftTop, rightBottom: safeRightBottom };
+}
+
+function registerMouseMove() {
+ unregisterMouseMove();
+ addEventListener('mousemove', onMouseMove);
+}
+
+function unregisterMouseMove() {
+ removeEventListener('mousemove', onMouseMove);
+}
+
+function registerMouseUp() {
+ unregisterMouseUp();
+ addEventListener('mouseup', onMouseUp);
+}
+
+function unregisterMouseUp() {
+ removeEventListener('mouseup', onMouseUp);
+}
+
+function createContext(): GridContext {
+ return {
+ selectedCell: selectedCell.value,
+ rangedCells: rangedCells.value,
+ rangedRows: rangedRows.value,
+ randedBounds: rangedBounds.value,
+ availableBounds: availableBounds.value,
+ state: state.value,
+ rows: rows.value,
+ columns: columns.value,
+ };
+}
+
+function refreshData() {
+ if (_DEV_) {
+ console.log('[grid][refresh-data][begin]');
+ }
+
+ // データを元ã«è¡Œãƒ»åˆ—・セルを作æˆã™ã‚‹ã€‚
+ // 行ã¯å…ƒãƒ‡ãƒ¼ã‚¿ã®é…列ã®é•·ã•ã«å¿œã˜ã¦ä½œæˆã™ã‚‹ãŒã€æœ€ä½Žé™ã®è¡Œæ•°ã¯è¨­å®šã«ã‚ˆã£ã¦æ±ºã¾ã‚‹ã€‚
+ // 行数ãŒå¤‰ã‚ã‚‹ãŸã³ã«éƒ½åº¦ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ã™ã‚‹ã¨ãƒ‘フォーマンスãŒã‚¤ãƒžã‚¤ãƒãªã®ã§ã€ã‚らã‹ã˜ã‚多ã‚ã«ã‚»ãƒ«ã‚’用æ„ã—ã¦ãŠããŸã‚ã®æŽªç½®ã€‚
+ const _data: DataSource[] = data.value;
+ const _rows: GridRow[] = (_data.length > rowSetting.minimumDefinitionCount)
+ ? _data.map((_, index) => createRow(index, true, rowSetting))
+ : Array.from({ length: rowSetting.minimumDefinitionCount }, (_, index) => createRow(index, index < _data.length, rowSetting));
+ const _cols: GridColumn[] = columns.value;
+
+ // 行・列ã®å®šç¾©ã‹ã‚‰ã€å…ƒãƒ‡ãƒ¼ã‚¿ã®é…列より値をå–å¾—ã—ã¦ã‚»ãƒ«ã‚’作æˆã™ã‚‹ã€‚
+ // 行・列ã®å®šç¾©ã¯ãれãžã‚Œã‚¤ãƒ³ãƒ‡ãƒƒã‚¯ã‚¹ã‚’æŒã£ã¦ãŠã‚Šã€ãã®ã‚¤ãƒ³ãƒ‡ãƒƒã‚¯ã‚¹ã¯å…ƒãƒ‡ãƒ¼ã‚¿ã®é…列番地ã«å¯¾å¿œã—ã¦ã„る。
+ const _cells: RowHolder[] = _rows.map(row => {
+ const newCells = row.using
+ ? _cols.map(col => createCell(col, row, _data[row.index][col.setting.bindTo], cellSettings))
+ : _cols.map(col => createCell(col, row, undefined, cellSettings));
+
+ return { row, cells: newCells, origin: _data[row.index] };
+ });
+
+ rows.value = _rows;
+ cells.value = _cells;
+
+ const allCells = _cells.filter(it => it.row.using).flatMap(it => it.cells);
+ for (const cell of allCells) {
+ cell.violation = cellValidation(allCells, cell, cell.value);
+ }
+
+ applyRowRules(allCells);
+
+ if (_DEV_) {
+ console.log('[grid][refresh-data][end]');
+ }
+}
+
+/**
+ * セル値を部分更新ã™ã‚‹ã€‚ã“ã®é–¢æ•°ã¯ã€å¤–部起因ã§ãƒ‡ãƒ¼ã‚¿ãŒå¤‰æ›´ã•れãŸå ´åˆã«å‘¼ã°ã‚Œã‚‹ã€‚
+ *
+ * 外部起因ã§ãƒ‡ãƒ¼ã‚¿ãŒå¤‰æ›´ã•れãŸå ´åˆã¯{@link data}ã®å€¤ãŒå¤‰æ›´ã•れるãŒã€ä½•処ã®ç•ªåœ°ãŒã©ã®ã‚ˆã†ã«å¤‰ã‚ã£ãŸã®ã‹ã¾ã§ã¯æ¤œçŸ¥ã§ããªã„。
+ * セルをã™ã¹ã¦ä½œã‚Šç›´ã›ã°ã„ã„ãŒã€ãã®æ‰‹æ³•ã ã¨ä»¥ä¸‹ã®ãƒ‡ãƒ¡ãƒªãƒƒãƒˆãŒã‚る。
+ * - æç”»è² è·ãŒã‹ã‹ã‚‹
+ * - å„ã‚»ãƒ«ãŒæŒã¤å€‹åˆ¥ã®çŠ¶æ…‹ï¼ˆé¸æŠžä¸­çŠ¶æ…‹ã‚„ãƒãƒªãƒ‡ãƒ¼ã‚·ãƒ§ãƒ³çµæžœãªã©ï¼‰ãŒå¤±ã‚れる
+ *
+ * ãã“ã§ã€æ–°ã—ã„値ã¨ã‚»ãƒ«ãŒæŒã¤å€¤ã‚’çªãåˆã‚ã›ã€å¤‰æ›´ãŒã‚ã£ãŸå ´åˆã®ã¿å€¤ã‚’æ›´æ–°ã—ã€ã‚»ãƒ«ãã®ã‚‚ã®ã¯ä½¿ã„ã¾ã‚ã—ã¤ã¤å€¤ã‚’最新化ã™ã‚‹ã€‚
+ */
+function patchData(newItems: DataSource[]) {
+ if (_DEV_) {
+ console.log('[grid][patch-data][begin]');
+ }
+
+ const _cols = columns.value;
+
+ if (rows.value.length < newItems.length) {
+ const newRows = Array.of<GridRow>();
+ const newCells = Array.of<RowHolder>();
+
+ // 未使用ã®è¡Œã‚’å«ã‚ã¦ã‚‚足りãªã„ã®ã§æ–°ã—ã„行を追加ã™ã‚‹
+ for (let rowIdx = rows.value.length; rowIdx < newItems.length; rowIdx++) {
+ const newRow = createRow(rowIdx, true, rowSetting);
+ newRows.push(newRow);
+ newCells.push({
+ row: newRow,
+ cells: _cols.map(col => createCell(col, newRow, newItems[rowIdx][col.setting.bindTo], cellSettings)),
+ origin: newItems[rowIdx],
+ });
+ }
+
+ rows.value.push(...newRows);
+ cells.value.push(...newCells);
+
+ applyRowRules(newCells.flatMap(it => it.cells));
+ }
+
+ // 行数ã®ä¸Šé™ãŒæ¬²ã—ã„å ´åˆã¯ã“ã“ã«è¨­ã‘ã¦ã‚‚ã„ã„ã‹ã‚‚ã—れãªã„
+
+ const usingRows = rows.value.filter(it => it.using);
+ if (usingRows.length > newItems.length) {
+ // è¡Œæ•°ãŒæ¸›ã£ã¦ã„ã‚‹ã®ã§å¤ã„行をクリアã™ã‚‹ï¼ˆå†ãƒžã‚¦ãƒ³ãƒˆãƒ»å†ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ãŒé‡ã„ã®ã§è¦ç´ ãã®ã‚‚ã®ã¯æ¶ˆã•ãªã„)
+ for (let rowIdx = newItems.length; rowIdx < usingRows.length; rowIdx++) {
+ resetRow(rows.value[rowIdx]);
+ for (let colIdx = 0; colIdx < _cols.length; colIdx++) {
+ const holder = cells.value[rowIdx];
+ holder.origin = {};
+ resetCell(holder.cells[colIdx]);
+ }
+ }
+ }
+
+ // æ–°ã—ã„å€¤ã¨æ—¢ã«è¨­å®šã•れã¦ã„ãŸå€¤ã‚’入れ替ãˆã‚‹
+ const changedCells = Array.of<GridCell>();
+ for (let rowIdx = 0; rowIdx < newItems.length; rowIdx++) {
+ const holder = cells.value[rowIdx];
+ holder.row.using = true;
+
+ const oldCells = holder.cells;
+ const newItem = newItems[rowIdx];
+ for (let colIdx = 0; colIdx < oldCells.length; colIdx++) {
+ const _col = columns.value[colIdx];
+
+ const oldCell = oldCells[colIdx];
+ const newValue = newItem[_col.setting.bindTo];
+ if (oldCell.value !== newValue) {
+ oldCell.value = _col.setting.valueTransformer
+ ? _col.setting.valueTransformer(holder.row, _col, newValue)
+ : newValue;
+ changedCells.push(oldCell);
+ }
+ }
+ }
+
+ if (changedCells.length > 0) {
+ const allCells = cells.value.slice(0, newItems.length).flatMap(it => it.cells);
+ for (const cell of allCells) {
+ cell.violation = cellValidation(allCells, cell, cell.value);
+ }
+
+ applyRowRules(changedCells);
+
+ // ã‚»ãƒ«å€¤ãŒæ›¸ãæ›ã‚ã£ã¦ãŠã‚Šã€ãƒãƒªãƒ‡ãƒ¼ã‚·ãƒ§ãƒ³ã®çµæžœã‚‚変ã‚ã£ã¦ã„ã‚‹ã®ã§å¤–部ã«é€šçŸ¥ã™ã‚‹å¿…è¦ãŒã‚ã‚‹
+ emitGridEvent({
+ type: 'cell-validation',
+ all: cells.value
+ .filter(it => it.row.using)
+ .flatMap(it => it.cells)
+ .map(it => it.violation)
+ .filter(it => !it.valid),
+ });
+ }
+
+ if (_DEV_) {
+ console.log('[grid][patch-data][end]');
+ }
+}
+
+// endregion
+// #endregion
+
+onMounted(() => {
+ state.value = 'normal';
+
+ const bindToList = columnSettings.map(it => it.bindTo);
+ if (new Set(bindToList).size !== columnSettings.length) {
+ // å–å¾—å…ƒã®ãƒ—ロパティåé‡è¤‡ã¯è¨±å®¹ã—ãŸããªã„
+ throw new Error(`Duplicate bindTo setting : [${bindToList.join(',')}]}]`);
+ }
+
+ if (rootEl.value) {
+ resizeObserver.observe(rootEl.value);
+
+ // åˆæœŸè¡¨ç¤ºæ™‚ã«ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ãŒè¡¨ç¤ºã•れã¦ã„ãªã„å ´åˆã¯hidden状態ã«ã—ã¦ãŠã。
+ // コンテンツ表示時ã«resizeイベントãŒç™ºç”Ÿã™ã‚‹ãŒã€ãã®ã¨ãã«hidden状態ã«ã—ã¦ãŠã‹ãªã„ã¨ã‚µã‚¤ã‚ºã®å†è¨ˆç®—ãŒèµ°ã‚‰ãªã„ã®ã§
+ const bounds = rootEl.value.getBoundingClientRect();
+ if (bounds.width === 0 || bounds.height === 0) {
+ state.value = 'hidden';
+ }
+ }
+
+ refreshData();
+});
+</script>
+
+<style module lang="scss">
+.grid {
+ font-size: 90%;
+ overflow-x: scroll;
+ // firefoxã ã¨ã‚¹ã‚¯ãƒ­ãƒ¼ãƒ«ãƒãƒ¼ãŒã‚»ãƒ«ã«é‡ãªã£ã¦è¦‹ã¥ã‚‰ããªã£ã¦ã—ã¾ã†ã®ã§ã‚¹ãƒšãƒ¼ã‚¹ã‚’空ã‘ã¦ãŠã
+ padding-bottom: 8px;
+
+ &.noOverflowHandling {
+ overflow-x: revert;
+ padding-bottom: 0;
+ }
+}
+</style>
+
+<style lang="scss">
+$borderSetting: solid 0.5px var(--MI_THEME-divider);
+
+// é…下コンãƒãƒ¼ãƒãƒ³ãƒˆã‚’å«ã‚ã¦ä¸€æ‹¬ã—ã¦ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«ã™ã‚‹ãŸã‚ã€scopedã‚‚moduleも使用ã§ããªã„
+.mk_grid_border {
+ --rootBorderSetting: none;
+ --borderRadius: 0;
+
+ border-spacing: 0;
+
+ &.mk_grid_root_border {
+ --rootBorderSetting: #{$borderSetting};
+ }
+
+ &.mk_grid_root_rounded {
+ --borderRadius: var(--MI-radius);
+ }
+
+ .mk_grid_thead {
+ .mk_grid_tr {
+ .mk_grid_th {
+ border-left: $borderSetting;
+ border-top: var(--rootBorderSetting);
+
+ &:first-child {
+ // 左上セル
+ border-left: var(--rootBorderSetting);
+ border-top-left-radius: var(--borderRadius);
+ }
+
+ &:last-child {
+ // å³ä¸Šã‚»ãƒ«
+ border-top-right-radius: var(--borderRadius);
+ border-right: var(--rootBorderSetting);
+ }
+ }
+ }
+ }
+
+ .mk_grid_tbody {
+ .mk_grid_tr {
+ .mk_grid_td, .mk_grid_th {
+ border-left: $borderSetting;
+ border-top: $borderSetting;
+
+ &:first-child {
+ // 左端ã®åˆ—
+ border-left: var(--rootBorderSetting);
+ }
+
+ &:last-child {
+ // 一番å³ç«¯ã®åˆ—
+ border-right: var(--rootBorderSetting);
+ }
+ }
+ }
+
+ .last_row {
+ .mk_grid_td, .mk_grid_th {
+ // 一番下ã®è¡Œ
+ border-bottom: var(--rootBorderSetting);
+
+ &:first-child {
+ // 左下セル
+ border-bottom-left-radius: var(--borderRadius);
+ }
+
+ &:last-child {
+ // å³ä¸‹ã‚»ãƒ«
+ border-bottom-right-radius: var(--borderRadius);
+ }
+ }
+ }
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/grid/MkHeaderCell.vue b/packages/frontend/src/components/grid/MkHeaderCell.vue
new file mode 100644
index 0000000000..aecfe7eaa3
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkHeaderCell.vue
@@ -0,0 +1,216 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+ ref="rootEl"
+ class="mk_grid_th"
+ :class="$style.cell"
+ :style="[{ maxWidth: column.width, minWidth: column.width, width: column.width }]"
+ data-grid-cell
+ :data-grid-cell-row="-1"
+ :data-grid-cell-col="column.index"
+>
+ <div :class="$style.root">
+ <div :class="$style.left"></div>
+ <div :class="$style.wrapper">
+ <div ref="contentEl" :class="$style.contentArea">
+ <span v-if="column.setting.icon" class="ti" :class="column.setting.icon" style="line-height: normal"></span>
+ <span v-else>{{ text }}</span>
+ </div>
+ </div>
+ <div
+ :class="$style.right"
+ @mousedown="onHandleMouseDown"
+ @dblclick="onHandleDoubleClick"
+ ></div>
+ </div>
+</div>
+</template>
+
+<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';
+
+const emit = defineEmits<{
+ (ev: 'operation:beginWidthChange', sender: GridColumn): void;
+ (ev: 'operation:endWidthChange', sender: GridColumn): void;
+ (ev: 'operation:widthLargest', sender: GridColumn): void;
+ (ev: 'change:width', sender: GridColumn, width: string): void;
+ (ev: 'change:contentSize', sender: GridColumn, newSize: Size): void;
+}>();
+const props = defineProps<{
+ column: GridColumn,
+ bus: GridEventEmitter,
+}>();
+
+const { column, bus } = toRefs(props);
+
+const rootEl = ref<InstanceType<typeof HTMLTableCellElement>>();
+const contentEl = ref<InstanceType<typeof HTMLDivElement>>();
+
+const resizing = ref<boolean>(false);
+
+const text = computed(() => {
+ const result = column.value.setting.title ?? column.value.setting.bindTo;
+ return result.length > 0 ? result : ' ';
+});
+
+watch(column, () => {
+ // 中身ãŒã‚»ãƒƒãƒˆã•れãŸç›´å¾Œã¯ã‚µã‚¤ã‚ºãŒåˆ†ã‹ã‚‰ãªã„ã®ã§ã€æ¬¡ã®ã‚¿ã‚¤ãƒŸãƒ³ã‚°ã§æ›´æ–°ã™ã‚‹
+ nextTick(emitContentSizeChanged);
+}, { immediate: true });
+
+function onHandleDoubleClick(ev: MouseEvent) {
+ switch (ev.type) {
+ case 'dblclick': {
+ emit('operation:widthLargest', column.value);
+ break;
+ }
+ }
+}
+
+function onHandleMouseDown(ev: MouseEvent) {
+ switch (ev.type) {
+ case 'mousedown': {
+ if (!resizing.value) {
+ registerHandleMouseUp();
+ registerHandleMouseMove();
+ resizing.value = true;
+ emit('operation:beginWidthChange', column.value);
+ }
+ break;
+ }
+ }
+}
+
+function onHandleMouseMove(ev: MouseEvent) {
+ if (!rootEl.value) {
+ // 型ガード
+ return;
+ }
+
+ switch (ev.type) {
+ case 'mousemove': {
+ if (resizing.value) {
+ const bounds = rootEl.value.getBoundingClientRect();
+ const clientWidth = rootEl.value.clientWidth;
+ const clientRight = bounds.left + clientWidth;
+ const nextWidth = clientWidth + (ev.clientX - clientRight);
+ emit('change:width', column.value, `${nextWidth}px`);
+ }
+ break;
+ }
+ }
+}
+
+function onHandleMouseUp(ev: MouseEvent) {
+ switch (ev.type) {
+ case 'mouseup': {
+ if (resizing.value) {
+ unregisterHandleMouseUp();
+ unregisterHandleMouseMove();
+ resizing.value = false;
+ emit('operation:endWidthChange', column.value);
+ }
+ break;
+ }
+ }
+}
+
+function onForceRefreshContentSize() {
+ emitContentSizeChanged();
+}
+
+function registerHandleMouseMove() {
+ unregisterHandleMouseMove();
+ addEventListener('mousemove', onHandleMouseMove);
+}
+
+function unregisterHandleMouseMove() {
+ removeEventListener('mousemove', onHandleMouseMove);
+}
+
+function registerHandleMouseUp() {
+ unregisterHandleMouseUp();
+ addEventListener('mouseup', onHandleMouseUp);
+}
+
+function unregisterHandleMouseUp() {
+ removeEventListener('mouseup', onHandleMouseUp);
+}
+
+function emitContentSizeChanged() {
+ const clientWidth = contentEl.value?.clientWidth ?? 0;
+ const clientHeight = contentEl.value?.clientHeight ?? 0;
+ emit('change:contentSize', column.value, {
+ // ãƒãƒ¼ã®æ¨ªå¹…も考慮ã—ãŸã„ã®ã§ã€+3px
+ width: clientWidth + 3 + 3,
+ height: clientHeight,
+ });
+}
+
+onMounted(() => {
+ bus.value.on('forceRefreshContentSize', onForceRefreshContentSize);
+});
+
+onUnmounted(() => {
+ bus.value.off('forceRefreshContentSize', onForceRefreshContentSize);
+});
+
+</script>
+
+<style module lang="scss">
+$handleWidth: 5px;
+$cellHeight: 28px;
+
+.cell {
+ cursor: pointer;
+}
+
+.root {
+ display: flex;
+ flex-direction: row;
+ height: $cellHeight;
+ max-height: $cellHeight;
+ min-height: $cellHeight;
+
+ .wrapper {
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ overflow: hidden;
+ justify-content: center;
+ }
+
+ .contentArea {
+ display: flex;
+ padding: 6px 4px;
+ box-sizing: border-box;
+ overflow: hidden;
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ .left {
+ // rightã®ã¶ã‚“ã ã‘ズレるã®ã§ãれを相殺ã™ã‚‹ãŸã‚ã®ãƒã‚¬ãƒ†ã‚£ãƒ–マージン
+ margin-left: -$handleWidth;
+ margin-right: auto;
+ width: $handleWidth;
+ min-width: $handleWidth;
+ }
+
+ .right {
+ margin-left: auto;
+ // 判定を罫線ã®ä¸Šã«é‡ã­ãŸã„ã®ã§ãƒã‚¬ãƒ†ã‚£ãƒ–マージンを使ã†
+ margin-right: -$handleWidth;
+ width: $handleWidth;
+ min-width: $handleWidth;
+ cursor: w-resize;
+ z-index: 1;
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/grid/MkHeaderRow.vue b/packages/frontend/src/components/grid/MkHeaderRow.vue
new file mode 100644
index 0000000000..8affa08fd5
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkHeaderRow.vue
@@ -0,0 +1,60 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+ class="mk_grid_tr"
+ :class="$style.root"
+ :data-grid-row="-1"
+>
+ <MkNumberCell
+ v-if="gridSetting.showNumber"
+ content="#"
+ :top="true"
+ />
+ <MkHeaderCell
+ v-for="column in columns"
+ :key="column.index"
+ :column="column"
+ :bus="bus"
+ @operation:beginWidthChange="(sender) => emit('operation:beginWidthChange', sender)"
+ @operation:endWidthChange="(sender) => emit('operation:endWidthChange', sender)"
+ @operation:widthLargest="(sender) => emit('operation:widthLargest', sender)"
+ @change:width="(sender, width) => emit('change:width', sender, width)"
+ @change:contentSize="(sender, newSize) => emit('change:contentSize', sender, newSize)"
+ />
+</div>
+</template>
+
+<script setup lang="ts">
+import { GridEventEmitter, Size } 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';
+
+const emit = defineEmits<{
+ (ev: 'operation:beginWidthChange', sender: GridColumn): void;
+ (ev: 'operation:endWidthChange', sender: GridColumn): void;
+ (ev: 'operation:widthLargest', sender: GridColumn): void;
+ (ev: 'operation:selectionColumn', sender: GridColumn): void;
+ (ev: 'change:width', sender: GridColumn, width: string): void;
+ (ev: 'change:contentSize', sender: GridColumn, newSize: Size): void;
+}>();
+
+defineProps<{
+ columns: GridColumn[],
+ gridSetting: GridRowSetting,
+ bus: GridEventEmitter,
+}>();
+</script>
+
+<style module lang="scss">
+.root {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+</style>
diff --git a/packages/frontend/src/components/grid/MkNumberCell.vue b/packages/frontend/src/components/grid/MkNumberCell.vue
new file mode 100644
index 0000000000..674bba96bc
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkNumberCell.vue
@@ -0,0 +1,61 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+ class="mk_grid_th"
+ :class="[$style.cell]"
+ :tabindex="-1"
+ data-grid-cell
+ :data-grid-cell-row="row?.index ?? -1"
+ :data-grid-cell-col="-1"
+>
+ <div :class="[$style.root]">
+ {{ content }}
+ </div>
+</div>
+</template>
+
+<script setup lang="ts">
+
+import { GridRow } from '@/components/grid/row.js';
+
+defineProps<{
+ content: string,
+ row?: GridRow,
+}>();
+
+</script>
+
+<style module lang="scss">
+$cellHeight: 28px;
+$cellWidth: 34px;
+
+.cell {
+ overflow: hidden;
+ white-space: nowrap;
+ height: $cellHeight;
+ max-height: $cellHeight;
+ min-height: $cellHeight;
+ min-width: $cellWidth;
+ width: $cellWidth;
+ cursor: pointer;
+}
+
+.root {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+ padding: 0 8px;
+ height: 100%;
+ border: solid 0.5px transparent;
+
+ &.selected {
+ background-color: var(--MI_THEME-accentedBg);
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/grid/cell-validators.ts b/packages/frontend/src/components/grid/cell-validators.ts
new file mode 100644
index 0000000000..949cab2ec6
--- /dev/null
+++ b/packages/frontend/src/components/grid/cell-validators.ts
@@ -0,0 +1,110 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * 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 { i18n } from '@/i18n.js';
+
+export type ValidatorParams = {
+ column: GridColumn;
+ row: GridRow;
+ value: CellValue;
+ allCells: GridCell[];
+};
+
+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;
+ const validators = column.setting.validators ?? [];
+
+ const params: ValidatorParams = {
+ column,
+ row,
+ value: newValue,
+ allCells,
+ };
+
+ const violations: ValidateViolationItem[] = validators.map(validator => {
+ const result = validator.validate(params);
+ return {
+ valid: result.valid,
+ validator,
+ result,
+ };
+ });
+
+ return {
+ valid: violations.every(v => v.result.valid),
+ params,
+ violations,
+ };
+}
+
+class ValidatorPreset {
+ required(): GridCellValidator {
+ return {
+ name: 'required',
+ validate: ({ value }): ValidatorResult => {
+ return {
+ valid: value !== null && value !== undefined && value !== '',
+ message: i18n.ts._gridComponent._error.requiredValue,
+ };
+ },
+ };
+ }
+
+ regex(pattern: RegExp): GridCellValidator {
+ return {
+ name: 'regex',
+ validate: ({ value }): ValidatorResult => {
+ return {
+ valid: (typeof value !== 'string') || pattern.test(value.toString() ?? ''),
+ message: i18n.tsx._gridComponent._error.patternNotMatch({ pattern: pattern.source }),
+ };
+ },
+ };
+ }
+
+ unique(): GridCellValidator {
+ return {
+ name: 'unique',
+ validate: ({ column, row, value, allCells }): ValidatorResult => {
+ const bindTo = column.setting.bindTo;
+ const isUnique = allCells
+ .filter(it => it.column.setting.bindTo === bindTo && it.row.index !== row.index)
+ .every(cell => cell.value !== value);
+ return {
+ valid: isUnique,
+ message: i18n.ts._gridComponent._error.notUnique,
+ };
+ },
+ };
+ }
+}
+
+export const validators = new ValidatorPreset();
diff --git a/packages/frontend/src/components/grid/cell.ts b/packages/frontend/src/components/grid/cell.ts
new file mode 100644
index 0000000000..71b7a3e3f1
--- /dev/null
+++ b/packages/frontend/src/components/grid/cell.ts
@@ -0,0 +1,88 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * 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';
+
+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,
+ col: -1,
+};
+
+export type GridCell = {
+ address: CellAddress;
+ value: CellValue;
+ column: GridColumn;
+ row: GridRow;
+ selected: boolean;
+ ranged: boolean;
+ 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,
+ row: GridRow,
+ value: CellValue,
+ setting: GridCellSetting,
+): GridCell {
+ const newValue = (row.using && column.setting.valueTransformer)
+ ? column.setting.valueTransformer(row, column, value)
+ : value;
+
+ return {
+ address: { row: row.index, col: column.index },
+ value: newValue,
+ column,
+ row,
+ selected: false,
+ ranged: false,
+ contentSize: { width: 0, height: 0 },
+ violation: {
+ valid: true,
+ params: {
+ column,
+ row,
+ value,
+ allCells: [],
+ },
+ violations: [],
+ },
+ setting,
+ };
+}
+
+export function resetCell(cell: GridCell): void {
+ cell.selected = false;
+ cell.ranged = false;
+ cell.violation = {
+ valid: true,
+ params: {
+ column: cell.column,
+ row: cell.row,
+ value: cell.value,
+ allCells: [],
+ },
+ violations: [],
+ };
+}
diff --git a/packages/frontend/src/components/grid/column.ts b/packages/frontend/src/components/grid/column.ts
new file mode 100644
index 0000000000..2f505756fe
--- /dev/null
+++ b/packages/frontend/src/components/grid/column.ts
@@ -0,0 +1,53 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * 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';
+
+export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image' | 'hidden';
+
+export type CustomValueEditor = (row: GridRow, col: GridColumn, value: CellValue, cellElement: HTMLElement) => Promise<CellValue>;
+export type CellValueTransformer = (row: GridRow, col: GridColumn, value: CellValue) => CellValue;
+export type GridColumnContextMenuFactory = (col: GridColumn, context: GridContext) => MenuItem[];
+
+export type GridColumnSetting = {
+ bindTo: string;
+ title?: string;
+ icon?: string;
+ type: ColumnType;
+ width: SizeStyle;
+ editable?: boolean;
+ validators?: GridCellValidator[];
+ customValueEditor?: CustomValueEditor;
+ valueTransformer?: CellValueTransformer;
+ contextMenuFactory?: GridColumnContextMenuFactory;
+ events?: {
+ copy?: (value: CellValue) => string;
+ paste?: (text: string) => CellValue;
+ delete?: (cell: GridCell, context: GridContext) => void;
+ }
+};
+
+export type GridColumn = {
+ index: number;
+ setting: GridColumnSetting;
+ width: string;
+ contentSize: Size;
+}
+
+export function createColumn(setting: GridColumnSetting, index: number): GridColumn {
+ return {
+ index,
+ setting,
+ width: calcCellWidth(setting.width),
+ contentSize: { width: 0, height: 0 },
+ };
+}
+
diff --git a/packages/frontend/src/components/grid/grid-event.ts b/packages/frontend/src/components/grid/grid-event.ts
new file mode 100644
index 0000000000..074b72b956
--- /dev/null
+++ b/packages/frontend/src/components/grid/grid-event.ts
@@ -0,0 +1,46 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * 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';
+
+export type GridContext = {
+ selectedCell?: GridCell;
+ rangedCells: GridCell[];
+ rangedRows: GridRow[];
+ randedBounds: {
+ leftTop: CellAddress;
+ rightBottom: CellAddress;
+ };
+ availableBounds: {
+ leftTop: CellAddress;
+ rightBottom: CellAddress;
+ };
+ state: GridState;
+ rows: GridRow[];
+ columns: GridColumn[];
+};
+
+export type GridEvent =
+ GridCellValueChangeEvent |
+ GridCellValidationEvent
+ ;
+
+export type GridCellValueChangeEvent = {
+ type: 'cell-value-change';
+ column: GridColumn;
+ row: GridRow;
+ oldValue: CellValue;
+ newValue: CellValue;
+};
+
+export type GridCellValidationEvent = {
+ type: 'cell-validation';
+ violation?: ValidateViolation;
+ all: ValidateViolation[];
+};
diff --git a/packages/frontend/src/components/grid/grid-utils.ts b/packages/frontend/src/components/grid/grid-utils.ts
new file mode 100644
index 0000000000..a45bc88926
--- /dev/null
+++ b/packages/frontend/src/components/grid/grid-utils.ts
@@ -0,0 +1,215 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * 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';
+
+export function isCellElement(elem: HTMLElement): boolean {
+ return elem.hasAttribute('data-grid-cell');
+}
+
+export function isRowElement(elem: HTMLElement): boolean {
+ return elem.hasAttribute('data-grid-row');
+}
+
+export function calcCellWidth(widthSetting: SizeStyle): string {
+ switch (widthSetting) {
+ case undefined:
+ case 'auto': {
+ return 'auto';
+ }
+ default: {
+ return `${widthSetting}px`;
+ }
+ }
+}
+
+function getCellRowByAttribute(elem: HTMLElement): number {
+ const row = elem.getAttribute('data-grid-cell-row');
+ if (row === null) {
+ throw new Error('data-grid-cell-row attribute not found');
+ }
+ return Number(row);
+}
+
+function getCellColByAttribute(elem: HTMLElement): number {
+ const col = elem.getAttribute('data-grid-cell-col');
+ if (col === null) {
+ throw new Error('data-grid-cell-col attribute not found');
+ }
+ return Number(col);
+}
+
+export function getCellAddress(elem: HTMLElement, parentNodeCount = 10): CellAddress {
+ let node = elem;
+ for (let i = 0; i < parentNodeCount; i++) {
+ if (!node.parentElement) {
+ break;
+ }
+
+ if (isCellElement(node) && isRowElement(node.parentElement)) {
+ const row = getCellRowByAttribute(node);
+ const col = getCellColByAttribute(node);
+
+ return { row, col };
+ }
+
+ node = node.parentElement;
+ }
+
+ return CELL_ADDRESS_NONE;
+}
+
+export function getCellElement(elem: HTMLElement, parentNodeCount = 10): HTMLElement | null {
+ let node = elem;
+ for (let i = 0; i < parentNodeCount; i++) {
+ if (isCellElement(node)) {
+ return node;
+ }
+
+ if (!node.parentElement) {
+ break;
+ }
+
+ node = node.parentElement;
+ }
+
+ return null;
+}
+
+export function equalCellAddress(a: CellAddress, b: CellAddress): boolean {
+ return a.row === b.row && a.col === b.col;
+}
+
+/**
+ * グリッドã®é¸æŠžç¯„囲ã®å†…容をタブ区切り形å¼ãƒ†ã‚­ã‚¹ãƒˆã«å¤‰æ›ã—ã¦ã‚¯ãƒªãƒƒãƒ—ボードã«ã‚³ãƒ”ーã™ã‚‹ã€‚
+ */
+export function copyGridDataToClipboard(
+ gridItems: Ref<DataSource[]> | DataSource[],
+ context: GridContext,
+) {
+ const items = isRef(gridItems) ? gridItems.value : gridItems;
+ const lines = Array.of<string>();
+ const bounds = context.randedBounds;
+
+ for (let row = bounds.leftTop.row; row <= bounds.rightBottom.row; row++) {
+ const rowItems = Array.of<string>();
+ for (let col = bounds.leftTop.col; col <= bounds.rightBottom.col; col++) {
+ const { bindTo, events } = context.columns[col].setting;
+ const value = items[row][bindTo];
+ const transformValue = events?.copy
+ ? events.copy(value)
+ : typeof value === 'object' || Array.isArray(value)
+ ? JSON.stringify(value)
+ : value?.toString() ?? '';
+ rowItems.push(transformValue);
+ }
+ lines.push(rowItems.join('\t'));
+ }
+
+ const text = lines.join('\n');
+ copyToClipboard(text);
+
+ if (_DEV_) {
+ console.log(`Copied to clipboard: ${text}`);
+ }
+}
+
+/**
+ * クリップボードã‹ã‚‰ã‚¿ãƒ–区切りテキストã¨ã—ã¦å€¤ã‚’読ã¿å–りã€ã‚°ãƒªãƒƒãƒ‰ã®é¸æŠžç¯„囲ã«è²¼ã‚Šä»˜ã‘ã‚‹ãŸã‚ã®ãƒ¦ãƒ¼ãƒ†ã‚£ãƒªãƒ†ã‚£é–¢æ•°ã€‚
+ * …ã¨è¨€ã„ã¤ã¤ã‚‚ã€ä½¿ç”¨ç®‡æ‰€ã«ã‚ˆã‚Šå映方法ã«å·®ãŒã‚ã‚‹ãŸã‚æ›´æ–°æ“作ã¯ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯é–¢æ•°ã«ä»»ã›ã¦ã„る。
+ */
+export async function pasteToGridFromClipboard(
+ context: GridContext,
+ callback: (row: GridRow, col: GridColumn, parsedValue: CellValue) => void,
+) {
+ function parseValue(value: string, setting: GridColumnSetting): CellValue {
+ if (setting.events?.paste) {
+ return setting.events.paste(value);
+ } else {
+ switch (setting.type) {
+ case 'number': {
+ return Number(value);
+ }
+ case 'boolean': {
+ return value === 'true';
+ }
+ default: {
+ return value;
+ }
+ }
+ }
+ }
+
+ const clipBoardText = await navigator.clipboard.readText();
+ if (_DEV_) {
+ console.log(`Paste from clipboard: ${clipBoardText}`);
+ }
+
+ const bounds = context.randedBounds;
+ const lines = clipBoardText.replace(/\r/g, '')
+ .split('\n')
+ .map(it => it.split('\t'));
+
+ if (lines.length === 1 && lines[0].length === 1) {
+ // å˜ç‹¬æ–‡å­—列ã®å ´åˆã¯é¸æŠžç¯„囲全体ã«åŒã˜ãƒ†ã‚­ã‚¹ãƒˆã‚’貼り付ã‘ã‚‹
+ const ranges = context.rangedCells;
+ for (const cell of ranges) {
+ if (cell.column.setting.editable) {
+ callback(cell.row, cell.column, parseValue(lines[0][0], cell.column.setting));
+ }
+ }
+ } else {
+ // è¡¨å½¢å¼æ–‡å­—列ã®å ´åˆã¯è¡¨å½¢å¼ã«ãƒ‘ースã—ã€é¸æŠžç¯„囲ã«åˆã†ã‚ˆã†ã«è²¼ã‚Šä»˜ã‘ã‚‹
+ const offsetRow = bounds.leftTop.row;
+ const offsetCol = bounds.leftTop.col;
+ const { columns, rows } = context;
+ for (let row = bounds.leftTop.row; row <= bounds.rightBottom.row; row++) {
+ const rowIdx = row - offsetRow;
+ if (lines.length <= rowIdx) {
+ // クリップボードã‹ã‚‰èª­ã‚“ã äºŒæ¬¡å…ƒé…åˆ—ã‚ˆã‚Šã‚‚é¸æŠžç¯„å›²ã®æ–¹ãŒå¤§ãã„å ´åˆã€è²¼ã‚Šä»˜ã‘æ“作を打ã¡åˆ‡ã‚‹
+ break;
+ }
+
+ const items = lines[rowIdx];
+ for (let col = bounds.leftTop.col; col <= bounds.rightBottom.col; col++) {
+ const colIdx = col - offsetCol;
+ if (items.length <= colIdx) {
+ // クリップボードã‹ã‚‰èª­ã‚“ã äºŒæ¬¡å…ƒé…åˆ—ã‚ˆã‚Šã‚‚é¸æŠžç¯„å›²ã®æ–¹ãŒå¤§ãã„å ´åˆã€è²¼ã‚Šä»˜ã‘æ“作を打ã¡åˆ‡ã‚‹
+ break;
+ }
+
+ if (columns[col].setting.editable) {
+ callback(rows[row], columns[col], parseValue(items[colIdx], columns[col].setting));
+ }
+ }
+ }
+ }
+}
+
+/**
+ * グリッドã®é¸æŠžç¯„囲ã«ã‚るデータを削除ã™ã‚‹ãŸã‚ã®ãƒ¦ãƒ¼ãƒ†ã‚£ãƒªãƒ†ã‚£é–¢æ•°ã€‚
+ * …ã¨è¨€ã„ã¤ã¤ã‚‚ã€ä½¿ç”¨ç®‡æ‰€ã«ã‚ˆã‚Šå映方法ã«å·®ãŒã‚ã‚‹ãŸã‚æ›´æ–°æ“作ã¯ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯é–¢æ•°ã«ä»»ã›ã¦ã„る。
+ */
+export function removeDataFromGrid(
+ context: GridContext,
+ callback: (cell: GridCell) => void,
+) {
+ for (const cell of context.rangedCells) {
+ const { editable, events } = cell.column.setting;
+ if (editable) {
+ if (events?.delete) {
+ events.delete(cell, context);
+ } else {
+ callback(cell);
+ }
+ }
+ }
+}
diff --git a/packages/frontend/src/components/grid/grid.ts b/packages/frontend/src/components/grid/grid.ts
new file mode 100644
index 0000000000..b82e12b304
--- /dev/null
+++ b/packages/frontend/src/components/grid/grid.ts
@@ -0,0 +1,49 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+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';
+
+export type GridSetting = {
+ root?: {
+ noOverflowStyle?: boolean;
+ rounded?: boolean;
+ outerBorder?: boolean;
+ };
+ row?: GridRowSetting;
+ cols: GridColumnSetting[];
+ cells?: GridCellSetting;
+};
+
+export type DataSource = Record<string, CellValue>;
+
+export type GridState =
+ 'normal' |
+ 'cellSelecting' |
+ 'cellEditing' |
+ 'colResizing' |
+ '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
new file mode 100644
index 0000000000..e0a317c9d3
--- /dev/null
+++ b/packages/frontend/src/components/grid/row.ts
@@ -0,0 +1,68 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * 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';
+
+export const defaultGridRowSetting: Required<GridRowSetting> = {
+ showNumber: true,
+ selectable: true,
+ minimumDefinitionCount: 100,
+ styleRules: [],
+ contextMenuFactory: () => [],
+ events: {},
+};
+
+export type GridRowStyleRuleConditionParams = {
+ row: GridRow,
+ targetCols: GridColumn[],
+ cells: GridCell[]
+};
+
+export type GridRowStyleRule = {
+ condition: (params: GridRowStyleRuleConditionParams) => boolean;
+ applyStyle: AdditionalStyle;
+}
+
+export type GridRowContextMenuFactory = (row: GridRow, context: GridContext) => MenuItem[];
+
+export type GridRowSetting = {
+ showNumber?: boolean;
+ selectable?: boolean;
+ minimumDefinitionCount?: number;
+ styleRules?: GridRowStyleRule[];
+ contextMenuFactory?: GridRowContextMenuFactory;
+ events?: {
+ delete?: (rows: GridRow[]) => void;
+ }
+}
+
+export type GridRow = {
+ index: number;
+ ranged: boolean;
+ using: boolean;
+ setting: GridRowSetting;
+ additionalStyles: AdditionalStyle[];
+}
+
+export function createRow(index: number, using: boolean, setting: GridRowSetting): GridRow {
+ return {
+ index,
+ ranged: false,
+ using: using,
+ setting,
+ additionalStyles: [],
+ };
+}
+
+export function resetRow(row: GridRow): void {
+ row.ranged = false;
+ row.using = false;
+ row.additionalStyles = [];
+}
+
diff --git a/packages/frontend/src/components/hook/useLoading.ts b/packages/frontend/src/components/hook/useLoading.ts
new file mode 100644
index 0000000000..6c6ff6ae0d
--- /dev/null
+++ b/packages/frontend/src/components/hook/useLoading.ts
@@ -0,0 +1,52 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { computed, h, ref } from 'vue';
+import MkLoading from '@/components/global/MkLoading.vue';
+
+export const useLoading = (props?: {
+ static?: boolean;
+ inline?: boolean;
+ colored?: boolean;
+ mini?: boolean;
+ em?: boolean;
+}) => {
+ const showingCnt = ref(0);
+
+ const show = () => {
+ showingCnt.value++;
+ };
+
+ const close = (force?: boolean) => {
+ if (force) {
+ showingCnt.value = 0;
+ } else {
+ showingCnt.value = Math.max(0, showingCnt.value - 1);
+ }
+ };
+
+ const scope = <T>(fn: () => T) => {
+ show();
+
+ const result = fn();
+ if (result instanceof Promise) {
+ return result.finally(() => close());
+ } else {
+ close();
+ return result;
+ }
+ };
+
+ const showing = computed(() => showingCnt.value > 0);
+ const component = computed(() => showing.value ? h(MkLoading, props) : null);
+
+ return {
+ show,
+ close,
+ scope,
+ component,
+ showing,
+ };
+};
diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html
index f9ce113687..e69de29bb2 100644
--- a/packages/frontend/src/index.html
+++ b/packages/frontend/src/index.html
@@ -1,39 +0,0 @@
-<!--
- SPDX-FileCopyrightText: syuilo and misskey-project
- SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<!--
- 開発モードã®viteã¯ã“ã®ãƒ•ァイルを起点ã«ã‚µãƒ¼ãƒãƒ¼ã‚’èµ·å‹•ã—ã¾ã™ã€‚
- ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã«æ›¸ã‹ã‚ŒãŸ [t]js ã®ãƒªãƒ³ã‚¯ã¨ (s)cssã®ãƒªãƒ³ã‚¯ã¨ã€ãã®ä¾å­˜é–¢ä¿‚ã«ã‚るファイルã¯ãƒ“ルドã•れã¾ã™
--->
-
-<!DOCTYPE html>
-<html>
-<head>
- <meta charset="UTF-8" />
- <title>[DEV] Loading...</title>
- <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
- <meta
- http-equiv="Content-Security-Policy"
- content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
- worker-src 'self' blob:;
- script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh https://cdn.jsdelivr.net https://raw.esm.sh;
- style-src 'self' 'unsafe-inline';
- img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 activitypub.software secure.gravatar.com avatars.githubusercontent.com;
- media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
- connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://api.listenbrainz.org https://api.friendlycaptcha.com https://raw.esm.sh;
- frame-src *;"
- />
- <meta property="og:site_name" content="[DEV BUILD] Sharkey" />
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="theme-color-orig" content="#86b300">
- <link rel='stylesheet' href='/assets/phosphor-icons/bold/style.css'>
- <link rel='stylesheet' href='/static-assets/fonts/sharkey-icons/style.css'>
-</head>
-
-<body>
-<div id="sharkey_app"></div>
-<script type="module" src="./_dev_boot_.ts"></script>
-</body>
-</html>
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index a81f67aef3..e33c121145 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -11,6 +11,7 @@ import * as Misskey from 'misskey-js';
import type { ComponentProps as CP } from 'vue-component-type-helpers';
import type { Form, GetFormResultType } from '@/scripts/form.js';
import type { MenuItem } from '@/types/menu.js';
+import type { PostFormProps } from '@/types/post-form.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
@@ -28,15 +29,15 @@ import { pleaseLogin } from '@/scripts/please-login.js';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
import { focusParent } from '@/scripts/focus.js';
-import type { PostFormProps } from '@/types/post-form.js';
export const openingWindowsCount = ref(0);
+export type ApiWithDialogCustomErrors = Record<string, { title?: string; text: string; }>;
export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
endpoint: E,
data: P,
token?: string | null | undefined,
- customErrors?: Record<string, { title?: string; text: string; }>,
+ customErrors?: ApiWithDialogCustomErrors,
) => {
const promise = misskeyApi(endpoint, data, token);
promiseDialog(promise, null, async (err) => {
@@ -610,6 +611,27 @@ export async function selectDriveFolder(multiple: boolean): Promise<Misskey.enti
});
}
+export async function selectRole(params: {
+ initialRoleIds?: string[],
+ title?: string,
+ infoMessage?: string,
+ publicOnly?: boolean,
+}): Promise<
+ { canceled: true; result: undefined; } |
+ { canceled: false; result: Misskey.entities.Role[] }
+> {
+ return new Promise((resolve) => {
+ popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, {
+ done: roles => {
+ resolve({ canceled: false, result: roles });
+ },
+ close: () => {
+ resolve({ canceled: true, result: undefined });
+ },
+ }, 'dispose');
+ });
+}
+
export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog>): Promise<string> {
return new Promise(resolve => {
const { dispose } = popup(MkEmojiPickerDialog, {
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/packages/frontend/src/pages/about-misskey.vue
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index f35cbe8d5a..1f36589a49 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
<XEmojis/>
</MkSpacer>
- <MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20">
+ <MkSpacer v-else-if="instance.federation !== 'none' && tab === 'federation'" :contentMax="1000" :marginMin="20">
<XFederation/>
</MkSpacer>
<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
@@ -25,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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';
@@ -51,22 +52,34 @@ watch(tab, () => {
const headerActions = computed(() => []);
-const headerTabs = computed(() => [{
- key: 'overview',
- title: i18n.ts.overview,
-}, {
- key: 'emojis',
- title: i18n.ts.customEmojis,
- icon: 'ph-smiley ph-bold ph-lg',
-}, {
- key: 'federation',
- title: i18n.ts.federation,
- icon: 'ti ti-whirl',
-}, {
- key: 'charts',
- title: i18n.ts.charts,
- icon: 'ti ti-chart-line',
-}]);
+const headerTabs = computed(() => {
+ const items = [];
+
+ items.push({
+ key: 'overview',
+ title: i18n.ts.overview,
+ }, {
+ key: 'emojis',
+ title: i18n.ts.customEmojis,
+ icon: 'ph-smiley ph-bold ph-lg',
+ });
+
+ if (instance.federation !== 'none') {
+ items.push({
+ key: 'federation',
+ title: i18n.ts.federation,
+ icon: 'ti ti-whirl',
+ });
+ }
+
+ items.push({
+ key: 'charts',
+ title: i18n.ts.charts,
+ icon: 'ti ti-chart-line',
+ });
+
+ return items;
+});
definePageMetadata(() => ({
title: i18n.ts.instanceInfo,
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 2f6dac8097..23c5a0f9b7 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -14,13 +14,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-else-if="botProtectionForm.savedState.provider === 'fc'" #suffix>FriendlyCaptcha</template>
<template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template>
<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
- <template v-if="botProtectionForm.modified.value" #footer>
- <MkFormFooter :form="botProtectionForm"/>
+ <template #footer>
+ <MkFormFooter :canSaving="canSaving" :form="botProtectionForm"/>
</template>
<div class="_gaps_m">
<MkRadios v-model="botProtectionForm.state.provider">
- <option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
+ <option value="none">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
<option value="hcaptcha">hCaptcha</option>
<option value="mcaptcha">mCaptcha</option>
<option value="recaptcha">reCAPTCHA</option>
@@ -30,71 +30,126 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkRadios>
<template v-if="botProtectionForm.state.provider === 'hcaptcha'">
- <MkInput v-model="botProtectionForm.state.hcaptchaSiteKey">
+ <MkInput v-model="botProtectionForm.state.hcaptchaSiteKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
</MkInput>
- <MkInput v-model="botProtectionForm.state.hcaptchaSecretKey">
+ <MkInput v-model="botProtectionForm.state.hcaptchaSecretKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
</MkInput>
- <FormSlot>
- <template #label>{{ i18n.ts.preview }}</template>
- <MkCaptcha provider="hcaptcha" :sitekey="botProtectionForm.state.hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
+ <FormSlot v-if="botProtectionForm.state.hcaptchaSiteKey">
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="hcaptcha"
+ :sitekey="botProtectionForm.state.hcaptchaSiteKey"
+ :secretKey="botProtectionForm.state.hcaptchaSecretKey"
+ />
</FormSlot>
+ <MkInfo>
+ <div :class="$style.captchaInfoMsg">
+ <div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
+ <div>
+ <span>ref: </span><a href="https://docs.hcaptcha.com/#integration-testing-test-keys" target="_blank">hCaptcha Developer Guide</a>
+ </div>
+ </div>
+ </MkInfo>
</template>
+
<template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
- <MkInput v-model="botProtectionForm.state.mcaptchaSiteKey">
+ <MkInput v-model="botProtectionForm.state.mcaptchaSiteKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
</MkInput>
- <MkInput v-model="botProtectionForm.state.mcaptchaSecretKey">
+ <MkInput v-model="botProtectionForm.state.mcaptchaSecretKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
</MkInput>
- <MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl">
+ <MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl" debounce>
<template #prefix><i class="ti ti-link"></i></template>
<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
</MkInput>
<FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
- <template #label>{{ i18n.ts.preview }}</template>
- <MkCaptcha provider="mcaptcha" :sitekey="botProtectionForm.state.mcaptchaSiteKey" :instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"/>
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="mcaptcha"
+ :sitekey="botProtectionForm.state.mcaptchaSiteKey"
+ :secretKey="botProtectionForm.state.mcaptchaSecretKey"
+ :instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"
+ />
</FormSlot>
</template>
+
<template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
- <MkInput v-model="botProtectionForm.state.recaptchaSiteKey">
+ <MkInput v-model="botProtectionForm.state.recaptchaSiteKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
</MkInput>
- <MkInput v-model="botProtectionForm.state.recaptchaSecretKey">
+ <MkInput v-model="botProtectionForm.state.recaptchaSecretKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
</MkInput>
<FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
- <template #label>{{ i18n.ts.preview }}</template>
- <MkCaptcha provider="recaptcha" :sitekey="botProtectionForm.state.recaptchaSiteKey"/>
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="recaptcha"
+ :sitekey="botProtectionForm.state.recaptchaSiteKey"
+ :secretKey="botProtectionForm.state.recaptchaSecretKey"
+ />
</FormSlot>
+ <MkInfo>
+ <div :class="$style.captchaInfoMsg">
+ <div>{{ i18n.ts._captcha.testSiteKeyMessage }}</div>
+ <div>
+ <span>ref: </span>
+ <a
+ href="https://developers.google.com/recaptcha/docs/faq?hl=ja#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do"
+ target="_blank"
+ >reCAPTCHA FAQ</a>
+ </div>
+ </div>
+ </MkInfo>
</template>
+
<template v-else-if="botProtectionForm.state.provider === 'turnstile'">
- <MkInput v-model="botProtectionForm.state.turnstileSiteKey">
+ <MkInput v-model="botProtectionForm.state.turnstileSiteKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.turnstileSiteKey }}</template>
</MkInput>
- <MkInput v-model="botProtectionForm.state.turnstileSecretKey">
+ <MkInput v-model="botProtectionForm.state.turnstileSecretKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.turnstileSecretKey }}</template>
</MkInput>
- <FormSlot>
- <template #label>{{ i18n.ts.preview }}</template>
- <MkCaptcha provider="turnstile" :sitekey="botProtectionForm.state.turnstileSiteKey || '1x00000000000000000000AA'"/>
+ <FormSlot v-if="botProtectionForm.state.turnstileSiteKey">
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="turnstile"
+ :sitekey="botProtectionForm.state.turnstileSiteKey"
+ :secretKey="botProtectionForm.state.turnstileSecretKey"
+ />
</FormSlot>
+ <MkInfo>
+ <div :class="$style.captchaInfoMsg">
+ <div>
+ {{ i18n.ts._captcha.testSiteKeyMessage }}
+ </div>
+ <div>
+ <span>ref: </span><a href="https://developers.cloudflare.com/turnstile/troubleshooting/testing/" target="_blank">Cloudflare Docs</a>
+ </div>
+ </div>
+ </MkInfo>
</template>
+
<template v-else-if="botProtectionForm.state.provider === 'fc'">
- <MkInput v-model="botProtectionForm.state.fcSiteKey">
+ <MkInput v-model="botProtectionForm.state.fcSiteKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
</MkInput>
- <MkInput v-model="botProtectionForm.state.fcSecretKey">
+ <MkInput v-model="botProtectionForm.state.fcSecretKey" debounce>
<template #prefix><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
</MkInput>
@@ -102,12 +157,32 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.preview }}</template>
<MkCaptcha provider="fc" :sitekey="botProtectionForm.state.fcSiteKey"/>
</FormSlot>
+ <FormSlot v-if="botProtectionForm.state.fcSiteKey">
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha
+ v-model="captchaResult"
+ provider="fc"
+ :sitekey="botProtectionForm.state.fcSiteKey"
+ :secretKey="botProtectionForm.state.fcSecretKey"
+ />
+ </FormSlot>
+ <MkInfo>
+ <div :class="$style.captchaInfoMsg">
+ <div>
+ {{ i18n.ts._captcha.testSiteKeyMessage }}
+ </div>
+ <div>
+ <span>ref: </span><a href="https://docs.friendlycaptcha.com/#/installation?id=_3-verifying-the-captcha-solution-on-the-server" target="_blank">FriendlyCaptcha Docs</a>
+ </div>
+ </div>
+ </MkInfo>
</template>
+
<template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
<MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
<FormSlot>
- <template #label>{{ i18n.ts.preview }}</template>
- <MkCaptcha provider="testcaptcha"/>
+ <template #label>{{ i18n.ts._captcha.verify }}</template>
+ <MkCaptcha v-model="captchaResult" provider="testcaptcha" :sitekey="null"/>
</FormSlot>
</template>
</div>
@@ -115,7 +190,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, ref } from 'vue';
+import { computed, defineAsyncComponent, ref, watch } from 'vue';
+import * as Misskey from 'misskey-js';
import MkRadios from '@/components/MkRadios.vue';
import MkInput from '@/components/MkInput.vue';
import FormSlot from '@/components/form/slot.vue';
@@ -127,56 +203,113 @@ import { useForm } from '@/scripts/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
+import { ApiWithDialogCustomErrors } from '@/os.js';
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
-const meta = await misskeyApi('admin/meta');
+const errorHandler: ApiWithDialogCustomErrors = {
+ // 検証リクエストãã®ã‚‚ã®ã«å¤±æ•—
+ '0f4fe2f1-2c15-4d6e-b714-efbfcde231cd': {
+ title: i18n.ts._captcha._error._requestFailed.title,
+ text: i18n.ts._captcha._error._requestFailed.text,
+ },
+ // 検証リクエストã®çµæžœãŒä¸æ­£
+ 'c41c067f-24f3-4150-84b2-b5a3ae8c2214': {
+ title: i18n.ts._captcha._error._verificationFailed.title,
+ text: i18n.ts._captcha._error._verificationFailed.text,
+ },
+ // 䏿˜Žãªã‚¨ãƒ©ãƒ¼
+ 'f868d509-e257-42a9-99c1-42614b031a97': {
+ title: i18n.ts._captcha._error._unknown.title,
+ text: i18n.ts._captcha._error._unknown.text,
+ },
+};
+
+const captchaResult = ref<string | null>(null);
+const meta = await misskeyApi('admin/captcha/current');
const botProtectionForm = useForm({
- provider: meta.enableHcaptcha
- ? 'hcaptcha'
- : meta.enableRecaptcha
- ? 'recaptcha'
- : meta.enableTurnstile
- ? 'turnstile'
- : meta.enableMcaptcha
- ? 'mcaptcha'
- : meta.enableFC
- ? 'fc'
- : meta.enableTestcaptcha
- ? 'testcaptcha'
- : null,
- hcaptchaSiteKey: meta.hcaptchaSiteKey,
- hcaptchaSecretKey: meta.hcaptchaSecretKey,
- mcaptchaSiteKey: meta.mcaptchaSiteKey,
- mcaptchaSecretKey: meta.mcaptchaSecretKey,
- mcaptchaInstanceUrl: meta.mcaptchaInstanceUrl,
- recaptchaSiteKey: meta.recaptchaSiteKey,
- recaptchaSecretKey: meta.recaptchaSecretKey,
- turnstileSiteKey: meta.turnstileSiteKey,
- turnstileSecretKey: meta.turnstileSecretKey,
- fcSiteKey: meta.fcSiteKey,
- fcSecretKey: meta.fcSecretKey,
+ provider: meta.provider,
+ hcaptchaSiteKey: meta.hcaptcha.siteKey,
+ hcaptchaSecretKey: meta.hcaptcha.secretKey,
+ mcaptchaSiteKey: meta.mcaptcha.siteKey,
+ mcaptchaSecretKey: meta.mcaptcha.secretKey,
+ mcaptchaInstanceUrl: meta.mcaptcha.instanceUrl,
+ recaptchaSiteKey: meta.recaptcha.siteKey,
+ recaptchaSecretKey: meta.recaptcha.secretKey,
+ turnstileSiteKey: meta.turnstile.siteKey,
+ turnstileSecretKey: meta.turnstile.secretKey,
+ fcSiteKey: meta.fc.siteKey,
+ fcSecretKey: meta.fc.secretKey,
}, async (state) => {
- await os.apiWithDialog('admin/update-meta', {
- enableHcaptcha: state.provider === 'hcaptcha',
- hcaptchaSiteKey: state.hcaptchaSiteKey,
- hcaptchaSecretKey: state.hcaptchaSecretKey,
- enableMcaptcha: state.provider === 'mcaptcha',
- mcaptchaSiteKey: state.mcaptchaSiteKey,
- mcaptchaSecretKey: state.mcaptchaSecretKey,
- mcaptchaInstanceUrl: state.mcaptchaInstanceUrl,
- enableRecaptcha: state.provider === 'recaptcha',
- recaptchaSiteKey: state.recaptchaSiteKey,
- recaptchaSecretKey: state.recaptchaSecretKey,
- enableTurnstile: state.provider === 'turnstile',
- turnstileSiteKey: state.turnstileSiteKey,
- turnstileSecretKey: state.turnstileSecretKey,
- enableFC: state.provider === 'fc',
- fcSiteKey: state.fcSiteKey,
- fcSecretKey: state.fcSecretKey,
- enableTestcaptcha: state.provider === 'testcaptcha',
- });
- fetchInstance(true);
+ const provider = state.provider;
+ if (provider === 'none') {
+ await os.apiWithDialog(
+ 'admin/captcha/save',
+ { provider: provider as Misskey.entities.AdminCaptchaSaveRequest['provider'] },
+ undefined,
+ errorHandler,
+ );
+ } else {
+ const sitekey = provider === 'hcaptcha'
+ ? state.hcaptchaSiteKey
+ : provider === 'mcaptcha'
+ ? state.mcaptchaSiteKey
+ : provider === 'recaptcha'
+ ? state.recaptchaSiteKey
+ : provider === 'turnstile'
+ ? state.turnstileSiteKey
+ : provider === 'fc'
+ ? state.fcSiteKey
+ : null;
+ const secret = provider === 'hcaptcha'
+ ? state.hcaptchaSecretKey
+ : provider === 'mcaptcha'
+ ? state.mcaptchaSecretKey
+ : provider === 'recaptcha'
+ ? state.recaptchaSecretKey
+ : provider === 'turnstile'
+ ? state.turnstileSecretKey
+ : provider === 'fc'
+ ? state.fcSecretKey
+ : null;
+
+ await os.apiWithDialog(
+ 'admin/captcha/save',
+ {
+ provider: provider as Misskey.entities.AdminCaptchaSaveRequest['provider'],
+ sitekey: sitekey,
+ secret: secret,
+ instanceUrl: state.mcaptchaInstanceUrl,
+ captchaResult: captchaResult.value,
+ },
+ undefined,
+ errorHandler,
+ );
+ }
+
+ await fetchInstance(true);
});
+
+watch(botProtectionForm.state, () => {
+ captchaResult.value = null;
+});
+
+const canSaving = computed((): boolean => {
+ return (botProtectionForm.state.provider === 'none') ||
+ (botProtectionForm.state.provider === 'hcaptcha' && !!captchaResult.value) ||
+ (botProtectionForm.state.provider === 'mcaptcha' && !!captchaResult.value) ||
+ (botProtectionForm.state.provider === 'recaptcha' && !!captchaResult.value) ||
+ (botProtectionForm.state.provider === 'turnstile' && !!captchaResult.value) ||
+ (botProtectionForm.state.provider === 'testcaptcha' && !!captchaResult.value);
+});
+
</script>
+
+<style lang="scss" module>
+.captchaInfoMsg {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+</style>
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.impl.ts b/packages/frontend/src/pages/admin/custom-emojis-manager.impl.ts
new file mode 100644
index 0000000000..141ab858d3
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.impl.ts
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export type RequestLogItem = {
+ failed: boolean;
+ url: string;
+ name: string;
+ error?: string;
+};
+
+export const gridSortOrderKeys = [
+ 'name',
+ 'category',
+ 'aliases',
+ 'type',
+ 'license',
+ 'host',
+ 'uri',
+ 'publicUrl',
+ 'isSensitive',
+ 'localOnly',
+ 'updatedAt',
+] as const satisfies string[];
+
+export type GridSortOrderKey = typeof gridSortOrderKeys[number];
+
+export function emptyStrToUndefined(value: string | null) {
+ return value ? value : undefined;
+}
+
+export function emptyStrToNull(value: string) {
+ return value === '' ? null : value;
+}
+
+export function emptyStrToEmptyArray(value: string) {
+ return value === '' ? [] : value.split(' ').map(it => it.trim());
+}
+
+export function roleIdsParser(text: string): { id: string, name: string }[] {
+ // idã¨nameã®ãƒšã‚¢é…列をJSONã§å—ã‘å–る。ãれ以外ã®å½¢å¼ã¯è¨±å®¹ã—ãªã„
+ try {
+ const obj = JSON.parse(text);
+ if (!Array.isArray(obj)) {
+ return [];
+ }
+ if (!obj.every(it => typeof it === 'object' && 'id' in it && 'name' in it)) {
+ return [];
+ }
+
+ return obj.map(it => ({ id: it.id, name: it.name }));
+ } catch (ex) {
+ console.warn(ex);
+ return [];
+ }
+}
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.logs.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.logs.vue
new file mode 100644
index 0000000000..4b145db0ed
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.logs.vue
@@ -0,0 +1,39 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkWindow
+ ref="uiWindow"
+ :initialWidth="400"
+ :initialHeight="500"
+ :canResize="true"
+ @closed="emit('closed')"
+>
+ <template #header>
+ <i class="ti ti-notes" style="margin-right: 0.5em;"></i> {{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}
+ </template>
+ <MkSpacer>
+ <XRegisterLogs :logs="logs"/>
+ </MkSpacer>
+</MkWindow>
+</template>
+
+<script setup lang="ts">
+import MkWindow from '@/components/MkWindow.vue';
+import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
+
+import { i18n } from '@/i18n.js';
+
+import type { RequestLogItem } from './custom-emojis-manager.impl.js';
+
+defineProps<{
+ logs: RequestLogItem[];
+}>();
+
+const emit = defineEmits<{
+ (ev: 'closed'): void;
+}>();
+
+</script>
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.search.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.search.vue
new file mode 100644
index 0000000000..ae43507d66
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.search.vue
@@ -0,0 +1,213 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkWindow
+ ref="uiWindow"
+ :initialWidth="400"
+ :initialHeight="500"
+ :canResize="true"
+ @closed="emit('closed')"
+>
+ <template #header>
+ <i class="ti ti-search" style="margin-right: 0.5em;"></i> {{ i18n.ts.search }}
+ </template>
+ <div :class="$style.root">
+ <MkSpacer>
+ <div class="_gaps">
+ <div class="_gaps_s">
+ <MkInput
+ v-model="model.name"
+ type="search"
+ autocapitalize="off"
+ >
+ <template #label>name</template>
+ </MkInput>
+ <MkInput
+ v-model="model.category"
+ type="search"
+ autocapitalize="off"
+ >
+ <template #label>category</template>
+ </MkInput>
+ <MkInput
+ v-model="model.aliases"
+ type="search"
+ autocapitalize="off"
+ >
+ <template #label>aliases</template>
+ </MkInput>
+
+ <MkInput
+ v-model="model.type"
+ type="search"
+ autocapitalize="off"
+ >
+ <template #label>type</template>
+ </MkInput>
+ <MkInput
+ v-model="model.license"
+ type="search"
+ autocapitalize="off"
+ >
+ <template #label>license</template>
+ </MkInput>
+ <MkSelect
+ v-model="model.sensitive"
+ >
+ <template #label>sensitive</template>
+ <option :value="null">-</option>
+ <option :value="true">true</option>
+ <option :value="false">false</option>
+ </MkSelect>
+
+ <MkSelect
+ v-model="model.localOnly"
+ >
+ <template #label>localOnly</template>
+ <option :value="null">-</option>
+ <option :value="true">true</option>
+ <option :value="false">false</option>
+ </MkSelect>
+ <MkInput
+ v-model="model.updatedAtFrom"
+ type="date"
+ autocapitalize="off"
+ >
+ <template #label>updatedAt(from)</template>
+ </MkInput>
+ <MkInput
+ v-model="model.updatedAtTo"
+ type="date"
+ autocapitalize="off"
+ >
+ <template #label>updatedAt(to)</template>
+ </MkInput>
+
+ <MkInput
+ v-model="queryRolesText"
+ type="text"
+ readonly
+ autocapitalize="off"
+ @click="onQueryRolesEditClicked"
+ >
+ <template #label>role</template>
+ <template #suffix><i class="ti ti-pencil"></i></template>
+ </MkInput>
+ </div>
+ <MkFolder :spacerMax="8" :spacerMin="8">
+ <template #icon><i class="ti ti-arrows-sort"></i></template>
+ <template #label>{{ i18n.ts._customEmojisManager._gridCommon.sortOrder }}</template>
+ <MkSortOrderEditor
+ :baseOrderKeyNames="gridSortOrderKeys"
+ :currentOrders="sortOrders"
+ @update="onSortOrderUpdate"
+ />
+ </MkFolder>
+ </div>
+ </MkSpacer>
+ <div :class="$style.footerActions">
+ <MkButton primary @click="onSearchRequest">
+ {{ i18n.ts.search }}
+ </MkButton>
+ <MkButton @click="onQueryResetButtonClicked">
+ {{ i18n.ts.reset }}
+ </MkButton>
+ </div>
+ </div>
+</MkWindow>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import MkWindow from '@/components/MkWindow.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkSelect from '@/components/MkSelect.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
+
+import {
+ gridSortOrderKeys,
+} from './custom-emojis-manager.impl.js';
+
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+
+import type { EmojiSearchQuery } from './custom-emojis-manager.local.list.vue';
+import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
+import type { GridSortOrderKey } from './custom-emojis-manager.impl.js';
+
+const props = defineProps<{
+ query: EmojiSearchQuery;
+}>();
+
+const emit = defineEmits<{
+ (ev: 'closed'): void;
+ (ev: 'queryUpdated', query: EmojiSearchQuery): void;
+ (ev: 'sortOrderUpdated', orders: SortOrder<GridSortOrderKey>[]): void;
+ (ev: 'search'): void;
+}>();
+
+const model = ref<EmojiSearchQuery>(props.query);
+const queryRolesText = computed(() => model.value.roles.map(it => it.name).join(','));
+
+watch(model, () => {
+ emit('queryUpdated', model.value);
+}, { deep: true });
+
+const sortOrders = ref<SortOrder<GridSortOrderKey>[]>([]);
+
+function onSortOrderUpdate(orders: SortOrder<GridSortOrderKey>[]) {
+ sortOrders.value = orders;
+ emit('sortOrderUpdated', orders);
+}
+
+function onSearchRequest() {
+ emit('search');
+}
+
+function onQueryResetButtonClicked() {
+ model.value.name = '';
+ model.value.category = '';
+ model.value.aliases = '';
+ model.value.type = '';
+ model.value.license = '';
+ model.value.sensitive = null;
+ model.value.localOnly = null;
+ model.value.updatedAtFrom = '';
+ model.value.updatedAtTo = '';
+ sortOrders.value = [];
+}
+
+async function onQueryRolesEditClicked() {
+ const result = await os.selectRole({
+ initialRoleIds: model.value.roles.map(it => it.id),
+ title: i18n.ts._customEmojisManager._local._list.dialogSelectRoleTitle,
+ publicOnly: true,
+ });
+ if (result.canceled) {
+ return;
+ }
+
+ model.value.roles = result.result;
+}
+</script>
+
+<style module>
+.root {
+ position: relative;
+}
+
+.footerActions {
+ position: sticky;
+ bottom: 0;
+ padding: var(--MI-margin);
+ background-color: var(--MI_THEME-bg);
+ display: flex;
+ gap: 8px;
+ z-index: 1;
+}
+</style>
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
new file mode 100644
index 0000000000..c4ea3b93e3
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
@@ -0,0 +1,660 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+ <template #header>
+ <MkPageHeader :overridePageMetadata="headerPageMetadata" :actions="headerActions"/>
+ </template>
+ <template #default>
+ <div class="_gaps" :class="$style.main">
+ <component :is="loadingHandler.component.value" v-if="loadingHandler.showing.value"/>
+ <template v-else>
+ <div v-if="gridItems.length === 0" style="text-align: center">
+ {{ i18n.ts._customEmojisManager._local._list.emojisNothing }}
+ </div>
+
+ <template v-else>
+ <div :class="$style.grid">
+ <MkGrid :data="gridItems" :settings="setupGrid()" @event="onGridEvent"/>
+ </div>
+ </template>
+ </template>
+ </div>
+ </template>
+
+ <template #footer>
+ <div v-if="gridItems.length > 0" :class="$style.footer">
+ <div :class="$style.left">
+ <MkButton danger style="margin-right: auto" @click="onDeleteButtonClicked">
+ {{ i18n.ts.delete }} ({{ deleteItemsCount }})
+ </MkButton>
+ </div>
+
+ <div :class="$style.center">
+ <MkPagingButtons :current="currentPage" :max="allPages" :buttonCount="5" @pageChanged="onPageChanged"/>
+ </div>
+
+ <div :class="$style.right">
+ <MkButton primary :disabled="updateButtonDisabled" @click="onUpdateButtonClicked">
+ {{ i18n.ts.update }} ({{ updatedItemsCount }})
+ </MkButton>
+ <MkButton @click="onGridResetButtonClicked">{{ i18n.ts.reset }}</MkButton>
+ </div>
+ </div>
+ </template>
+</MkStickyContainer>
+</template>
+
+<script lang="ts">
+import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
+import type { GridSortOrderKey } from './custom-emojis-manager.impl.js';
+
+export type EmojiSearchQuery = {
+ name: string | null;
+ category: string | null;
+ aliases: string | null;
+ type: string | null;
+ license: string | null;
+ updatedAtFrom: string | null;
+ updatedAtTo: string | null;
+ sensitive: string | null;
+ localOnly: string | null;
+ roles: { id: string, name: string }[];
+ sortOrders: SortOrder<GridSortOrderKey>[];
+ limit: number;
+};
+</script>
+
+<script setup lang="ts">
+import { computed, defineAsyncComponent, onMounted, ref, nextTick, useCssModule } from 'vue';
+import * as Misskey from 'misskey-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 MkPagingButtons from '@/components/MkPagingButtons.vue';
+import { GridSetting } from '@/components/grid/grid.js';
+import { selectFile } from '@/scripts/select-file.js';
+import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
+import { useLoading } from "@/components/hook/useLoading.js";
+
+type GridItem = {
+ checked: boolean;
+ id: string;
+ url: string;
+ name: string;
+ host: string;
+ category: string;
+ aliases: string;
+ license: string;
+ isSensitive: boolean;
+ localOnly: boolean;
+ roleIdsThatCanBeUsedThisEmojiAsReaction: { id: string, name: string }[];
+ fileId?: string;
+ updatedAt: string | null;
+ publicUrl?: string | null;
+ originalUrl?: string | null;
+ type: string | null;
+}
+
+function setupGrid(): GridSetting {
+ const $style = useCssModule();
+
+ const required = validators.required();
+ const regex = validators.regex(/^[a-zA-Z0-9_]+$/);
+ const unique = validators.unique();
+ return {
+ root: {
+ noOverflowStyle: true,
+ rounded: false,
+ outerBorder: false,
+ },
+ row: {
+ showNumber: true,
+ selectable: true,
+ // グリッドã®è¡Œæ•°ã‚’ã‚らã‹ã˜ã‚100行確ä¿ã™ã‚‹
+ minimumDefinitionCount: 100,
+ styleRules: [
+ {
+ // åˆæœŸå€¤ã‹ã‚‰å¤‰ã‚ã£ã¦ã„ãŸã‚‰èƒŒæ™¯è‰²ã‚’変更
+ condition: ({ row }) => JSON.stringify(gridItems.value[row.index]) !== JSON.stringify(originGridItems.value[row.index]),
+ applyStyle: { className: $style.changedRow },
+ },
+ {
+ // ãƒãƒªãƒ‡ãƒ¼ã‚·ãƒ§ãƒ³ã«å¼•ã£ã‹ã‹ã£ã¦ã„ãŸã‚‰èƒŒæ™¯è‰²ã‚’変更
+ condition: ({ cells }) => cells.some(it => !it.violation.valid),
+ applyStyle: { className: $style.violationRow },
+ },
+ ],
+ // 行ã®ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼è¨­å®š
+ contextMenuFactory: (row, context) => {
+ return [
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.copySelectionRows,
+ icon: 'ti ti-copy',
+ action: () => copyGridDataToClipboard(gridItems, context),
+ },
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._local._list.markAsDeleteTargetRows,
+ icon: 'ti ti-trash',
+ action: () => {
+ for (const rangedRow of context.rangedRows) {
+ gridItems.value[rangedRow.index].checked = true;
+ }
+ },
+ },
+ ];
+ },
+ events: {
+ delete(rows) {
+ // 行削除時ã¯å…ƒãƒ‡ãƒ¼ã‚¿ã®è¡Œã‚’消ã•ãšã€å‰Šé™¤å¯¾è±¡ã¨ã—ã¦ãƒžãƒ¼ã‚¯ã™ã‚‹ã®ã¿ã«ã™ã‚‹
+ for (const row of rows) {
+ gridItems.value[row.index].checked = true;
+ }
+ },
+ },
+ },
+ cols: [
+ { bindTo: 'checked', icon: 'ti-trash', type: 'boolean', editable: true, width: 34 },
+ {
+ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: true, width: 'auto', validators: [required],
+ async customValueEditor(row, col, value, cellElement) {
+ const file = await selectFile(cellElement);
+ gridItems.value[row.index].url = file.url;
+ gridItems.value[row.index].fileId = file.id;
+
+ return file.url;
+ },
+ },
+ {
+ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140,
+ validators: [required, regex, unique],
+ },
+ { bindTo: 'category', title: 'category', type: 'text', editable: true, width: 140 },
+ { bindTo: 'aliases', title: 'aliases', type: 'text', editable: true, width: 140 },
+ { bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
+ { bindTo: 'isSensitive', title: 'sensitive', type: 'boolean', editable: true, width: 90 },
+ { bindTo: 'localOnly', title: 'localOnly', type: 'boolean', editable: true, width: 90 },
+ {
+ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140,
+ valueTransformer(row) {
+ // ãƒãƒƒã‚¯ã‚¨ãƒ³ãƒ‰ã‹ã‚‰ã‹ã‚‰ã¯IDã¨åå‰ã®ãƒšã‚¢é…列ã§å—ã‘å–ã‚‹ãŒã€è¡¨ç¤ºã«IDãŒã‚ã‚‹ã¨ç…©é›‘ãªã®ã§åå‰ã ã‘ã«ã™ã‚‹
+ return gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction
+ .map((it) => it.name)
+ .join(',');
+ },
+ async customValueEditor(row) {
+ // ID直記入ã¯ä½“é¨“çš„ã«æœ€æ‚ªãªã®ã§ãƒ¢ãƒ¼ãƒ€ãƒ«ã‚’使ã£ã¦å…¥åŠ›ã™ã‚‹
+ const current = gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction;
+ const result = await os.selectRole({
+ initialRoleIds: current.map(it => it.id),
+ title: i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction,
+ infoMessage: i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription,
+ publicOnly: true,
+ });
+ if (result.canceled) {
+ return current;
+ }
+
+ const transform = result.result.map(it => ({ id: it.id, name: it.name }));
+ gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = transform;
+
+ return transform;
+ },
+ events: {
+ paste: roleIdsParser,
+ delete(cell) {
+ // デフォルトã¯undefinedã«ãªã‚‹ãŒã€ã“ã®ãƒ—ロパティã¯ç©ºé…列ã«ã—ãŸã„
+ gridItems.value[cell.row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = [];
+ },
+ },
+ },
+ { bindTo: 'type', type: 'text', editable: false, width: 90 },
+ { bindTo: 'updatedAt', type: 'text', editable: false, width: 'auto' },
+ { bindTo: 'publicUrl', type: 'text', editable: false, width: 180 },
+ { bindTo: 'originalUrl', type: 'text', editable: false, width: 180 },
+ ],
+ cells: {
+ // セルã®ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼è¨­å®š
+ contextMenuFactory(col, row, value, context) {
+ return [
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.copySelectionRanges,
+ icon: 'ti ti-copy',
+ action: () => {
+ return copyGridDataToClipboard(gridItems, context);
+ },
+ },
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.deleteSelectionRanges,
+ icon: 'ti ti-trash',
+ action: () => {
+ removeDataFromGrid(context, (cell) => {
+ gridItems.value[cell.row.index][cell.column.setting.bindTo] = undefined;
+ });
+ },
+ },
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._local._list.markAsDeleteTargetRanges,
+ icon: 'ti ti-trash',
+ action: () => {
+ for (const rowIdx of [...new Set(context.rangedCells.map(it => it.row.index)).values()]) {
+ gridItems.value[rowIdx].checked = true;
+ }
+ },
+ },
+ ];
+ },
+ },
+ };
+}
+
+const loadingHandler = useLoading();
+
+const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
+const allPages = ref<number>(0);
+const currentPage = ref<number>(0);
+
+const searchQuery = ref<EmojiSearchQuery>({
+ name: null,
+ category: null,
+ aliases: null,
+ type: null,
+ license: null,
+ updatedAtFrom: null,
+ updatedAtTo: null,
+ sensitive: null,
+ localOnly: null,
+ roles: [],
+ sortOrders: [],
+ limit: 25,
+});
+let searchWindowOpening = false;
+
+const previousQuery = ref<string | undefined>(undefined);
+const sortOrders = ref<SortOrder<GridSortOrderKey>[]>([]);
+const requestLogs = ref<RequestLogItem[]>([]);
+
+const gridItems = ref<GridItem[]>([]);
+const originGridItems = ref<GridItem[]>([]);
+const updateButtonDisabled = ref<boolean>(false);
+
+const updatedItemsCount = computed(() => {
+ return gridItems.value.filter((it, idx) => !it.checked && JSON.stringify(it) !== JSON.stringify(originGridItems.value[idx])).length;
+});
+const deleteItemsCount = computed(() => gridItems.value.filter(it => it.checked).length);
+
+async function onUpdateButtonClicked() {
+ const _items = gridItems.value;
+ const _originItems = originGridItems.value;
+ if (_items.length !== _originItems.length) {
+ throw new Error('The number of items has been changed. Please refresh the page and try again.');
+ }
+
+ const updatedItems = _items.filter((it, idx) => !it.checked && JSON.stringify(it) !== JSON.stringify(_originItems[idx]));
+ if (updatedItems.length === 0) {
+ await os.alert({
+ type: 'info',
+ text: i18n.ts._customEmojisManager._local._list.alertUpdateEmojisNothingDescription,
+ });
+ return;
+ }
+
+ const { canceled } = await os.confirm({
+ type: 'info',
+ text: i18n.tsx._customEmojisManager._local._list.confirmUpdateEmojisDescription({ count: updatedItems.length }),
+ });
+ if (canceled) {
+ return;
+ }
+
+ const action = () => {
+ return updatedItems.map(item =>
+ misskeyApi(
+ 'admin/emoji/update',
+ {
+ // eslint-disable-next-line
+ id: item.id!,
+ name: item.name,
+ category: emptyStrToNull(item.category),
+ aliases: emptyStrToEmptyArray(item.aliases),
+ license: emptyStrToNull(item.license),
+ isSensitive: item.isSensitive,
+ localOnly: item.localOnly,
+ roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map(it => it.id),
+ fileId: item.fileId,
+ })
+ .then(() => ({ item, success: true, err: undefined }))
+ .catch(err => ({ item, success: false, err })),
+ );
+ };
+
+ const result = await os.promiseDialog(Promise.all(action()));
+ const failedItems = result.filter(it => !it.success);
+
+ if (failedItems.length > 0) {
+ await os.alert({
+ type: 'error',
+ title: i18n.ts.somethingHappened,
+ text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
+ });
+ }
+
+ requestLogs.value = result.map(it => ({
+ failed: !it.success,
+ url: it.item.url,
+ name: it.item.name,
+ error: it.err ? JSON.stringify(it.err) : undefined,
+ }));
+
+ await refreshCustomEmojis();
+}
+
+async function onDeleteButtonClicked() {
+ const _items = gridItems.value;
+ const _originItems = originGridItems.value;
+ if (_items.length !== _originItems.length) {
+ throw new Error('The number of items has been changed. Please refresh the page and try again.');
+ }
+
+ const deleteItems = _items.filter((it) => it.checked);
+ if (deleteItems.length === 0) {
+ await os.alert({
+ type: 'info',
+ text: i18n.ts._customEmojisManager._local._list.alertDeleteEmojisNothingDescription,
+ });
+ return;
+ }
+
+ const { canceled } = await os.confirm({
+ type: 'info',
+ text: i18n.tsx._customEmojisManager._local._list.confirmDeleteEmojisDescription({ count: deleteItems.length }),
+ });
+ if (canceled) {
+ return;
+ }
+
+ async function action() {
+ const deleteIds = deleteItems.map(it => it.id!);
+ await misskeyApi('admin/emoji/delete-bulk', { ids: deleteIds });
+ }
+
+ await os.promiseDialog(
+ action(),
+ );
+}
+
+async function onGridResetButtonClicked() {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ title: i18n.ts.resetAreYouSure,
+ text: i18n.ts._customEmojisManager._local._list.confirmResetDescription,
+ });
+
+ if (canceled) return;
+
+ refreshGridItems();
+}
+
+async function onSearchRequest() {
+ await refreshCustomEmojis();
+}
+
+async function onPageChanged(pageNumber: number) {
+ if (updatedItemsCount.value > 0) {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ title: i18n.ts._customEmojisManager._local._list.confirmMovePage,
+ text: i18n.ts._customEmojisManager._local._list.confirmMovePageDesciption,
+ });
+ if (canceled) return;
+ }
+
+ currentPage.value = pageNumber;
+ await nextTick();
+ refreshCustomEmojis();
+}
+
+function onGridEvent(event: GridEvent) {
+ switch (event.type) {
+ case 'cell-validation':
+ onGridCellValidation(event);
+ break;
+ case 'cell-value-change':
+ onGridCellValueChange(event);
+ break;
+ }
+}
+
+function onGridCellValidation(event: GridCellValidationEvent) {
+ updateButtonDisabled.value = event.all.filter(it => !it.valid).length > 0;
+}
+
+function onGridCellValueChange(event: GridCellValueChangeEvent) {
+ const { row, column, newValue } = event;
+ if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
+ gridItems.value[row.index][column.setting.bindTo] = newValue;
+ }
+}
+
+async function refreshCustomEmojis() {
+ const limit = searchQuery.value.limit;
+
+ const query: Misskey.entities.V2AdminEmojiListRequest['query'] = {
+ name: emptyStrToUndefined(searchQuery.value.name),
+ type: emptyStrToUndefined(searchQuery.value.type),
+ 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,
+ updatedAtFrom: emptyStrToUndefined(searchQuery.value.updatedAtFrom),
+ updatedAtTo: emptyStrToUndefined(searchQuery.value.updatedAtTo),
+ roleIds: searchQuery.value.roles.map(it => it.id),
+ hostType: 'local',
+ };
+
+ if (JSON.stringify(query) !== previousQuery.value) {
+ currentPage.value = 1;
+ }
+
+ const result = await loadingHandler.scope(() => misskeyApi('v2/admin/emoji/list', {
+ query: query,
+ limit: limit,
+ page: currentPage.value,
+ sortKeys: sortOrders.value.map(({ key, direction }) => `${direction}${key}` as any),
+ }));
+
+ customEmojis.value = result.emojis;
+ allPages.value = result.allPages;
+
+ previousQuery.value = JSON.stringify(query);
+
+ refreshGridItems();
+}
+
+function refreshGridItems() {
+ gridItems.value = customEmojis.value.map(it => ({
+ checked: false,
+ id: it.id,
+ fileId: undefined,
+ url: it.publicUrl,
+ name: it.name,
+ host: it.host ?? '',
+ category: it.category ?? '',
+ aliases: it.aliases.join(','),
+ license: it.license ?? '',
+ isSensitive: it.isSensitive,
+ localOnly: it.localOnly,
+ roleIdsThatCanBeUsedThisEmojiAsReaction: it.roleIdsThatCanBeUsedThisEmojiAsReaction,
+ updatedAt: it.updatedAt,
+ publicUrl: it.publicUrl,
+ originalUrl: it.originalUrl,
+ type: it.type,
+ }));
+ originGridItems.value = JSON.parse(JSON.stringify(gridItems.value));
+}
+
+onMounted(async () => {
+ await refreshCustomEmojis();
+});
+
+const headerPageMetadata = computed(() => ({
+ title: i18n.ts._customEmojisManager._local.tabTitleList,
+ icon: 'ti ti-icons',
+}));
+
+const headerActions = computed(() => [{
+ icon: 'ti ti-search',
+ text: i18n.ts.search,
+ handler: () => {
+ if (searchWindowOpening) return;
+ searchWindowOpening = true;
+ const { dispose } = os.popup(defineAsyncComponent(() => import('./custom-emojis-manager.local.list.search.vue')), {
+ query: searchQuery.value,
+ }, {
+ queryUpdated: (query: EmojiSearchQuery) => {
+ searchQuery.value = query;
+ },
+ sortOrderUpdated: (orders: SortOrder<GridSortOrderKey>[]) => {
+ sortOrders.value = orders;
+ },
+ search: () => {
+ onSearchRequest();
+ },
+ closed: () => {
+ dispose();
+ searchWindowOpening = false;
+ },
+ });
+ },
+}, {
+ icon: 'ti ti-list-numbers',
+ text: i18n.ts._customEmojisManager._gridCommon.searchLimit,
+ handler: (ev: MouseEvent) => {
+ async function changeSearchLimit(to: number) {
+ if (updatedItemsCount.value > 0) {
+ const { canceled } = await os.confirm({
+ type: 'warning',
+ title: i18n.ts._customEmojisManager._local._list.confirmChangeView,
+ text: i18n.ts._customEmojisManager._local._list.confirmMovePageDesciption,
+ });
+ if (canceled) return;
+ }
+
+ searchQuery.value.limit = to;
+ refreshCustomEmojis();
+ }
+
+ os.popupMenu([{
+ type: 'radioOption',
+ text: '25',
+ active: computed(() => searchQuery.value.limit === 25),
+ action: () => changeSearchLimit(25),
+ }, {
+ type: 'radioOption',
+ text: '50',
+ active: computed(() => searchQuery.value.limit === 50),
+ action: () => changeSearchLimit(50),
+ }, {
+ type: 'radioOption',
+ text: '100',
+ active: computed(() => searchQuery.value.limit === 100),
+ action: () => changeSearchLimit(100),
+ }], ev.currentTarget ?? ev.target);
+ },
+}, {
+ icon: 'ti ti-notes',
+ text: i18n.ts._customEmojisManager._gridCommon.registrationLogs,
+ handler: () => {
+ const { dispose } = os.popup(defineAsyncComponent(() => import('./custom-emojis-manager.local.list.logs.vue')), {
+ logs: requestLogs.value,
+ }, {
+ closed: () => {
+ dispose();
+ },
+ });
+ }
+}]);
+</script>
+
+<style module lang="scss">
+.violationRow {
+ background-color: var(--MI_THEME-infoWarnBg);
+}
+
+.changedRow {
+ background-color: var(--MI_THEME-infoBg);
+}
+
+.editedRow {
+ background-color: var(--MI_THEME-infoBg);
+}
+
+.main {
+ height: calc(100vh - var(--MI-stickyTop) - var(--MI-stickyBottom));
+ overflow: scroll;
+}
+
+.grid {
+ width: max-content;
+ border-bottom: 1px solid var(--MI_THEME-divider);
+}
+
+.footer {
+ background-color: var(--MI_THEME-bg);
+
+ padding: var(--MI-margin);
+ border-top: 1px solid var(--MI_THEME-divider);
+
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 8px;
+
+ & .left {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 8px;
+ }
+
+ & .center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ }
+
+ & .right {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ flex-direction: row;
+ gap: 8px;
+ }
+}
+
+.divider {
+ margin: 8px 0;
+ border-top: solid 0.5px var(--MI_THEME-divider);
+}
+
+</style>
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
new file mode 100644
index 0000000000..cc8b625cd5
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
@@ -0,0 +1,481 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+ <MkFolder>
+ <template #icon><i class="ti ti-settings"></i></template>
+ <template #label>{{ i18n.ts._customEmojisManager._local._register.uploadSettingTitle }}</template>
+ <template #caption>{{ i18n.ts._customEmojisManager._local._register.uploadSettingDescription }}</template>
+
+ <div class="_gaps">
+ <MkSelect v-model="selectedFolderId">
+ <template #label>{{ i18n.ts.uploadFolder }}</template>
+ <option v-for="folder in uploadFolders" :key="folder.id" :value="folder.id">
+ {{ folder.name }}
+ </option>
+ </MkSelect>
+
+ <MkSwitch v-model="keepOriginalUploading">
+ <template #label>{{ i18n.ts.keepOriginalUploading }}</template>
+ <template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
+ </MkSwitch>
+
+ <MkSwitch v-model="directoryToCategory">
+ <template #label>{{ i18n.ts._customEmojisManager._local._register.directoryToCategoryLabel }}</template>
+ <template #caption>{{ i18n.ts._customEmojisManager._local._register.directoryToCategoryCaption }}</template>
+ </MkSwitch>
+ </div>
+ </MkFolder>
+
+ <MkFolder>
+ <template #icon><i class="ti ti-notes"></i></template>
+ <template #label>{{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}</template>
+ <template #caption>
+ {{ i18n.ts._customEmojisManager._gridCommon.registrationLogsCaption }}
+ </template>
+ <XRegisterLogs :logs="requestLogs"/>
+ </MkFolder>
+
+ <div
+ :class="[$style.uploadBox, [isDragOver ? $style.dragOver : {}]]"
+ @dragover.prevent="isDragOver = true"
+ @dragleave.prevent="isDragOver = false"
+ @drop.prevent.stop="onDrop"
+ >
+ <div style="margin-top: 1em">
+ {{ i18n.ts._customEmojisManager._local._register.emojiInputAreaCaption }}
+ </div>
+ <ul>
+ <li>{{ i18n.ts._customEmojisManager._local._register.emojiInputAreaList1 }}</li>
+ <li><a @click.prevent="onFileSelectClicked">{{ i18n.ts._customEmojisManager._local._register.emojiInputAreaList2 }}</a></li>
+ <li><a @click.prevent="onDriveSelectClicked">{{ i18n.ts._customEmojisManager._local._register.emojiInputAreaList3 }}</a></li>
+ </ul>
+ </div>
+
+ <div v-if="gridItems.length > 0" :class="$style.gridArea">
+ <MkGrid
+ :data="gridItems"
+ :settings="setupGrid()"
+ @event="onGridEvent"
+ />
+ </div>
+
+ <div v-if="gridItems.length > 0" :class="$style.footer">
+ <MkButton primary :disabled="registerButtonDisabled" @click="onRegistryClicked">
+ {{ i18n.ts.registration }}
+ </MkButton>
+ <MkButton @click="onClearClicked">
+ {{ i18n.ts.clear }}
+ </MkButton>
+ </div>
+</div>
+</template>
+
+<script setup lang="ts">
+/* 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 {
+ 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 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';
+
+const MAXIMUM_EMOJI_REGISTER_COUNT = 100;
+
+type FolderItem = {
+ id?: string;
+ name: string;
+};
+
+type GridItem = {
+ fileId: string;
+ url: string;
+ name: string;
+ host: string;
+ category: string;
+ aliases: string;
+ license: string;
+ isSensitive: boolean;
+ localOnly: boolean;
+ roleIdsThatCanBeUsedThisEmojiAsReaction: { id: string, name: string }[];
+ type: string | null;
+}
+
+function setupGrid(): GridSetting {
+ const $style = useCssModule();
+
+ const required = validators.required();
+ const regex = validators.regex(/^[a-zA-Z0-9_]+$/);
+ const unique = validators.unique();
+
+ function removeRows(rows: GridRow[]) {
+ const idxes = [...new Set(rows.map(it => it.index))];
+ gridItems.value = gridItems.value.filter((_, i) => !idxes.includes(i));
+ }
+
+ return {
+ row: {
+ showNumber: true,
+ selectable: true,
+ minimumDefinitionCount: 100,
+ styleRules: [
+ {
+ // 1ã¤ã§ã‚‚ãƒãƒªãƒ‡ãƒ¼ã‚·ãƒ§ãƒ³ã‚¨ãƒ©ãƒ¼ãŒã‚れã°è¡Œå…¨ä½“をエラー表示ã™ã‚‹
+ condition: ({ cells }) => cells.some(it => !it.violation.valid),
+ applyStyle: { className: $style.violationRow },
+ },
+ ],
+ // 行ã®ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼è¨­å®š
+ contextMenuFactory: (row, context) => {
+ return [
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.copySelectionRows,
+ icon: 'ti ti-copy',
+ action: () => copyGridDataToClipboard(gridItems, context),
+ },
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.deleteSelectionRows,
+ icon: 'ti ti-trash',
+ action: () => removeRows(context.rangedRows),
+ },
+ ];
+ },
+ events: {
+ delete(rows) {
+ removeRows(rows);
+ },
+ },
+ },
+ cols: [
+ { bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto', validators: [required] },
+ {
+ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140,
+ validators: [required, regex, unique],
+ },
+ { bindTo: 'category', title: 'category', type: 'text', editable: true, width: 140 },
+ { bindTo: 'aliases', title: 'aliases', type: 'text', editable: true, width: 140 },
+ { bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
+ { bindTo: 'isSensitive', title: 'sensitive', type: 'boolean', editable: true, width: 90 },
+ { bindTo: 'localOnly', title: 'localOnly', type: 'boolean', editable: true, width: 90 },
+ {
+ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 140,
+ valueTransformer: (row) => {
+ // ãƒãƒƒã‚¯ã‚¨ãƒ³ãƒ‰ã‹ã‚‰ã‹ã‚‰ã¯IDã¨åå‰ã®ãƒšã‚¢é…列ã§å—ã‘å–ã‚‹ãŒã€è¡¨ç¤ºã«IDãŒã‚ã‚‹ã¨ç…©é›‘ãªã®ã§åå‰ã ã‘ã«ã™ã‚‹
+ return gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction
+ .map((it) => it.name)
+ .join(',');
+ },
+ customValueEditor: async (row) => {
+ // ID直記入ã¯ä½“é¨“çš„ã«æœ€æ‚ªãªã®ã§ãƒ¢ãƒ¼ãƒ€ãƒ«ã‚’使ã£ã¦å…¥åŠ›ã™ã‚‹
+ const current = gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction;
+ const result = await os.selectRole({
+ initialRoleIds: current.map(it => it.id),
+ title: i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction,
+ infoMessage: i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription,
+ publicOnly: true,
+ });
+ if (result.canceled) {
+ return current;
+ }
+
+ const transform = result.result.map(it => ({ id: it.id, name: it.name }));
+ gridItems.value[row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = transform;
+
+ return transform;
+ },
+ events: {
+ paste: roleIdsParser,
+ delete(cell) {
+ // デフォルトã¯undefinedã«ãªã‚‹ãŒã€ã“ã®ãƒ—ロパティã¯ç©ºé…列ã«ã—ãŸã„
+ gridItems.value[cell.row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = [];
+ },
+ },
+ },
+ { bindTo: 'type', type: 'text', editable: false, width: 90 },
+ ],
+ cells: {
+ // セルã®ã‚³ãƒ³ãƒ†ã‚­ã‚¹ãƒˆãƒ¡ãƒ‹ãƒ¥ãƒ¼è¨­å®š
+ contextMenuFactory: (col, row, value, context) => {
+ return [
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.copySelectionRanges,
+ icon: 'ti ti-copy',
+ action: () => copyGridDataToClipboard(gridItems, context),
+ },
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.deleteSelectionRanges,
+ icon: 'ti ti-trash',
+ action: () => removeRows(context.rangedCells.map(it => it.row)),
+ },
+ ];
+ },
+ },
+ };
+}
+
+const uploadFolders = ref<FolderItem[]>([]);
+const gridItems = ref<GridItem[]>([]);
+const selectedFolderId = ref(defaultStore.state.uploadFolder);
+const keepOriginalUploading = ref(defaultStore.state.keepOriginalUploading);
+const directoryToCategory = ref<boolean>(false);
+const registerButtonDisabled = ref<boolean>(false);
+const requestLogs = ref<RequestLogItem[]>([]);
+const isDragOver = ref<boolean>(false);
+
+async function onRegistryClicked() {
+ const dialogSelection = await os.confirm({
+ type: 'info',
+ text: i18n.tsx._customEmojisManager._local._register.confirmRegisterEmojisDescription({ count: MAXIMUM_EMOJI_REGISTER_COUNT }),
+ });
+
+ if (dialogSelection.canceled) {
+ return;
+ }
+
+ const items = gridItems.value;
+ const upload = () => {
+ return items.slice(0, MAXIMUM_EMOJI_REGISTER_COUNT)
+ .map(item =>
+ misskeyApi(
+ 'admin/emoji/add', {
+ name: item.name,
+ category: emptyStrToNull(item.category),
+ aliases: emptyStrToEmptyArray(item.aliases),
+ license: emptyStrToNull(item.license),
+ isSensitive: item.isSensitive,
+ localOnly: item.localOnly,
+ roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map(it => it.id),
+ fileId: item.fileId!,
+ })
+ .then(() => ({ item, success: true, err: undefined }))
+ .catch(err => ({ item, success: false, err })),
+ );
+ };
+
+ const result = await os.promiseDialog(Promise.all(upload()));
+ const failedItems = result.filter(it => !it.success);
+
+ if (failedItems.length > 0) {
+ await os.alert({
+ type: 'error',
+ title: i18n.ts.somethingHappened,
+ text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
+ });
+ }
+
+ requestLogs.value = result.map(it => ({
+ failed: !it.success,
+ url: it.item.url,
+ name: it.item.name,
+ error: it.err ? JSON.stringify(it.err) : undefined,
+ }));
+
+ // ç™»éŒ²ã«æˆåŠŸã—ãŸã‚‚ã®ã¯ä¸€è¦§ã‹ã‚‰é™¤ã
+ const successItems = result.filter(it => it.success).map(it => it.item);
+ gridItems.value = gridItems.value.filter(it => !successItems.includes(it));
+}
+
+async function onClearClicked() {
+ const result = await os.confirm({
+ type: 'warning',
+ text: i18n.ts._customEmojisManager._local._register.confirmClearEmojisDescription,
+ });
+
+ if (!result.canceled) {
+ gridItems.value = [];
+ }
+}
+
+async function onDrop(ev: DragEvent) {
+ isDragOver.value = false;
+
+ const droppedFiles = await extractDroppedItems(ev).then(it => flattenDroppedFiles(it));
+ const confirm = await os.confirm({
+ type: 'info',
+ text: i18n.tsx._customEmojisManager._local._register.confirmUploadEmojisDescription({ count: droppedFiles.length }),
+ });
+ if (confirm.canceled) {
+ return;
+ }
+
+ const uploadedItems = Array.of<{ droppedFile: DroppedFile, driveFile: Misskey.entities.DriveFile }>();
+ try {
+ uploadedItems.push(
+ ...await os.promiseDialog(
+ Promise.all(
+ droppedFiles.map(async (it) => ({
+ droppedFile: it,
+ driveFile: await uploadFile(
+ it.file,
+ selectedFolderId.value,
+ it.file.name.replace(/\.[^.]+$/, ''),
+ keepOriginalUploading.value,
+ ),
+ }),
+ ),
+ ),
+ () => {
+ },
+ () => {
+ },
+ ),
+ );
+ } catch (err) {
+ // ダイアログã¯å…±é€šéƒ¨å“å´ã§å‡ºã¦ã„ã‚‹ã¯ãšãªã®ã§ä½•ã‚‚ã—ãªã„
+ return;
+ }
+
+ const items = uploadedItems.map(({ droppedFile, driveFile }) => {
+ const item = fromDriveFile(driveFile);
+ if (directoryToCategory.value) {
+ item.category = droppedFile.path
+ .replace(/^\//, '')
+ .replace(/\/[^/]+$/, '')
+ .replace(droppedFile.file.name, '');
+ }
+ return item;
+ });
+
+ gridItems.value.push(...items);
+}
+
+async function onFileSelectClicked() {
+ const driveFiles = await chooseFileFromPc(
+ true,
+ {
+ uploadFolder: selectedFolderId.value,
+ keepOriginal: keepOriginalUploading.value,
+ // æ‹¡å¼µå­ã¯æ¶ˆã™
+ nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''),
+ },
+ );
+
+ gridItems.value.push(...driveFiles.map(fromDriveFile));
+}
+
+async function onDriveSelectClicked() {
+ const driveFiles = await chooseFileFromDrive(true);
+ gridItems.value.push(...driveFiles.map(fromDriveFile));
+}
+
+function onGridEvent(event: GridEvent) {
+ switch (event.type) {
+ case 'cell-validation':
+ onGridCellValidation(event);
+ break;
+ case 'cell-value-change':
+ onGridCellValueChange(event);
+ break;
+ }
+}
+
+function onGridCellValidation(event: GridCellValidationEvent) {
+ registerButtonDisabled.value = event.all.filter(it => !it.valid).length > 0;
+}
+
+function onGridCellValueChange(event: GridCellValueChangeEvent) {
+ const { row, column, newValue } = event;
+ if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
+ gridItems.value[row.index][column.setting.bindTo] = newValue;
+ }
+}
+
+function fromDriveFile(it: Misskey.entities.DriveFile): GridItem {
+ return {
+ fileId: it.id,
+ url: it.url,
+ name: it.name.replace(/(\.[a-zA-Z0-9]+)+$/, ''),
+ host: '',
+ category: '',
+ aliases: '',
+ license: '',
+ isSensitive: it.isSensitive,
+ localOnly: false,
+ roleIdsThatCanBeUsedThisEmojiAsReaction: [],
+ type: it.type,
+ };
+}
+
+async function refreshUploadFolders() {
+ const result = await misskeyApi('drive/folders', {});
+ uploadFolders.value = Array.of<FolderItem>({ name: '-' }, ...result);
+}
+
+onMounted(async () => {
+ await refreshUploadFolders();
+});
+</script>
+
+<style module lang="scss">
+.violationRow {
+ background-color: var(--MI_THEME-infoWarnBg);
+}
+
+.uploadBox {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: auto;
+ border: 0.5px dotted var(--MI_THEME-accentedBg);
+ border-radius: var(--MI-radius);
+ background-color: var(--MI_THEME-accentedBg);
+ box-sizing: border-box;
+
+ &.dragOver {
+ cursor: copy;
+ }
+}
+
+.gridArea {
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+.footer {
+ background-color: var(--MI_THEME-bg);
+
+ position: sticky;
+ left:0;
+ bottom:0;
+ z-index: 1;
+ // stickyã§è¿½å¾“ã•ã›ã‚‹éƒ½åˆä¸Šã€ãƒ•ッター自身ã§paddingã‚’æŒã¤å¿…è¦ãŒã‚ã‚‹ãŸã‚ã€è¦ªè¦ç´ ã§ç”»ä¸€çš„ã«æŒ‡å®šã—ã¦ã„る分をãƒã‚¬ãƒ†ã‚£ãƒ–マージンã§ç›¸æ®ºã—ã¦ã„ã‚‹
+ margin-top: calc(var(--MI-margin) * -1);
+ margin-bottom: calc(var(--MI-margin) * -1);
+ padding-top: var(--MI-margin);
+ padding-bottom: var(--MI-margin);
+
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+}
+</style>
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.vue
new file mode 100644
index 0000000000..6e7e7e53e3
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.vue
@@ -0,0 +1,35 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+ <template #header>
+ <MkPageHeader v-model:tab="headerTab" :tabs="headerTabs" hideTitle thin/>
+ </template>
+ <XListComponent v-if="headerTab === 'list'" key="localList"/>
+ <MkSpacer v-else key="localRegister">
+ <XRegisterComponent/>
+ </MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue';
+import { i18n } from '@/i18n.js';
+import XListComponent from '@/pages/admin/custom-emojis-manager.local.list.vue';
+import XRegisterComponent from '@/pages/admin/custom-emojis-manager.local.register.vue';
+
+type PageMode = 'list' | 'register';
+
+const headerTab = ref<PageMode>('list');
+
+const headerTabs = computed(() => [{
+ key: 'list',
+ title: i18n.ts._customEmojisManager._local.tabTitleList,
+}, {
+ key: 'register',
+ title: i18n.ts._customEmojisManager._local.tabTitleRegister,
+}]);
+</script>
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.logs.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.logs.vue
new file mode 100644
index 0000000000..eef55a9f7e
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.logs.vue
@@ -0,0 +1,88 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+ <div v-if="logs.length > 0" style="display:flex; flex-direction: column; overflow-y: scroll; gap: 16px;">
+ <MkSwitch v-model="showingSuccessLogs">
+ <template #label>{{ i18n.ts._customEmojisManager._logs.showSuccessLogSwitch }}</template>
+ </MkSwitch>
+ <div>
+ <div v-if="filteredLogs.length > 0">
+ <MkGrid
+ :data="filteredLogs"
+ :settings="setupGrid()"
+ />
+ </div>
+ <div v-else>
+ {{ i18n.ts._customEmojisManager._logs.failureLogNothing }}
+ </div>
+ </div>
+ </div>
+ <div v-else>
+ {{ i18n.ts._customEmojisManager._logs.logNothing }}
+ </div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, toRefs } from 'vue';
+import { i18n } from '@/i18n.js';
+import MkGrid from '@/components/grid/MkGrid.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
+
+import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
+import type { GridSetting } from '@/components/grid/grid.js';
+
+function setupGrid(): GridSetting {
+ return {
+ row: {
+ showNumber: false,
+ selectable: false,
+ contextMenuFactory: (row, context) => {
+ return [
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.copySelectionRows,
+ icon: 'ti ti-copy',
+ action: () => copyGridDataToClipboard(logs, context),
+ },
+ ];
+ },
+ },
+ cols: [
+ { bindTo: 'failed', title: 'failed', type: 'boolean', editable: false, width: 50 },
+ { bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto' },
+ { bindTo: 'name', title: 'name', type: 'text', editable: false, width: 140 },
+ { bindTo: 'error', title: 'log', type: 'text', editable: false, width: 'auto' },
+ ],
+ cells: {
+ contextMenuFactory: (col, row, value, context) => {
+ return [
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._gridCommon.copySelectionRanges,
+ icon: 'ti ti-copy',
+ action: () => copyGridDataToClipboard(logs, context),
+ },
+ ];
+ },
+ },
+ };
+}
+
+const props = defineProps<{
+ logs: RequestLogItem[];
+}>();
+
+const { logs } = toRefs(props);
+const showingSuccessLogs = ref<boolean>(false);
+
+const filteredLogs = computed(() => {
+ const forceShowing = showingSuccessLogs.value;
+ return logs.value.filter((log) => forceShowing || log.failed);
+});
+</script>
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
new file mode 100644
index 0000000000..eecf8d7390
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
@@ -0,0 +1,503 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+ <template #default>
+ <div :class="$style.root" class="_gaps">
+ <MkFolder>
+ <template #icon><i class="ti ti-search"></i></template>
+ <template #label>{{ i18n.ts._customEmojisManager._gridCommon.searchSettings }}</template>
+ <template #caption>
+ {{ i18n.ts._customEmojisManager._gridCommon.searchSettingCaption }}
+ </template>
+
+ <div class="_gaps">
+ <div :class="[[spMode ? $style.searchAreaSp : $style.searchArea]]">
+ <MkInput
+ v-model="queryName"
+ type="search"
+ autocapitalize="off"
+ :class="[$style.col1, $style.row1]"
+ @enter="onSearchRequest"
+ >
+ <template #label>name</template>
+ </MkInput>
+ <MkInput
+ v-model="queryHost"
+ type="search"
+ autocapitalize="off"
+ :class="[$style.col2, $style.row1]"
+ @enter="onSearchRequest"
+ >
+ <template #label>host</template>
+ </MkInput>
+ <MkInput
+ v-model="queryLicense"
+ type="search"
+ autocapitalize="off"
+ :class="[$style.col3, $style.row1]"
+ @enter="onSearchRequest"
+ >
+ <template #label>license</template>
+ </MkInput>
+
+ <MkInput
+ v-model="queryUri"
+ type="search"
+ autocapitalize="off"
+ :class="[$style.col1, $style.row2]"
+ @enter="onSearchRequest"
+ >
+ <template #label>uri</template>
+ </MkInput>
+ <MkInput
+ v-model="queryPublicUrl"
+ type="search"
+ autocapitalize="off"
+ :class="[$style.col2, $style.row2]"
+ @enter="onSearchRequest"
+ >
+ <template #label>publicUrl</template>
+ </MkInput>
+ </div>
+
+ <hr>
+
+ <MkFolder :spacerMax="8" :spacerMin="8">
+ <template #icon><i class="ti ti-arrows-sort"></i></template>
+ <template #label>{{ i18n.ts._customEmojisManager._gridCommon.sortOrder }}</template>
+ <MkSortOrderEditor
+ :baseOrderKeyNames="gridSortOrderKeys"
+ :currentOrders="sortOrders"
+ @update="onSortOrderUpdate"
+ />
+ </MkFolder>
+
+ <MkInput
+ v-model="queryLimit"
+ type="number"
+ :max="100"
+ >
+ <template #label>{{ i18n.ts._customEmojisManager._gridCommon.searchLimit }}</template>
+ </MkInput>
+
+ <div :class="[[spMode ? $style.searchButtonsSp : $style.searchButtons]]">
+ <MkButton primary @click="onSearchRequest">
+ {{ i18n.ts.search }}
+ </MkButton>
+ <MkButton @click="onQueryResetButtonClicked">
+ {{ i18n.ts.reset }}
+ </MkButton>
+ </div>
+ </div>
+ </MkFolder>
+
+ <MkFolder>
+ <template #icon><i class="ti ti-notes"></i></template>
+ <template #label>{{ i18n.ts._customEmojisManager._gridCommon.registrationLogs }}</template>
+ <template #caption>
+ {{ i18n.ts._customEmojisManager._gridCommon.registrationLogsCaption }}
+ </template>
+ <XRegisterLogs :logs="requestLogs"/>
+ </MkFolder>
+
+ <component :is="loadingHandler.component.value" v-if="loadingHandler.showing.value"/>
+ <template v-else>
+ <div v-if="gridItems.length === 0" style="text-align: center">
+ {{ i18n.ts._customEmojisManager._local._list.emojisNothing }}
+ </div>
+
+ <template v-else>
+ <div v-if="gridItems.length > 0" :class="$style.gridArea">
+ <MkGrid :data="gridItems" :settings="setupGrid()" @event="onGridEvent"/>
+ </div>
+
+ <div :class="$style.footer">
+ <div>
+ <!-- レイアウト調整用ã®ã‚¹ãƒšãƒ¼ã‚¹ -->
+ </div>
+
+ <div :class="$style.center">
+ <MkPagingButtons :current="currentPage" :max="allPages" :buttonCount="5" @pageChanged="onPageChanged"/>
+ </div>
+
+ <div :class="$style.right">
+ <MkButton primary @click="onImportClicked">
+ {{
+ i18n.ts._customEmojisManager._remote.importEmojisButton
+ }} ({{ checkedItemsCount }})
+ </MkButton>
+ </div>
+ </div>
+ </template>
+ </template>
+ </div>
+ </template>
+</MkStickyContainer>
+</template>
+
+<script setup lang="ts">
+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 { 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 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 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';
+
+type GridItem = {
+ checked: boolean;
+ id: string;
+ url: string;
+ name: string;
+ host: string;
+}
+
+function setupGrid(): GridSetting {
+ const $style = useCssModule();
+
+ return {
+ row: {
+ // グリッドã®è¡Œæ•°ã‚’ã‚らã‹ã˜ã‚100行確ä¿ã™ã‚‹
+ minimumDefinitionCount: 100,
+ styleRules: [
+ {
+ // ãƒã‚§ãƒƒã‚¯ã•れãŸã‚‰èƒŒæ™¯è‰²ã‚’変ãˆã‚‹
+ condition: ({ row }) => gridItems.value[row.index].checked,
+ applyStyle: { className: $style.changedRow },
+ },
+ ],
+ contextMenuFactory: (row, context) => {
+ return [
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._remote.importSelectionRows,
+ icon: 'ti ti-download',
+ action: async () => {
+ const targets = context.rangedRows.map(it => gridItems.value[it.index]);
+ await importEmojis(targets);
+ },
+ },
+ ];
+ },
+ },
+ cols: [
+ { bindTo: 'checked', icon: 'ti-download', type: 'boolean', editable: true, width: 34 },
+ { bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto' },
+ { bindTo: 'name', title: 'name', type: 'text', editable: false, width: 'auto' },
+ { bindTo: 'host', title: 'host', type: 'text', editable: false, width: 'auto' },
+ { bindTo: 'license', title: 'license', type: 'text', editable: false, width: 200 },
+ { bindTo: 'uri', title: 'uri', type: 'text', editable: false, width: 'auto' },
+ { bindTo: 'publicUrl', title: 'publicUrl', type: 'text', editable: false, width: 'auto' },
+ ],
+ cells: {
+ contextMenuFactory: (col, row, value, context) => {
+ return [
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._remote.selectionRowDetail,
+ icon: 'ti ti-info-circle',
+ action: async () => {
+ const target = customEmojis.value[row.index];
+ const { dispose } = os.popup(MkRemoteEmojiEditDialog, {
+ emoji: {
+ id: target.id,
+ name: target.name,
+ host: target.host!,
+ license: target.license,
+ url: target.publicUrl,
+ },
+ }, {
+ done: () => {
+ dispose();
+ },
+ closed: () => {
+ dispose();
+ },
+ });
+ },
+ },
+ {
+ type: 'button',
+ text: i18n.ts._customEmojisManager._remote.importSelectionRangesRows,
+ icon: 'ti ti-download',
+ action: async () => {
+ const targets = context.rangedCells.map(it => gridItems.value[it.row.index]);
+ await importEmojis(targets);
+ },
+ },
+ ];
+ },
+ },
+ };
+}
+
+const loadingHandler = useLoading();
+
+const customEmojis = ref<Misskey.entities.EmojiDetailedAdmin[]>([]);
+const allPages = ref<number>(0);
+const currentPage = ref<number>(0);
+
+const queryName = ref<string | null>(null);
+const queryHost = ref<string | null>(null);
+const queryLicense = ref<string | null>(null);
+const queryUri = ref<string | null>(null);
+const queryPublicUrl = ref<string | null>(null);
+const queryLimit = ref<number>(25);
+const previousQuery = ref<string | undefined>(undefined);
+const sortOrders = ref<SortOrder<GridSortOrderKey>[]>([]);
+const requestLogs = ref<RequestLogItem[]>([]);
+
+const gridItems = ref<GridItem[]>([]);
+
+const spMode = computed(() => ['smartphone', 'tablet'].includes(deviceKind));
+const checkedItemsCount = computed(() => gridItems.value.filter(it => it.checked).length);
+
+function onSortOrderUpdate(_sortOrders: SortOrder<GridSortOrderKey>[]) {
+ sortOrders.value = _sortOrders;
+}
+
+async function onSearchRequest() {
+ await refreshCustomEmojis();
+}
+
+function onQueryResetButtonClicked() {
+ queryName.value = null;
+ queryHost.value = null;
+ queryLicense.value = null;
+ queryUri.value = null;
+ queryPublicUrl.value = null;
+}
+
+async function onPageChanged(pageNumber: number) {
+ currentPage.value = pageNumber;
+ await refreshCustomEmojis();
+}
+
+async function onImportClicked() {
+ const targets = gridItems.value.filter(it => it.checked);
+ await importEmojis(targets);
+}
+
+function onGridEvent(event: GridEvent) {
+ switch (event.type) {
+ case 'cell-value-change':
+ onGridCellValueChange(event);
+ break;
+ }
+}
+
+function onGridCellValueChange(event: GridCellValueChangeEvent) {
+ const { row, column, newValue } = event;
+ if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
+ gridItems.value[row.index][column.setting.bindTo] = newValue;
+ }
+}
+
+async function importEmojis(targets: GridItem[]) {
+ const confirm = await os.confirm({
+ type: 'info',
+ title: i18n.ts._customEmojisManager._remote.confirmImportEmojisTitle,
+ text: i18n.tsx._customEmojisManager._remote.confirmImportEmojisDescription({ count: targets.length }),
+ });
+
+ if (confirm.canceled) {
+ return;
+ }
+
+ const result = await os.promiseDialog(
+ Promise.all(
+ targets.map(item =>
+ misskeyApi(
+ 'admin/emoji/copy',
+ {
+ emojiId: item.id!,
+ })
+ .then(() => ({ item, success: true, err: undefined }))
+ .catch(err => ({ item, success: false, err })),
+ ),
+ ),
+ );
+ const failedItems = result.filter(it => !it.success);
+
+ if (failedItems.length > 0) {
+ await os.alert({
+ type: 'error',
+ title: i18n.ts.somethingHappened,
+ text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
+ });
+ }
+
+ requestLogs.value = result.map(it => ({
+ failed: !it.success,
+ url: it.item.url,
+ name: it.item.name,
+ error: it.err ? JSON.stringify(it.err) : undefined,
+ }));
+
+ await refreshCustomEmojis();
+}
+
+async function refreshCustomEmojis() {
+ const query: Misskey.entities.V2AdminEmojiListRequest['query'] = {
+ name: emptyStrToUndefined(queryName.value),
+ host: emptyStrToUndefined(queryHost.value),
+ license: emptyStrToUndefined(queryLicense.value),
+ uri: emptyStrToUndefined(queryUri.value),
+ publicUrl: emptyStrToUndefined(queryPublicUrl.value),
+ hostType: 'remote',
+ };
+
+ if (JSON.stringify(query) !== previousQuery.value) {
+ currentPage.value = 1;
+ }
+
+ const result = await loadingHandler.scope(() => misskeyApi('v2/admin/emoji/list', {
+ limit: queryLimit.value,
+ query: query,
+ page: currentPage.value,
+ sortKeys: sortOrders.value.map(({ key, direction }) => `${direction}${key}`) as never[],
+ }));
+
+ customEmojis.value = result.emojis;
+ allPages.value = result.allPages;
+ previousQuery.value = JSON.stringify(query);
+ gridItems.value = customEmojis.value.map(it => ({
+ checked: false,
+ id: it.id,
+ url: it.publicUrl,
+ name: it.name,
+ license: it.license,
+ host: it.host!,
+ }));
+}
+
+onMounted(async () => {
+ await refreshCustomEmojis();
+});
+</script>
+
+<style module lang="scss">
+.row1 {
+ grid-row: 1 / 2;
+}
+
+.row2 {
+ grid-row: 2 / 3;
+}
+
+.col1 {
+ grid-column: 1 / 2;
+}
+
+.col2 {
+ grid-column: 2 / 3;
+}
+
+.col3 {
+ grid-column: 3 / 4;
+}
+
+.root {
+ padding: 16px;
+}
+
+.changedRow {
+ background-color: var(--MI_THEME-infoBg);
+}
+
+.searchArea {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 16px;
+}
+
+.searchButtons {
+ display: flex;
+ justify-content: flex-end;
+ align-items: flex-end;
+ gap: 8px;
+}
+
+.searchButtonsSp {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+}
+
+.searchAreaSp {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.gridArea {
+ padding-top: 8px;
+ padding-bottom: 8px;
+}
+
+.pages {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ button {
+ background-color: var(--MI_THEME-buttonBg);
+ border-radius: 9999px;
+ border: none;
+ margin: 0 4px;
+ padding: 8px;
+ }
+}
+
+.footer {
+ background-color: var(--MI_THEME-bg);
+
+ position: sticky;
+ left:0;
+ bottom:0;
+ z-index: 1;
+ // stickyã§è¿½å¾“ã•ã›ã‚‹éƒ½åˆä¸Šã€ãƒ•ッター自身ã§paddingã‚’æŒã¤å¿…è¦ãŒã‚ã‚‹ãŸã‚ã€è¦ªè¦ç´ ã§ç”»ä¸€çš„ã«æŒ‡å®šã—ã¦ã„る分をãƒã‚¬ãƒ†ã‚£ãƒ–マージンã§ç›¸æ®ºã—ã¦ã„ã‚‹
+ margin-top: calc(var(--MI-margin) * -1);
+ margin-bottom: calc(var(--MI-margin) * -1);
+ padding-top: var(--MI-margin);
+ padding-bottom: var(--MI-margin);
+
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 8px;
+
+ & .center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ & .right {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ }
+}
+</style>
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
new file mode 100644
index 0000000000..f62304277a
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts
@@ -0,0 +1,160 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { delay, http, HttpResponse } from 'msw';
+import { StoryObj } from '@storybook/vue3';
+import { entities } from 'misskey-js';
+import { commonHandlers } from '../../../.storybook/mocks.js';
+import { emoji } from '../../../.storybook/fakes.js';
+import { fakeId } from '../../../.storybook/fake-utils.js';
+import custom_emojis_manager2 from './custom-emojis-manager2.vue';
+
+function createRender(params: {
+ emojis: entities.EmojiDetailedAdmin[];
+}) {
+ const storedEmojis: entities.EmojiDetailedAdmin[] = [...params.emojis];
+ const storedDriveFiles: entities.DriveFile[] = [];
+
+ return {
+ render(args) {
+ return {
+ components: {
+ custom_emojis_manager2,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '<custom_emojis_manager2 v-bind="props" />',
+ };
+ },
+ args: {
+
+ },
+ parameters: {
+ layout: 'fullscreen',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ http.post('/api/v2/admin/emoji/list', async ({ request }) => {
+ await delay(100);
+
+ const bodyStream = request.body as ReadableStream;
+ const body = await new Response(bodyStream).json() as entities.V2AdminEmojiListRequest;
+
+ const emojis = storedEmojis;
+ const limit = body.limit ?? 10;
+ const page = body.page ?? 1;
+ const result = emojis.slice((page - 1) * limit, page * limit);
+
+ return HttpResponse.json({
+ emojis: result,
+ count: Math.min(emojis.length, limit),
+ allCount: emojis.length,
+ allPages: Math.ceil(emojis.length / limit),
+ });
+ }),
+ http.post('/api/drive/folders', () => {
+ return HttpResponse.json([]);
+ }),
+ http.post('/api/drive/files', () => {
+ return HttpResponse.json(storedDriveFiles);
+ }),
+ http.post('/api/drive/files/create', async ({ request }) => {
+ const data = await request.formData();
+ const file = data.get('file');
+ if (!file || !(file instanceof File)) {
+ return HttpResponse.json({ error: 'file is required' }, {
+ status: 400,
+ });
+ }
+
+ // FIXME: ファイルã®ãƒã‚¤ãƒŠãƒªã«0xEF 0xBF 0xBDãŒæ··å…¥ã—ã¦ã—ã¾ã„ã€ã†ã¾ãç”»åƒãƒ•ァイルã¨ã—ã¦è¡¨ç¤ºã§ããªã„å•題ãŒã‚ã‚‹
+ const base64 = await new Promise<string>((resolve) => {
+ const reader = new FileReader();
+ reader.onload = () => {
+ resolve(reader.result as string);
+ };
+ reader.readAsDataURL(new Blob([file], { type: 'image/webp' }));
+ });
+
+ const driveFile: entities.DriveFile = {
+ id: fakeId(file.name),
+ createdAt: new Date().toISOString(),
+ name: file.name,
+ type: file.type,
+ md5: '',
+ size: file.size,
+ isSensitive: false,
+ blurhash: null,
+ properties: {},
+ url: base64,
+ thumbnailUrl: null,
+ comment: null,
+ folderId: null,
+ folder: null,
+ userId: null,
+ user: null,
+ };
+
+ storedDriveFiles.push(driveFile);
+
+ return HttpResponse.json(driveFile);
+ }),
+ http.post('api/admin/emoji/add', async ({ request }) => {
+ await delay(100);
+
+ const bodyStream = request.body as ReadableStream;
+ const body = await new Response(bodyStream).json() as entities.AdminEmojiAddRequest;
+
+ const fileId = body.fileId;
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const file = storedDriveFiles.find(f => f.id === fileId)!;
+
+ const em = emoji({
+ id: fakeId(file.name),
+ name: body.name,
+ publicUrl: file.url,
+ originalUrl: file.url,
+ type: file.type,
+ aliases: body.aliases,
+ category: body.category ?? undefined,
+ license: body.license ?? undefined,
+ localOnly: body.localOnly,
+ isSensitive: body.isSensitive,
+ });
+ storedEmojis.push(em);
+
+ return HttpResponse.json(null);
+ }),
+ ],
+ },
+ },
+ } satisfies StoryObj<typeof custom_emojis_manager2>;
+}
+
+export const Default = createRender({
+ emojis: [],
+});
+
+export const List10 = createRender({
+ emojis: Array.from({ length: 10 }, (_, i) => emoji({ name: `emoji_${i}` }, i.toString())),
+});
+
+export const List100 = createRender({
+ emojis: Array.from({ length: 100 }, (_, i) => emoji({ name: `emoji_${i}` }, i.toString())),
+});
+
+export const List1000 = createRender({
+ emojis: Array.from({ length: 1000 }, (_, i) => emoji({ name: `emoji_${i}` }, i.toString())),
+});
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager2.vue b/packages/frontend/src/pages/admin/custom-emojis-manager2.vue
new file mode 100644
index 0000000000..fb930064ff
--- /dev/null
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager2.vue
@@ -0,0 +1,51 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+ <MkStickyContainer>
+ <template #header>
+ <MkPageHeader v-model:tab="headerTab" :tabs="headerTabs"/>
+ </template>
+ <XGridLocalComponent v-if="headerTab === 'local'" :class="$style.local"/>
+ <XGridRemoteComponent v-else/>
+ </MkStickyContainer>
+</div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue';
+import { i18n } from '@/i18n.js';
+import { definePageMetadata } from '@/scripts/page-metadata.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';
+import MkStickyContainer from '@/components/global/MkStickyContainer.vue';
+
+type PageMode = 'local' | 'remote';
+
+const headerTab = ref<PageMode>('local');
+
+const headerTabs = computed(() => [{
+ key: 'local',
+ title: i18n.ts.local,
+}, {
+ key: 'remote',
+ title: i18n.ts.remote,
+}]);
+
+definePageMetadata(computed(() => ({
+ title: i18n.ts.customEmojis,
+ icon: 'ti ti-icons',
+ needWideArea: true,
+})));
+</script>
+
+<style lang="css" module>
+.local {
+ height: calc(100dvh - var(--MI-stickyTop) - var(--MI-stickyBottom));
+ overflow: clip;
+}
+</style>
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 6cdf0eda7a..cbd0d12dcc 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -35,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
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';
@@ -56,7 +57,7 @@ const indexInfo = {
provide('shouldOmitHeaderTitle', false);
-const INFO = ref(indexInfo);
+const INFO = ref<PageMetadata>(indexInfo);
const childInfo = ref<null | PageMetadata>(null);
const narrow = ref(false);
const view = ref(null);
@@ -91,7 +92,7 @@ const ro = new ResizeObserver((entries, observer) => {
narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
});
-const menuDef = computed(() => [{
+const menuDef = computed<SuperMenuDef[]>(() => [{
title: i18n.ts.quickAction,
items: [{
type: 'button',
@@ -99,7 +100,7 @@ const menuDef = computed(() => [{
text: i18n.ts.lookup,
action: adminLookup,
}, ...(instance.disableRegistration ? [{
- type: 'button',
+ type: 'button' as const,
icon: 'ti ti-user-plus',
text: i18n.ts.createInviteCode,
action: invite,
@@ -137,6 +138,11 @@ const menuDef = computed(() => [{
to: '/admin/emojis',
active: currentPage.value?.route.name === 'emojis',
}, {
+ icon: 'ti ti-icons',
+ text: i18n.ts.customEmojis + '(beta)',
+ to: '/admin/emojis2',
+ active: currentPage.value?.route.name === 'emojis2',
+ }, {
icon: 'ti ti-sparkles',
text: i18n.ts.avatarDecorations,
to: '/admin/avatar-decorations',
@@ -343,12 +349,14 @@ defineExpose({
height: 100%;
> .nav {
+ position: sticky;
+ top: 0;
width: 32%;
max-width: 280px;
box-sizing: border-box;
border-right: solid 0.5px var(--MI_THEME-divider);
overflow: auto;
- height: 100%;
+ height: 100dvh;
}
> .main {
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 5d896db98c..6bab594d36 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -641,7 +641,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="role.policies.avatarDecorationLimit.useDefault" :readonly="readonly">
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
</MkSwitch>
- <MkInput v-model="role.policies.avatarDecorationLimit.value" type="number" :min="0">
+ <MkInput v-model="role.policies.avatarDecorationLimit.value" type="number" :min="0" :max="16" @update:modelValue="updateAvatarDecorationLimit">
<template #label>{{ i18n.ts._role._options.avatarDecorationLimit }}</template>
</MkInput>
<MkRange v-model="role.policies.avatarDecorationLimit.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 : ''">
@@ -757,6 +757,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { watch, ref, computed } from 'vue';
import { throttle } from 'throttle-debounce';
+import { ROLE_POLICIES } from '@@/js/const.js';
import RolesEditorFormula from './RolesEditorFormula.vue';
import MkInput from '@/components/MkInput.vue';
import MkColorInput from '@/components/MkColorInput.vue';
@@ -767,7 +768,6 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkRange from '@/components/MkRange.vue';
import FormSlot from '@/components/form/slot.vue';
import { i18n } from '@/i18n.js';
-import { ROLE_POLICIES } from '@@/js/const.js';
import { instance } from '@/instance.js';
import { deepClone } from '@/scripts/clone.js';
@@ -793,6 +793,12 @@ for (const ROLE_POLICY of ROLE_POLICIES) {
}
}
+function updateAvatarDecorationLimit(value: string | number) {
+ const numValue = Number(value);
+ const limited = Math.min(16, Math.max(0, numValue));
+ role.value.policies.avatarDecorationLimit.value = limited;
+}
+
const rolePermission = computed({
get: () => role.value.isAdministrator ? 'administrator' : role.value.isModerator ? 'moderator' : 'normal',
set: (val) => {
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 036f18fe0d..f67b1cd582 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -239,7 +239,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFolder v-if="matchQuery([i18n.ts._role._options.avatarDecorationLimit, 'avatarDecorationLimit'])">
<template #label>{{ i18n.ts._role._options.avatarDecorationLimit }}</template>
<template #suffix>{{ policies.avatarDecorationLimit }}</template>
- <MkInput v-model="policies.avatarDecorationLimit" type="number" :min="0">
+ <MkInput v-model="avatarDecorationLimit" type="number" :min="0" :max="16" @update:modelValue="updateAvatarDecorationLimit">
</MkInput>
</MkFolder>
@@ -334,6 +334,17 @@ for (const ROLE_POLICY of ROLE_POLICIES) {
policies[ROLE_POLICY] = instance.policies[ROLE_POLICY];
}
+const avatarDecorationLimit = computed({
+ get: () => Math.min(16, Math.max(0, policies.avatarDecorationLimit)),
+ set: (value) => {
+ policies.avatarDecorationLimit = Math.min(Number(value), 16);
+ },
+});
+
+function updateAvatarDecorationLimit(value: string | number) {
+ avatarDecorationLimit.value = Number(value);
+}
+
function matchQuery(keywords: string[]): boolean {
if (baseRoleQ.value.trim().length === 0) return true;
return keywords.some(keyword => keyword.toLowerCase().includes(baseRoleQ.value.toLowerCase()));
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 9cd2546312..fb99379a0a 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkNotes :pagination="featuredPagination"/>
</div>
<div v-else-if="tab === 'search'" key="search">
- <div class="_gaps">
+ <div v-if="notesSearchAvailable" class="_gaps">
<div>
<MkInput v-model="searchQuery" @enter="search()">
<template #prefix><i class="ti ti-search"></i></template>
@@ -54,6 +54,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
</div>
+ <div v-else>
+ <MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
+ </div>
</div>
</MkHorizontalSwipe>
</MkSpacer>
@@ -94,6 +97,7 @@ 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 { miLocalStorage } from '@/local-storage.js';
import { useRouter } from '@/router/supplier.js';
import { deepMerge } from '@/scripts/merge.js';
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index bde1650754..6830c1ace4 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
- <MkSpacer :contentMax="700">
+ <MkSpacer :contentMax="1200">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <div v-if="tab === 'search'" key="search">
+ <div v-if="tab === 'search'" key="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>
@@ -27,23 +27,31 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-if="tab === 'featured'" key="featured">
<MkPagination v-slot="{items}" :pagination="featuredPagination">
- <MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+ <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">
<MkPagination v-slot="{items}" :pagination="favoritesPagination">
- <MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+ <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">
<MkPagination v-slot="{items}" :pagination="followingPagination">
- <MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+ <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">
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="ownedPagination">
- <MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+ <div :class="$style.root">
+ <MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
+ </div>
</MkPagination>
</div>
</MkHorizontalSwipe>
@@ -85,6 +93,7 @@ onMounted(() => {
const featuredPagination = {
endpoint: 'channels/featured' as const,
+ limit: 10,
noPaging: true,
};
const favoritesPagination = {
@@ -157,3 +166,17 @@ definePageMetadata(() => ({
icon: 'ti ti-device-tv',
}));
</script>
+
+<style lang="scss" module>
+.searchRoot {
+ width: 100%;
+ max-width: 700px;
+ margin: 0 auto;
+}
+
+.root {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
+ gap: var(--MI-margin);
+}
+</style>
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 716cd9a73f..240f395e04 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -46,9 +46,10 @@ 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 { getServerContext } from '@/server-context.js';
+import { assertServerContext, serverContext } from '@/server-context.js';
-const CTX_CLIP = getServerContext('clip');
+// contextã¯éžãƒ­ã‚°ã‚¤ãƒ³çŠ¶æ…‹ã®æƒ…å ±ã—ã‹ãªã„ãŸã‚ログイン時ã¯åˆ©ç”¨ã§ããªã„
+const CTX_CLIP = !$i && assertServerContext(serverContext, 'clip') ? serverContext.clip : null;
const props = defineProps<{
clipId: string,
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index 850c1c5eb0..107a0d760c 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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/${emoji.name}.webp`" class="img" :alt="emoji.name"/>
+ <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>
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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="`/emoji/${emoji.name}@${emoji.host}.webp`" class="img" :alt="emoji.name"/>
+ <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>
@@ -78,11 +78,13 @@ import { computed, defineAsyncComponent, ref, shallowRef } 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 * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
+import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -161,6 +163,19 @@ const edit = (emoji) => {
});
};
+const detailRemoteEmoji = (emoji) => {
+ const { dispose } = os.popup(MkRemoteEmojiEditDialog, {
+ emoji: emoji,
+ }, {
+ done: () => {
+ dispose();
+ },
+ closed: () => {
+ dispose();
+ },
+ });
+};
+
const importEmoji = (emoji) => {
os.apiWithDialog('admin/emoji/copy', {
emojiId: emoji.id,
@@ -171,13 +186,15 @@ const remoteMenu = (emoji, ev: MouseEvent) => {
os.popupMenu([{
type: 'label',
text: ':' + emoji.name + ':',
- },
- {
+ }, {
+ text: i18n.ts.details,
+ icon: 'ti ti-info-circle',
+ action: () => { detailRemoteEmoji(emoji); },
+ }, {
text: i18n.ts.import,
icon: 'ti ti-plus',
action: () => { importEmoji(emoji); },
- },
- {
+ }, {
text: i18n.ts.delete,
icon: 'ph-trash ph-bold ph-lg',
action: () => {
diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue
index d3e9ca0dcf..c8e6dfb05a 100644
--- a/packages/frontend/src/pages/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/emoji-edit-dialog.vue
@@ -118,7 +118,7 @@ watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
}, { immediate: true });
-const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
+const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? props.emoji.url : null);
async function changeImage(ev: Event) {
file.value = await selectFile(ev.currentTarget ?? ev.target, null);
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index 2550100a42..0d2c6217d4 100644
--- a/packages/frontend/src/pages/explore.users.vue
+++ b/packages/frontend/src/pages/explore.users.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkSpacer :contentMax="1200">
- <MkTab v-model="origin" style="margin-bottom: var(--MI-margin);">
+ <MkTab v-if="instance.federation !== 'none'" v-model="origin" style="margin-bottom: var(--MI-margin);">
<option value="local">{{ i18n.ts.local }}</option>
<option value="remote">{{ i18n.ts.remote }}</option>
</MkTab>
@@ -69,6 +69,7 @@ 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 { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index e85d2c29c1..ab060587c5 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -59,18 +59,18 @@ async function onAccept(token: string) {
name: props.name,
iconUrl: props.icon,
permission: _permissions.value,
- }, token).catch(() => {
+ }, token).then(() => {
+ if (props.callback && props.callback !== '') {
+ 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();
+ } else {
+ authRoot.value?.showUI('success');
+ }
+ }).catch(() => {
authRoot.value?.showUI('failed');
});
-
- if (props.callback && props.callback !== '') {
- 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();
- } else {
- authRoot.value?.showUI('success');
- }
}
function onDeny() {
@@ -117,5 +117,6 @@ definePageMetadata(() => ({
border-radius: var(--MI-radius);
background-color: var(--MI_THEME-panel);
overflow-x: scroll;
+ white-space: nowrap;
}
</style>
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index 737b0eea4c..b70bff052a 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -50,6 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
+import { host } from '@@/js/config.js';
import type { Paging } from '@/components/MkPagination.vue';
import MkNotes from '@/components/MkNotes.vue';
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
@@ -61,9 +62,11 @@ import { dateString } from '@/filters/date.js';
import MkClipPreview from '@/components/MkClipPreview.vue';
import { defaultStore } from '@/store.js';
import { pleaseLogin } from '@/scripts/please-login.js';
-import { getServerContext } from '@/server-context.js';
+import { serverContext, assertServerContext } from '@/server-context.js';
+import { $i } from '@/account.js';
-const CTX_NOTE = getServerContext('note');
+// contextã¯éžãƒ­ã‚°ã‚¤ãƒ³çŠ¶æ…‹ã®æƒ…å ±ã—ã‹ãªã„ãŸã‚ログイン時ã¯åˆ©ç”¨ã§ããªã„
+const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
const MkNoteDetailed = defineAsyncComponent(() =>
(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNoteDetailed.vue') :
@@ -146,7 +149,12 @@ function fetchNote() {
}).catch(err => {
if (err.id === '8e75455b-738c-471d-9f80-62693f33372e') {
pleaseLogin({
+ path: '/',
message: i18n.ts.thisContentsAreMarkedAsSigninRequiredByAuthor,
+ openOnRemote: {
+ type: 'lookup',
+ url: `https://${host}/notes/${props.noteId}`,
+ },
});
}
error.value = err;
diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue
index d64537d289..73b2839a44 100644
--- a/packages/frontend/src/pages/search.note.vue
+++ b/packages/frontend/src/pages/search.note.vue
@@ -13,16 +13,20 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header>{{ i18n.ts.options }}</template>
<div class="_gaps_m">
- <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">
- <template #prefix><i class="ti ti-server"></i></template>
- </MkInput>
+ <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">
+ <template #prefix><i class="ti ti-server"></i></template>
+ </MkInput>
+ </template>
+
<MkSwitch v-model="order">Sort by newest to oldest</MkSwitch>
+
<MkSelect v-model="filetype" small>
<template #label>File Type</template>
<option :value="null">None</option>
@@ -114,7 +118,7 @@ setHostSelectWithInput(hostInput.value, undefined);
watch(hostInput, setHostSelectWithInput);
const searchHost = computed(() => {
- if (hostSelect.value === 'local') return '.';
+ if (hostSelect.value === 'local' || instance.federation === 'none') return '.';
if (hostSelect.value === 'specified') return hostInput.value;
return null;
});
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index a355c0eeaa..772ee91d63 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter.prevent="search">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
- <MkRadios v-model="searchOrigin" @update:modelValue="search()">
+ <MkRadios v-if="instance.federation !== 'none'" v-model="searchOrigin" @update:modelValue="search()">
<option value="combined">{{ i18n.ts.all }}</option>
<option value="local">{{ i18n.ts.local }}</option>
<option value="remote">{{ i18n.ts.remote }}</option>
@@ -33,6 +33,7 @@ import MkInput from '@/components/MkInput.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
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';
@@ -118,7 +119,7 @@ async function search() {
limit: 10,
params: {
query: query,
- origin: searchOrigin.value,
+ origin: instance.federation === 'none' ? 'local' : searchOrigin.value,
},
};
diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index 97e960675f..c2588736b3 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -12,7 +12,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton @click="init"><i class="ti ti-refresh"></i> {{ i18n.ts.reloadAccountsList }}</MkButton>
</div>
- <MkUserCardMini v-for="user in accounts" :key="user.id" :user="user" :class="$style.user" @click.prevent="menu(user, $event)"/>
+ <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>
</div>
</FormSuspense>
</div>
@@ -29,9 +38,10 @@ import { getAccounts, removeAccount as _removeAccount, login, $i, getAccountWith
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import { MenuItem } from '@/types/menu';
const storedAccounts = ref<{ id: string, token: string }[] | null>(null);
-const accounts = ref<Misskey.entities.UserDetailed[]>([]);
+const accounts = ref(new Map<string, Misskey.entities.UserDetailed | null>());
const init = async () => {
getAccounts().then(accounts => {
@@ -41,21 +51,35 @@ const init = async () => {
userIds: storedAccounts.value.map(x => x.id),
});
}).then(response => {
- accounts.value = 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 menu(account: Misskey.entities.UserDetailed, ev: MouseEvent) {
- os.popupMenu([{
- text: i18n.ts.switch,
- icon: 'ti ti-switch-horizontal',
- action: () => switchAccount(account),
- }, {
- text: i18n.ts.logout,
- icon: 'ti ti-trash',
- danger: true,
- action: () => removeAccount(account),
- }], ev.currentTarget ?? ev.target);
+function menu(account: Misskey.entities.UserDetailed | string, 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),
+ }];
+ }
+
+ os.popupMenu(menu, ev.currentTarget ?? ev.target);
}
function addAccount(ev: MouseEvent) {
@@ -68,9 +92,9 @@ function addAccount(ev: MouseEvent) {
}], ev.currentTarget ?? ev.target);
}
-async function removeAccount(account: Misskey.entities.UserDetailed) {
- await _removeAccount(account.id);
- accounts.value = accounts.value.filter(x => x.id !== account.id);
+async function removeAccount(id: string) {
+ await _removeAccount(id);
+ accounts.value.delete(id);
}
function addExistingAccount() {
@@ -90,9 +114,9 @@ function createAccount() {
});
}
-async function switchAccount(account: Misskey.entities.UserDetailed) {
+async function switchAccount(id: string) {
const fetchedAccounts = await getAccounts();
- const token = fetchedAccounts.find(x => x.id === account.id)!.token;
+ const token = fetchedAccounts.find(x => x.id === id)!.token;
switchAccountWithToken(token);
}
@@ -112,6 +136,49 @@ definePageMetadata(() => ({
<style lang="scss" module>
.user {
- cursor: pointer;
+ cursor: pointer;
+}
+
+.unknownUser {
+ display: flex;
+ align-items: center;
+ text-align: start;
+ padding: 16px;
+ background: var(--MI_THEME-panel);
+ border-radius: 8px;
+ font-size: 0.9em;
+}
+
+.unknownUserAvatarMock {
+ display: block;
+ width: 34px;
+ height: 34px;
+ line-height: 34px;
+ text-align: center;
+ font-size: 16px;
+ margin-right: 12px;
+ background-color: color-mix(in srgb, var(--MI_THEME-fg), transparent 85%);
+ color: color-mix(in srgb, var(--MI_THEME-fg), transparent 25%);
+ border-radius: 50%;
+}
+
+.unknownUserTitle {
+ display: block;
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 18px;
+}
+
+.unknownUserSub {
+ display: block;
+ width: 100%;
+ font-size: 95%;
+ opacity: 0.7;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 16px;
}
</style>
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index cb0451c8b4..f8a0575a77 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -106,7 +106,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="limitWidthOfReaction">{{ i18n.ts.limitWidthOfReaction }}</MkSwitch>
</div>
- <MkSelect v-model="instanceTicker">
+ <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>
@@ -357,6 +357,7 @@ 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';
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 552b4ee028..b7bf8c5dc1 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -43,7 +43,7 @@ const indexInfo = {
icon: 'ti ti-settings',
hideHeader: true,
};
-const INFO = ref(indexInfo);
+const INFO = ref<PageMetadata>(indexInfo);
const el = shallowRef<HTMLElement | null>(null);
const childInfo = ref<null | PageMetadata>(null);
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index 82aeb6063f..d6ee45e074 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -9,17 +9,24 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #icon><i class="ph-envelope ph-bold ph-lg"></i></template>
<template #label>{{ i18n.ts.wordMute }}</template>
- <XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
+ <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>
<MkFolder>
<template #icon><i class="ph-x-square ph-bold ph-lg"></i></template>
<template #label>{{ i18n.ts.hardWordMute }}</template>
- <XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
+ <div class="_gaps_m">
+ <MkInfo>{{ i18n.ts.hardWordMuteDescription }}</MkInfo>
+ <XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
+ </div>
</MkFolder>
- <MkFolder>
+ <MkFolder v-if="instance.federation !== 'none'">
<template #icon><i class="ti ti-planet-off"></i></template>
<template #label>{{ i18n.ts.instanceMute }}</template>
@@ -126,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
import XInstanceMute from './mute-block.instance-mute.vue';
import XWordMute from './mute-block.word-mute.vue';
import MkPagination from '@/components/MkPagination.vue';
@@ -135,9 +142,13 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import * as os from '@/os.js';
-import { infoImageUrl } from '@/instance.js';
+import { instance, infoImageUrl } from '@/instance.js';
import { signinRequired } from '@/account.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';
const $i = signinRequired();
@@ -160,6 +171,14 @@ const expandedRenoteMuteItems = ref([]);
const expandedMuteItems = ref([]);
const expandedBlockItems = ref([]);
+const showSoftWordMutedWord = computed(defaultStore.makeGetterSetter('showSoftWordMutedWord'));
+
+watch([
+ showSoftWordMutedWord,
+], async () => {
+ await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
+});
+
async function unrenoteMute(user, ev) {
os.popupMenu([{
text: i18n.ts.renoteUnmute,
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index 790f9e44e2..ce4c229a3a 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #caption>
<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div>
- <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
+ <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>
</MkSwitch>
@@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #caption>
<div>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</div>
- <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
+ <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>
@@ -129,7 +129,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #caption>
<div>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</div>
- <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
+ <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>
@@ -171,6 +171,7 @@ import MkFolder from '@/components/MkFolder.vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
+import { instance } from '@/instance.js';
import { signinRequired } from '@/account.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import FormSlot from '@/components/form/slot.vue';
@@ -224,7 +225,7 @@ watch([makeNotesFollowersOnlyBefore, makeNotesHiddenBefore], () => {
});
async function update_requireSigninToViewContents(value: boolean) {
- if (value) {
+ if (value === true && instance.federation !== 'none') {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.ts.acknowledgeNotesAndEnable,
diff --git a/packages/frontend/src/pages/settings/statusbar.statusbar.vue b/packages/frontend/src/pages/settings/statusbar.statusbar.vue
index 67943524ef..140b6beb14 100644
--- a/packages/frontend/src/pages/settings/statusbar.statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.statusbar.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSelect v-model="statusbar.type" placeholder="Please select">
<template #label>{{ i18n.ts.type }}</template>
<option value="rss">RSS</option>
- <option value="federation">Federation</option>
+ <option v-if="instance.federation !== 'none'" value="federation">Federation</option>
<option value="userList">User list timeline</option>
</MkSelect>
@@ -96,6 +96,7 @@ 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';
const props = defineProps<{
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index a530f4b5d6..e49d6af470 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { watch, ref, computed } from 'vue';
-import { toUnicode } from 'punycode/';
+import { toUnicode } from 'punycode.js';
import tinycolor from 'tinycolor2';
import { v4 as uuid } from 'uuid';
import JSON5 from 'json5';
diff --git a/packages/frontend/src/pages/user/files.vue b/packages/frontend/src/pages/user/files.vue
new file mode 100644
index 0000000000..b6c7c1c777
--- /dev/null
+++ b/packages/frontend/src/pages/user/files.vue
@@ -0,0 +1,56 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+ <MkSpacer :contentMax="1100">
+ <div :class="$style.root">
+ <MkPagination v-slot="{items}" :pagination="pagination">
+ <div :class="$style.stream">
+ <MkNoteMediaGrid v-for="note in items" :note="note" square/>
+ </div>
+ </MkPagination>
+ </div>
+ </MkSpacer>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as Misskey from 'misskey-js';
+
+import MkNoteMediaGrid from '@/components/MkNoteMediaGrid.vue';
+import MkPagination from '@/components/MkPagination.vue';
+
+const props = defineProps<{
+ user: Misskey.entities.UserDetailed;
+}>();
+
+const pagination = {
+ endpoint: 'users/notes' as const,
+ limit: 15,
+ params: computed(() => ({
+ userId: props.user.id,
+ withFiles: true,
+ })),
+};
+</script>
+
+<style lang="scss" module>
+.root {
+ padding: 8px;
+}
+
+.stream {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ gap: var(--MI-marginHalf);
+}
+
+@media screen and (min-width: 600px) {
+ .stream {
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+ }
+
+}
+</style>
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 5565555ca4..645c3b3c3c 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -138,7 +138,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo v-if="user.pinnedNotes.length === 0 && $i?.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
<template v-if="narrow">
<MkLazy>
- <XFiles :key="user.id" :user="user" :collapsed="true"/>
+ <XFiles :key="user.id" :user="user" :collapsed="true" @unfold="emit('unfoldFiles')"/>
</MkLazy>
<MkLazy>
<XActivity :key="user.id" :user="user" :collapsed="true"/>
@@ -180,7 +180,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
- <XFiles :key="user.id" :user="user"/>
+ <XFiles :key="user.id" :user="user" @unfold="emit('unfoldFiles')"/>
<XActivity :key="user.id" :user="user"/>
<XListenBrainz v-if="user.listenbrainz && listenbrainzdata" :key="user.id" :user="user"/>
</div>
@@ -242,7 +242,6 @@ function calcAge(birthdate: string): number {
const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
const XListenBrainz = defineAsyncComponent(() => import('./index.listenbrainz.vue'));
-//const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed;
@@ -252,6 +251,10 @@ const props = withDefaults(defineProps<{
disableNotes: false,
});
+const emit = defineEmits<{
+ (ev: 'unfoldFiles'): void;
+}>();
+
const router = useRouter();
const user = ref(props.user);
diff --git a/packages/frontend/src/pages/user/index.files.vue b/packages/frontend/src/pages/user/index.files.vue
index 7fe90da865..44e35e3479 100644
--- a/packages/frontend/src/pages/user/index.files.vue
+++ b/packages/frontend/src/pages/user/index.files.vue
@@ -4,30 +4,15 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<MkContainer :max-height="300" :foldable="true" :expanded="!collapsed">
+<MkContainer :max-height="300" :foldable="true" :expanded="!collapsed" :onUnfold="unfoldContainer">
<template #icon><i class="ti ti-photo"></i></template>
<template #header>{{ i18n.ts.files }}</template>
<div :class="$style.root">
<MkLoading v-if="fetching"/>
- <div v-if="!fetching && files.length > 0" :class="$style.stream">
- <template v-for="file in files" :key="file.note.id + file.file.id">
- <div v-if="file.file.isSensitive && !showingFiles.includes(file.file.id)" :class="$style.img" @click="showingFiles.push(file.file.id)">
- <!-- TODO: ç”»åƒä»¥å¤–ã®ãƒ•ァイルã«å¯¾å¿œ -->
- <ImgWithBlurhash :class="$style.sensitiveImg" :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name" :forceBlurhash="true"/>
- <div :class="$style.sensitive">
- <div>
- <div><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</div>
- <div>{{ i18n.ts.clickToShow }}</div>
- </div>
- </div>
- </div>
- <MkA v-else :class="$style.img" :to="notePage(file.note)">
- <!-- TODO: ç”»åƒä»¥å¤–ã®ãƒ•ァイルã«å¯¾å¿œ -->
- <ImgWithBlurhash :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name"/>
- </MkA>
- </template>
+ <div v-if="!fetching && notes.length > 0" :class="$style.stream">
+ <MkNoteMediaGrid v-for="note in notes" :note="note"/>
</div>
- <p v-if="!fetching && files.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
+ <p v-if="!fetching && notes.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
</div>
</MkContainer>
</template>
@@ -35,13 +20,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { getStaticImageUrl } from '@/scripts/media-proxy.js';
-import { notePage } from '@/filters/note.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
-import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
-import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
+import MkNoteMediaGrid from '@/components/MkNoteMediaGrid.vue';
const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed;
@@ -50,33 +32,25 @@ const props = withDefaults(defineProps<{
collapsed: false,
});
+const emit = defineEmits<{
+ (ev: 'unfold'): void;
+}>();
+
const fetching = ref(true);
-const files = ref<{
- note: Misskey.entities.Note;
- file: Misskey.entities.DriveFile;
-}[]>([]);
-const showingFiles = ref<string[]>([]);
+const notes = ref<Misskey.entities.Note[]>([]);
-function thumbnail(image: Misskey.entities.DriveFile): string {
- return defaultStore.state.disableShowingAnimatedImages
- ? getStaticImageUrl(image.url)
- : image.thumbnailUrl;
+function unfoldContainer(): boolean {
+ emit('unfold');
+ return false;
}
onMounted(() => {
misskeyApi('users/notes', {
userId: props.user.id,
withFiles: true,
- limit: 15,
- }).then(notes => {
- for (const note of notes) {
- for (const file of note.files) {
- files.value.push({
- note,
- file,
- });
- }
- }
+ limit: 10,
+ }).then(_notes => {
+ notes.value = _notes;
fetching.value = false;
});
});
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index a35250bf5f..ba02559d68 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -9,10 +9,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<div>
<div v-if="user">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
- <XHome v-if="tab === 'home'" key="home" :user="user"/>
+ <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"/>
@@ -39,10 +40,11 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { getServerContext } from '@/server-context.js';
+import { serverContext, assertServerContext } from '@/server-context.js';
const XHome = defineAsyncComponent(() => import('./home.vue'));
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
+const XFiles = defineAsyncComponent(() => import('./files.vue'));
const XActivity = defineAsyncComponent(() => import('./activity.vue'));
const XAchievements = defineAsyncComponent(() => import('./achievements.vue'));
const XReactions = defineAsyncComponent(() => import('./reactions.vue'));
@@ -53,7 +55,8 @@ const XFlashs = defineAsyncComponent(() => import('./flashs.vue'));
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
const XRaw = defineAsyncComponent(() => import('./raw.vue'));
-const CTX_USER = getServerContext('user');
+// contextã¯éžãƒ­ã‚°ã‚¤ãƒ³çŠ¶æ…‹ã®æƒ…å ±ã—ã‹ãªã„ãŸã‚ログイン時ã¯åˆ©ç”¨ã§ããªã„
+const CTX_USER = !$i && assertServerContext(serverContext, 'user') ? serverContext.user : null;
const props = withDefaults(defineProps<{
acct: string;
@@ -103,6 +106,10 @@ const headerTabs = computed(() => user.value ? [{
title: i18n.ts.notes,
icon: 'ti ti-pencil',
}, {
+ key: 'files',
+ title: i18n.ts.files,
+ icon: 'ti ti-photo',
+}, {
key: 'activity',
title: i18n.ts.activity,
icon: 'ti ti-chart-line',
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index f1842255e0..c5731bd2a9 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -53,12 +53,14 @@ function getInstanceIcon(instance: Misskey.entities.FederationInstance): string
if (!instance.iconUrl) {
return '';
}
+
return getProxiedImageUrl(instance.iconUrl, 'preview');
}
misskeyApiGet('federation/instances', {
sort: '+pubSub',
limit: 20,
+ blocked: 'false',
}).then(_instances => {
instances.value = _instances;
});
diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts
index c7637a1db9..74e1482ce0 100644
--- a/packages/frontend/src/router/definition.ts
+++ b/packages/frontend/src/router/definition.ts
@@ -391,6 +391,10 @@ const routes: RouteDef[] = [{
name: 'emojis',
component: page(() => import('@/pages/custom-emojis-manager.vue')),
}, {
+ path: '/emojis2',
+ name: 'emojis2',
+ component: page(() => import('@/pages/admin/custom-emojis-manager2.vue')),
+ }, {
path: '/avatar-decorations',
name: 'avatarDecorations',
component: page(() => import('@/pages/avatar-decorations.vue')),
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts
index 46aed49330..e203c51bba 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/scripts/aiscript/api.ts
@@ -3,14 +3,24 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { utils, values } from '@syuilo/aiscript';
+import { errors, utils, values } from '@syuilo/aiscript';
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 { miLocalStorage } from '@/local-storage.js';
import { customEmojis } from '@/custom-emojis.js';
-import { url, lang } from '@@/js/config.js';
+
+const DIALOG_TYPES = [
+ 'error',
+ 'info',
+ 'success',
+ 'warning',
+ 'waiting',
+ 'question',
+] as const;
export function aiScriptReadline(q: string): Promise<string> {
return new Promise(ok => {
@@ -22,15 +32,20 @@ export function aiScriptReadline(q: string): Promise<string> {
});
}
-export function createAiScriptEnv(opts) {
+export function createAiScriptEnv(opts: { storageKey: string, token?: string }) {
return {
USER_ID: $i ? values.STR($i.id) : values.NULL,
- USER_NAME: $i ? values.STR($i.name) : values.NULL,
+ USER_NAME: $i?.name ? values.STR($i.name) : values.NULL,
USER_USERNAME: $i ? values.STR($i.username) : values.NULL,
CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value),
LOCALE: values.STR(lang),
SERVER_URL: values.STR(url),
'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => {
+ utils.assertString(title);
+ utils.assertString(text);
+ if (type != null) {
+ assertStringAndIsIn(type, DIALOG_TYPES);
+ }
await os.alert({
type: type ? type.value : 'info',
title: title.value,
@@ -39,6 +54,11 @@ export function createAiScriptEnv(opts) {
return values.NULL;
}),
'Mk:confirm': values.FN_NATIVE(async ([title, text, type]) => {
+ utils.assertString(title);
+ utils.assertString(text);
+ if (type != null) {
+ assertStringAndIsIn(type, DIALOG_TYPES);
+ }
const confirm = await os.confirm({
type: type ? type.value : 'question',
title: title.value,
@@ -48,14 +68,20 @@ export function createAiScriptEnv(opts) {
}),
'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => {
utils.assertString(ep);
- if (ep.value.includes('://')) throw new Error('invalid endpoint');
+ if (ep.value.includes('://')) {
+ throw new errors.AiScriptRuntimeError('invalid endpoint');
+ }
if (token) {
utils.assertString(token);
// ãƒã‚°ãŒã‚れã°undefinedã‚‚ã‚り得るãŸã‚念ã®ãŸã‚
if (typeof token.value !== 'string') throw new Error('invalid token');
}
const actualToken: string|null = token?.value ?? opts.token ?? null;
- return misskeyApi(ep.value, utils.valToJs(param), actualToken).then(res => {
+ if (param == null) {
+ throw new errors.AiScriptRuntimeError('expected param');
+ }
+ utils.assertObject(param);
+ return misskeyApi(ep.value, utils.valToJs(param) as object, actualToken).then(res => {
return utils.jsToVal(res);
}, err => {
return values.ERROR('request_failed', utils.jsToVal(err));
@@ -75,12 +101,18 @@ export function createAiScriptEnv(opts) {
*/
'Mk:save': values.FN_NATIVE(([key, value]) => {
utils.assertString(key);
+ utils.expectAny(value);
miLocalStorage.setItem(`aiscript:${opts.storageKey}:${key.value}`, JSON.stringify(utils.valToJs(value)));
return values.NULL;
}),
'Mk:load': values.FN_NATIVE(([key]) => {
utils.assertString(key);
- return utils.jsToVal(JSON.parse(miLocalStorage.getItem(`aiscript:${opts.storageKey}:${key.value}`)));
+ return utils.jsToVal(miLocalStorage.getItemAsJson(`aiscript:${opts.storageKey}:${key.value}`) ?? null);
+ }),
+ 'Mk:remove': values.FN_NATIVE(([key]) => {
+ utils.assertString(key);
+ miLocalStorage.removeItem(`aiscript:${opts.storageKey}:${key.value}`);
+ return values.NULL;
}),
'Mk:url': values.FN_NATIVE(() => {
return values.STR(window.location.href);
diff --git a/packages/frontend/src/scripts/aiscript/common.ts b/packages/frontend/src/scripts/aiscript/common.ts
new file mode 100644
index 0000000000..de6fa1d633
--- /dev/null
+++ b/packages/frontend/src/scripts/aiscript/common.ts
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { errors, utils, 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);
+ const str = value.value;
+ if (!expects.includes(str)) {
+ const expected = expects.map((expect) => `"${expect}"`).join(', ');
+ throw new errors.AiScriptRuntimeError(`"${value.value}" is not in ${expected}`);
+ }
+}
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts
index 2b386bebb8..ca92b27ff5 100644
--- a/packages/frontend/src/scripts/aiscript/ui.ts
+++ b/packages/frontend/src/scripts/aiscript/ui.ts
@@ -7,6 +7,15 @@ import { utils, values } from '@syuilo/aiscript';
import { v4 as uuid } from 'uuid';
import { ref, Ref } from 'vue';
import * as Misskey from 'misskey-js';
+import { assertStringAndIsIn } from './common.js';
+
+const ALIGNS = ['left', 'center', 'right'] as const;
+const FONTS = ['serif', 'sans-serif', 'monospace'] as const;
+const BORDER_STYLES = ['hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'] as const;
+
+type Align = (typeof ALIGNS)[number];
+type Font = (typeof FONTS)[number];
+type BorderStyle = (typeof BORDER_STYLES)[number];
export type AsUiComponentBase = {
id: string;
@@ -21,13 +30,13 @@ export type AsUiRoot = AsUiComponentBase & {
export type AsUiContainer = AsUiComponentBase & {
type: 'container';
children?: AsUiComponent['id'][];
- align?: 'left' | 'center' | 'right';
+ align?: Align;
bgColor?: string;
fgColor?: string;
- font?: 'serif' | 'sans-serif' | 'monospace';
+ font?: Font;
borderWidth?: number;
borderColor?: string;
- borderStyle?: 'hidden' | 'dotted' | 'dashed' | 'solid' | 'double' | 'groove' | 'ridge' | 'inset' | 'outset';
+ borderStyle?: BorderStyle;
borderRadius?: number;
padding?: number;
rounded?: boolean;
@@ -40,7 +49,7 @@ export type AsUiText = AsUiComponentBase & {
size?: number;
bold?: boolean;
color?: string;
- font?: 'serif' | 'sans-serif' | 'monospace';
+ font?: Font;
};
export type AsUiMfm = AsUiComponentBase & {
@@ -49,14 +58,14 @@ export type AsUiMfm = AsUiComponentBase & {
size?: number;
bold?: boolean;
color?: string;
- font?: 'serif' | 'sans-serif' | 'monospace';
- onClickEv?: (evId: string) => void
+ font?: Font;
+ onClickEv?: (evId: string) => Promise<void>;
};
export type AsUiButton = AsUiComponentBase & {
type: 'button';
text?: string;
- onClick?: () => void;
+ onClick?: () => Promise<void>;
primary?: boolean;
rounded?: boolean;
disabled?: boolean;
@@ -69,7 +78,7 @@ export type AsUiButtons = AsUiComponentBase & {
export type AsUiSwitch = AsUiComponentBase & {
type: 'switch';
- onChange?: (v: boolean) => void;
+ onChange?: (v: boolean) => Promise<void>;
default?: boolean;
label?: string;
caption?: string;
@@ -77,7 +86,7 @@ export type AsUiSwitch = AsUiComponentBase & {
export type AsUiTextarea = AsUiComponentBase & {
type: 'textarea';
- onInput?: (v: string) => void;
+ onInput?: (v: string) => Promise<void>;
default?: string;
label?: string;
caption?: string;
@@ -85,7 +94,7 @@ export type AsUiTextarea = AsUiComponentBase & {
export type AsUiTextInput = AsUiComponentBase & {
type: 'textInput';
- onInput?: (v: string) => void;
+ onInput?: (v: string) => Promise<void>;
default?: string;
label?: string;
caption?: string;
@@ -93,7 +102,7 @@ export type AsUiTextInput = AsUiComponentBase & {
export type AsUiNumberInput = AsUiComponentBase & {
type: 'numberInput';
- onInput?: (v: number) => void;
+ onInput?: (v: number) => Promise<void>;
default?: number;
label?: string;
caption?: string;
@@ -105,7 +114,7 @@ export type AsUiSelect = AsUiComponentBase & {
text: string;
value: string;
}[];
- onChange?: (v: string) => void;
+ onChange?: (v: string) => Promise<void>;
default?: string;
label?: string;
caption?: string;
@@ -140,11 +149,15 @@ export type AsUiPostForm = AsUiComponentBase & {
export type AsUiComponent = AsUiRoot | AsUiContainer | AsUiText | AsUiMfm | AsUiButton | AsUiButtons | AsUiSwitch | AsUiTextarea | AsUiTextInput | AsUiNumberInput | AsUiSelect | AsUiFolder | AsUiPostFormButton | AsUiPostForm;
+type Options<T extends AsUiComponent> = T extends AsUiButtons
+ ? Omit<T, 'id' | 'type' | 'buttons'> & { 'buttons'?: Options<AsUiButton>[] }
+ : Omit<T, 'id' | 'type'>;
+
export function patch(id: string, def: values.Value, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>) {
// TODO
}
-function getRootOptions(def: values.Value | undefined): Omit<AsUiRoot, 'id' | 'type'> {
+function getRootOptions(def: values.Value | undefined): Options<AsUiRoot> {
utils.assertObject(def);
const children = def.value.get('children');
@@ -153,30 +166,32 @@ function getRootOptions(def: values.Value | undefined): Omit<AsUiRoot, 'id' | 't
return {
children: children.value.map(v => {
utils.assertObject(v);
- return v.value.get('id').value;
+ const id = v.value.get('id');
+ utils.assertString(id);
+ return id.value;
}),
};
}
-function getContainerOptions(def: values.Value | undefined): Omit<AsUiContainer, 'id' | 'type'> {
+function getContainerOptions(def: values.Value | undefined): Options<AsUiContainer> {
utils.assertObject(def);
const children = def.value.get('children');
if (children) utils.assertArray(children);
const align = def.value.get('align');
- if (align) utils.assertString(align);
+ if (align) assertStringAndIsIn(align, ALIGNS);
const bgColor = def.value.get('bgColor');
if (bgColor) utils.assertString(bgColor);
const fgColor = def.value.get('fgColor');
if (fgColor) utils.assertString(fgColor);
const font = def.value.get('font');
- if (font) utils.assertString(font);
+ if (font) assertStringAndIsIn(font, FONTS);
const borderWidth = def.value.get('borderWidth');
if (borderWidth) utils.assertNumber(borderWidth);
const borderColor = def.value.get('borderColor');
if (borderColor) utils.assertString(borderColor);
const borderStyle = def.value.get('borderStyle');
- if (borderStyle) utils.assertString(borderStyle);
+ if (borderStyle) assertStringAndIsIn(borderStyle, BORDER_STYLES);
const borderRadius = def.value.get('borderRadius');
if (borderRadius) utils.assertNumber(borderRadius);
const padding = def.value.get('padding');
@@ -189,7 +204,9 @@ function getContainerOptions(def: values.Value | undefined): Omit<AsUiContainer,
return {
children: children ? children.value.map(v => {
utils.assertObject(v);
- return v.value.get('id').value;
+ const id = v.value.get('id');
+ utils.assertString(id);
+ return id.value;
}) : [],
align: align?.value,
fgColor: fgColor?.value,
@@ -205,7 +222,7 @@ function getContainerOptions(def: values.Value | undefined): Omit<AsUiContainer,
};
}
-function getTextOptions(def: values.Value | undefined): Omit<AsUiText, 'id' | 'type'> {
+function getTextOptions(def: values.Value | undefined): Options<AsUiText> {
utils.assertObject(def);
const text = def.value.get('text');
@@ -217,7 +234,7 @@ function getTextOptions(def: values.Value | undefined): Omit<AsUiText, 'id' | 't
const color = def.value.get('color');
if (color) utils.assertString(color);
const font = def.value.get('font');
- if (font) utils.assertString(font);
+ if (font) assertStringAndIsIn(font, FONTS);
return {
text: text?.value,
@@ -228,7 +245,7 @@ function getTextOptions(def: values.Value | undefined): Omit<AsUiText, 'id' | 't
};
}
-function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiMfm, 'id' | 'type'> {
+function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiMfm> {
utils.assertObject(def);
const text = def.value.get('text');
@@ -240,7 +257,7 @@ function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, arg
const color = def.value.get('color');
if (color) utils.assertString(color);
const font = def.value.get('font');
- if (font) utils.assertString(font);
+ if (font) assertStringAndIsIn(font, FONTS);
const onClickEv = def.value.get('onClickEv');
if (onClickEv) utils.assertFunction(onClickEv);
@@ -250,13 +267,13 @@ function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, arg
bold: bold?.value,
color: color?.value,
font: font?.value,
- onClickEv: (evId: string) => {
- if (onClickEv) call(onClickEv, [values.STR(evId)]);
+ onClickEv: async (evId: string) => {
+ if (onClickEv) await call(onClickEv, [values.STR(evId)]);
},
};
}
-function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiTextInput, 'id' | 'type'> {
+function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiTextInput> {
utils.assertObject(def);
const onInput = def.value.get('onInput');
@@ -269,8 +286,8 @@ function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VF
if (caption) utils.assertString(caption);
return {
- onInput: (v) => {
- if (onInput) call(onInput, [utils.jsToVal(v)]);
+ onInput: async (v) => {
+ if (onInput) await call(onInput, [utils.jsToVal(v)]);
},
default: defaultValue?.value,
label: label?.value,
@@ -278,7 +295,7 @@ function getTextInputOptions(def: values.Value | undefined, call: (fn: values.VF
};
}
-function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiTextarea, 'id' | 'type'> {
+function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiTextarea> {
utils.assertObject(def);
const onInput = def.value.get('onInput');
@@ -291,8 +308,8 @@ function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn
if (caption) utils.assertString(caption);
return {
- onInput: (v) => {
- if (onInput) call(onInput, [utils.jsToVal(v)]);
+ onInput: async (v) => {
+ if (onInput) await call(onInput, [utils.jsToVal(v)]);
},
default: defaultValue?.value,
label: label?.value,
@@ -300,7 +317,7 @@ function getTextareaOptions(def: values.Value | undefined, call: (fn: values.VFn
};
}
-function getNumberInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiNumberInput, 'id' | 'type'> {
+function getNumberInputOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiNumberInput> {
utils.assertObject(def);
const onInput = def.value.get('onInput');
@@ -313,8 +330,8 @@ function getNumberInputOptions(def: values.Value | undefined, call: (fn: values.
if (caption) utils.assertString(caption);
return {
- onInput: (v) => {
- if (onInput) call(onInput, [utils.jsToVal(v)]);
+ onInput: async (v) => {
+ if (onInput) await call(onInput, [utils.jsToVal(v)]);
},
default: defaultValue?.value,
label: label?.value,
@@ -322,7 +339,7 @@ function getNumberInputOptions(def: values.Value | undefined, call: (fn: values.
};
}
-function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiButton, 'id' | 'type'> {
+function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiButton> {
utils.assertObject(def);
const text = def.value.get('text');
@@ -338,8 +355,8 @@ function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn,
return {
text: text?.value,
- onClick: () => {
- if (onClick) call(onClick, []);
+ onClick: async () => {
+ if (onClick) await call(onClick, []);
},
primary: primary?.value,
rounded: rounded?.value,
@@ -347,7 +364,7 @@ function getButtonOptions(def: values.Value | undefined, call: (fn: values.VFn,
};
}
-function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiButtons, 'id' | 'type'> {
+function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiButtons> {
utils.assertObject(def);
const buttons = def.value.get('buttons');
@@ -369,8 +386,8 @@ function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn,
return {
text: text.value,
- onClick: () => {
- call(onClick, []);
+ onClick: async () => {
+ await call(onClick, []);
},
primary: primary?.value,
rounded: rounded?.value,
@@ -380,7 +397,7 @@ function getButtonsOptions(def: values.Value | undefined, call: (fn: values.VFn,
};
}
-function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiSwitch, 'id' | 'type'> {
+function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiSwitch> {
utils.assertObject(def);
const onChange = def.value.get('onChange');
@@ -393,8 +410,8 @@ function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn,
if (caption) utils.assertString(caption);
return {
- onChange: (v) => {
- if (onChange) call(onChange, [utils.jsToVal(v)]);
+ onChange: async (v) => {
+ if (onChange) await call(onChange, [utils.jsToVal(v)]);
},
default: defaultValue?.value,
label: label?.value,
@@ -402,7 +419,7 @@ function getSwitchOptions(def: values.Value | undefined, call: (fn: values.VFn,
};
}
-function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiSelect, 'id' | 'type'> {
+function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiSelect> {
utils.assertObject(def);
const items = def.value.get('items');
@@ -428,8 +445,8 @@ function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn,
value: value ? value.value : text.value,
};
}) : [],
- onChange: (v) => {
- if (onChange) call(onChange, [utils.jsToVal(v)]);
+ onChange: async (v) => {
+ if (onChange) await call(onChange, [utils.jsToVal(v)]);
},
default: defaultValue?.value,
label: label?.value,
@@ -437,7 +454,7 @@ function getSelectOptions(def: values.Value | undefined, call: (fn: values.VFn,
};
}
-function getFolderOptions(def: values.Value | undefined): Omit<AsUiFolder, 'id' | 'type'> {
+function getFolderOptions(def: values.Value | undefined): Options<AsUiFolder> {
utils.assertObject(def);
const children = def.value.get('children');
@@ -450,7 +467,9 @@ function getFolderOptions(def: values.Value | undefined): Omit<AsUiFolder, 'id'
return {
children: children ? children.value.map(v => {
utils.assertObject(v);
- return v.value.get('id').value;
+ const id = v.value.get('id');
+ utils.assertString(id);
+ return id.value;
}) : [],
title: title?.value ?? '',
opened: opened?.value ?? true,
@@ -475,7 +494,7 @@ function getPostFormProps(form: values.VObj): PostFormPropsForAsUi {
};
}
-function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiPostFormButton, 'id' | 'type'> {
+function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiPostFormButton> {
utils.assertObject(def);
const text = def.value.get('text');
@@ -497,7 +516,7 @@ function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: valu
};
}
-function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiPostForm, 'id' | 'type'> {
+function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Options<AsUiPostForm> {
utils.assertObject(def);
const form = def.value.get('form');
@@ -511,18 +530,26 @@ function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn
}
export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: Ref<AsUiRoot>) => void) {
+ type OptionsConverter<T extends AsUiComponent, C> = (def: values.Value | undefined, call: C) => Options<T>;
+
const instances = {};
- function createComponentInstance(type: AsUiComponent['type'], def: values.Value | undefined, id: values.Value | undefined, getOptions: (def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>) => any, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>) {
+ function createComponentInstance<T extends AsUiComponent, C>(
+ type: T['type'],
+ def: values.Value | undefined,
+ id: values.Value | undefined,
+ getOptions: OptionsConverter<T, C>,
+ call: C,
+ ) {
if (id) utils.assertString(id);
const _id = id?.value ?? uuid();
const component = ref({
...getOptions(def, call),
type,
id: _id,
- });
+ } as T);
components.push(component);
- const instance = values.OBJ(new Map([
+ const instance = values.OBJ(new Map<string, values.Value>([
['id', values.STR(_id)],
['update', values.FN_NATIVE(([def], opts) => {
utils.assertObject(def);
@@ -547,7 +574,7 @@ export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: R
'Ui:patch': values.FN_NATIVE(([id, val], opts) => {
utils.assertString(id);
utils.assertArray(val);
- patch(id.value, val.value, opts.call);
+ // patch(id.value, val.value, opts.call); // TODO
}),
'Ui:get': values.FN_NATIVE(([id], opts) => {
@@ -566,7 +593,9 @@ export function registerAsUiLib(components: Ref<AsUiComponent>[], done: (root: R
rootComponent.value.children = children.value.map(v => {
utils.assertObject(v);
- return v.value.get('id').value;
+ const id = v.value.get('id');
+ utils.assertString(id);
+ return id.value;
});
}),
diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/scripts/autocomplete.ts
index d942402ffc..8a3a6bf6db 100644
--- a/packages/frontend/src/scripts/autocomplete.ts
+++ b/packages/frontend/src/scripts/autocomplete.ts
@@ -5,7 +5,7 @@
import { nextTick, Ref, ref, defineAsyncComponent } from 'vue';
import getCaretCoordinates from 'textarea-caret';
-import { toASCII } from 'punycode/';
+import { toASCII } from 'punycode.js';
import { popup } from '@/os.js';
export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam';
diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts
index 6525c207f7..194ef0f420 100644
--- a/packages/frontend/src/scripts/check-word-mute.ts
+++ b/packages/frontend/src/scripts/check-word-mute.ts
@@ -4,7 +4,7 @@
*/
import * as Misskey from 'misskey-js';
-export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): boolean {
+export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): Array<string | string[]> | false {
// 自分自身
if (me && (note.userId === me.id)) return false;
@@ -13,7 +13,7 @@ export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.
if (text === '') return false;
- const matched = mutedWords.some(filter => {
+ const matched = mutedWords.filter(filter => {
if (Array.isArray(filter)) {
// Clean up
const filteredFilter = filter.filter(keyword => keyword !== '');
@@ -36,7 +36,7 @@ export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.
}
});
- if (matched) return true;
+ if (matched.length > 0) return matched;
}
return false;
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts
index 6710d9826e..4d57dcd944 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/scripts/code-highlighter.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { createHighlighterCore, loadWasm } from 'shiki/core';
+import { createHighlighterCore } from 'shiki/core';
+import { createOnigurumaEngine } from 'shiki/engine/oniguruma';
import darkPlus from 'shiki/themes/dark-plus.mjs';
import { bundledThemesInfo } from 'shiki/themes';
import { bundledLanguagesInfo } from 'shiki/langs';
@@ -60,8 +61,6 @@ export async function getHighlighter(): Promise<HighlighterCore> {
}
async function initHighlighter() {
- await loadWasm(import('shiki/onig.wasm?init'));
-
// テーマã®é‡è¤‡ã‚’消ã™
const themes = unique([
darkPlus,
@@ -70,6 +69,7 @@ async function initHighlighter() {
const jsLangInfo = bundledLanguagesInfo.find(t => t.id === 'javascript');
const highlighter = await createHighlighterCore({
+ engine: createOnigurumaEngine(() => import('shiki/onig.wasm?init')),
themes,
langs: [
...(jsLangInfo ? [async () => await jsLangInfo.import()] : []),
diff --git a/packages/frontend/src/scripts/file-drop.ts b/packages/frontend/src/scripts/file-drop.ts
new file mode 100644
index 0000000000..c2e863c0dc
--- /dev/null
+++ b/packages/frontend/src/scripts/file-drop.ts
@@ -0,0 +1,121 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export type DroppedItem = DroppedFile | DroppedDirectory;
+
+export type DroppedFile = {
+ isFile: true;
+ path: string;
+ file: File;
+};
+
+export type DroppedDirectory = {
+ isFile: false;
+ path: string;
+ children: DroppedItem[];
+}
+
+export async function extractDroppedItems(ev: DragEvent): Promise<DroppedItem[]> {
+ const dropItems = ev.dataTransfer?.items;
+ if (!dropItems || dropItems.length === 0) {
+ return [];
+ }
+
+ const apiTestItem = dropItems[0];
+ if ('webkitGetAsEntry' in apiTestItem) {
+ return readDataTransferItems(dropItems);
+ } else {
+ // webkitGetAsEntryã«å¯¾å¿œã—ã¦ã„ãªã„å ´åˆã¯filesã‹ã‚‰å–å¾—ã™ã‚‹ï¼ˆãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ã‚µãƒãƒ¼ãƒˆã¯å‡ºæ¥ãªã„)
+ const dropFiles = ev.dataTransfer.files;
+ if (dropFiles.length === 0) {
+ return [];
+ }
+
+ const droppedFiles = Array.of<DroppedFile>();
+ for (let i = 0; i < dropFiles.length; i++) {
+ const file = dropFiles.item(i);
+ if (file) {
+ droppedFiles.push({
+ isFile: true,
+ path: file.name,
+ file,
+ });
+ }
+ }
+
+ return droppedFiles;
+ }
+}
+
+/**
+ * ドラッグ&ドロップã•れãŸãƒ•ァイルã®ãƒªã‚¹ãƒˆã‹ã‚‰ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªæ§‹é€ ã¨ãƒ•ァイルã¸ã®å‚照({@link File})をå–å¾—ã™ã‚‹ã€‚
+ */
+export async function readDataTransferItems(itemList: DataTransferItemList): Promise<DroppedItem[]> {
+ async function readEntry(entry: FileSystemEntry): Promise<DroppedItem> {
+ if (entry.isFile) {
+ return {
+ isFile: true,
+ path: entry.fullPath,
+ file: await readFile(entry as FileSystemFileEntry),
+ };
+ } else {
+ return {
+ isFile: false,
+ path: entry.fullPath,
+ children: await readDirectory(entry as FileSystemDirectoryEntry),
+ };
+ }
+ }
+
+ function readFile(fileSystemFileEntry: FileSystemFileEntry): Promise<File> {
+ return new Promise((resolve, reject) => {
+ fileSystemFileEntry.file(resolve, reject);
+ });
+ }
+
+ function readDirectory(fileSystemDirectoryEntry: FileSystemDirectoryEntry): Promise<DroppedItem[]> {
+ return new Promise(async (resolve) => {
+ const allEntries = Array.of<FileSystemEntry>();
+ const reader = fileSystemDirectoryEntry.createReader();
+ while (true) {
+ const entries = await new Promise<FileSystemEntry[]>((res, rej) => reader.readEntries(res, rej));
+ if (entries.length === 0) {
+ break;
+ }
+ allEntries.push(...entries);
+ }
+
+ resolve(await Promise.all(allEntries.map(readEntry)));
+ });
+ }
+
+ // 扱ã„ã«ãã„ã®ã§é…列ã«å¤‰æ›
+ const items = Array.of<DataTransferItem>();
+ for (let i = 0; i < itemList.length; i++) {
+ items.push(itemList[i]);
+ }
+
+ return Promise.all(
+ items
+ .map(it => it.webkitGetAsEntry())
+ .filter(it => it)
+ .map(it => readEntry(it!)),
+ );
+}
+
+/**
+ * {@link DroppedItem}ã®ãƒªã‚¹ãƒˆã‹ã‚‰ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’å†å¸°çš„ã«æ¤œç´¢ã—ã€ãƒ•ァイルã®ãƒªã‚¹ãƒˆã‚’å–å¾—ã™ã‚‹ã€‚
+ */
+export function flattenDroppedFiles(items: DroppedItem[]): DroppedFile[] {
+ const result = Array.of<DroppedFile>();
+ for (const item of items) {
+ if (item.isFile) {
+ result.push(item);
+ } else {
+ result.push(...flattenDroppedFiles(item.children));
+ }
+ }
+ return result;
+}
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index c56fd185b6..9112daf49f 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -5,19 +5,19 @@
import { defineAsyncComponent, Ref, ShallowRef } from 'vue';
import * as Misskey from 'misskey-js';
+import { url } from '@@/js/config.js';
import { claimAchievement } from './achievements.js';
+import type { MenuItem } from '@/types/menu.js';
import { $i } from '@/account.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 { url } from '@@/js/config.js';
import { defaultStore, noteActions } from '@/store.js';
import { miLocalStorage } from '@/local-storage.js';
import { getUserMenu } from '@/scripts/get-user-menu.js';
import { clipsCache, favoritedChannelsCache } from '@/cache.js';
-import type { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { isSupportShare } from '@/scripts/navigator.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
@@ -205,7 +205,7 @@ export function getNoteMenu(props: {
noteId: appearNote.id,
});
- if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60) {
+ if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60 && appearNote.userId === $i.id) {
claimAchievement('noteDeletedWithin1min');
}
});
@@ -224,7 +224,7 @@ export function getNoteMenu(props: {
os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel });
- if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60) {
+ if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60 && appearNote.userId === $i.id) {
claimAchievement('noteDeletedWithin1min');
}
});
@@ -259,11 +259,6 @@ export function getNoteMenu(props: {
os.success();
}
- function copyLink(): void {
- copyToClipboard(`${url}/notes/${appearNote.id}`);
- os.success();
- }
-
function togglePin(pin: boolean): void {
os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', {
noteId: appearNote.id,
@@ -347,6 +342,13 @@ export function getNoteMenu(props: {
getCopyNoteOriginLinkMenu(appearNote, 'Copy link (Origin)')
);
menuItems.push({
+ icon: 'ti ti-link',
+ text: i18n.ts.copyRemoteLink,
+ action: () => {
+ copyToClipboard(appearNote.url ?? appearNote.uri);
+ os.success();
+ },
+ }, {
icon: 'ti ti-external-link',
text: i18n.ts.showOnRemote,
action: () => {
@@ -508,6 +510,13 @@ export function getNoteMenu(props: {
getCopyNoteOriginLinkMenu(appearNote, 'Copy link (Origin)')
);
menuItems.push({
+ icon: 'ti ti-link',
+ text: i18n.ts.copyRemoteLink,
+ action: () => {
+ copyToClipboard(appearNote.url ?? appearNote.uri);
+ os.success();
+ },
+ }, {
icon: 'ti ti-external-link',
text: i18n.ts.showOnRemote,
action: () => {
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 090cffe203..2fbdaf5d3c 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { toUnicode } from 'punycode';
+import { toUnicode } from 'punycode.js';
import { defineAsyncComponent, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/scripts/key-event.ts b/packages/frontend/src/scripts/key-event.ts
new file mode 100644
index 0000000000..a72776d48c
--- /dev/null
+++ b/packages/frontend/src/scripts/key-event.ts
@@ -0,0 +1,153 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/**
+ * {@link KeyboardEvent.code} ã®å€¤ã‚’è¡¨ã™æ–‡å­—列。ä¸è¶³åˆ†ã¯é©å®œè¿½åŠ ã™ã‚‹
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values
+ */
+export type KeyCode =
+ | 'Backspace'
+ | 'Tab'
+ | 'Enter'
+ | 'Shift'
+ | 'Control'
+ | 'Alt'
+ | 'Pause'
+ | 'CapsLock'
+ | 'Escape'
+ | 'Space'
+ | 'PageUp'
+ | 'PageDown'
+ | 'End'
+ | 'Home'
+ | 'ArrowLeft'
+ | 'ArrowUp'
+ | 'ArrowRight'
+ | 'ArrowDown'
+ | 'Insert'
+ | 'Delete'
+ | 'Digit0'
+ | 'Digit1'
+ | 'Digit2'
+ | 'Digit3'
+ | 'Digit4'
+ | 'Digit5'
+ | 'Digit6'
+ | 'Digit7'
+ | 'Digit8'
+ | 'Digit9'
+ | 'KeyA'
+ | 'KeyB'
+ | 'KeyC'
+ | 'KeyD'
+ | 'KeyE'
+ | 'KeyF'
+ | 'KeyG'
+ | 'KeyH'
+ | 'KeyI'
+ | 'KeyJ'
+ | 'KeyK'
+ | 'KeyL'
+ | 'KeyM'
+ | 'KeyN'
+ | 'KeyO'
+ | 'KeyP'
+ | 'KeyQ'
+ | 'KeyR'
+ | 'KeyS'
+ | 'KeyT'
+ | 'KeyU'
+ | 'KeyV'
+ | 'KeyW'
+ | 'KeyX'
+ | 'KeyY'
+ | 'KeyZ'
+ | 'MetaLeft'
+ | 'MetaRight'
+ | 'ContextMenu'
+ | 'F1'
+ | 'F2'
+ | 'F3'
+ | 'F4'
+ | 'F5'
+ | 'F6'
+ | 'F7'
+ | 'F8'
+ | 'F9'
+ | 'F10'
+ | 'F11'
+ | 'F12'
+ | 'NumLock'
+ | 'ScrollLock'
+ | 'Semicolon'
+ | 'Equal'
+ | 'Comma'
+ | 'Minus'
+ | 'Period'
+ | 'Slash'
+ | 'Backquote'
+ | 'BracketLeft'
+ | 'Backslash'
+ | 'BracketRight'
+ | 'Quote'
+ | 'Meta'
+ | 'AltGraph'
+ ;
+
+/**
+ * ä¿®é£¾ã‚­ãƒ¼ã‚’è¡¨ã™æ–‡å­—列。ä¸è¶³åˆ†ã¯é©å®œè¿½åŠ ã™ã‚‹ã€‚
+ */
+export type KeyModifier =
+ | 'Shift'
+ | 'Control'
+ | 'Alt'
+ | 'Meta'
+ ;
+
+/**
+ * 押下ã•れãŸã‚­ãƒ¼ä»¥å¤–ã®çŠ¶æ…‹ã‚’è¡¨ã™æ–‡å­—列。ä¸è¶³åˆ†ã¯é©å®œè¿½åŠ ã™ã‚‹ã€‚
+ */
+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[]) {
+ if (modifiers) {
+ return modifiers.every(modifier => ev.getModifierState(modifier));
+ }
+ return true;
+ }
+
+ function checkState(ev: KeyboardEvent, states?: KeyState[]) {
+ if (states) {
+ return states.every(state => ev.getModifierState(state));
+ }
+ return true;
+ }
+
+ let hit = false;
+ for (const handler of handlers.filter(it => it.code === event.code)) {
+ if (checkModifier(event, handler.modifiers) && checkState(event, handler.states)) {
+ handler.handler(event);
+ hit = true;
+ break;
+ }
+ }
+
+ if (!hit) {
+ for (const handler of handlers.filter(it => it.code === 'any')) {
+ handler.handler(event);
+ }
+ }
+}
diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts
index e20b23f166..54ec2ce39b 100644
--- a/packages/frontend/src/scripts/lookup.ts
+++ b/packages/frontend/src/scripts/lookup.ts
@@ -33,7 +33,43 @@ export async function lookup(router?: Router) {
uri: query,
});
- os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
+ 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;
diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts
index 89fdda0cbb..004b6d42a4 100644
--- a/packages/frontend/src/scripts/merge.ts
+++ b/packages/frontend/src/scripts/merge.ts
@@ -7,10 +7,10 @@ import { deepClone } from './clone.js';
import type { Cloneable } from './clone.js';
export type DeepPartial<T> = {
- [P in keyof T]?: T[P] extends Record<string | number | symbol, unknown> ? DeepPartial<T[P]> : T[P];
+ [P in keyof T]?: T[P] extends Record<PropertyKey, unknown> ? DeepPartial<T[P]> : T[P];
};
-function isPureObject(value: unknown): value is Record<string | number | symbol, unknown> {
+function isPureObject(value: unknown): value is Record<PropertyKey, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
@@ -18,14 +18,14 @@ function isPureObject(value: unknown): value is Record<string | number | symbol,
* valueã«ãªã„キーをdefã‹ã‚‰ã‚‚らã†ï¼ˆå†å¸°çš„)\
* nullã¯ãã®ã¾ã¾ã€undefinedã¯defã®å€¤
**/
-export function deepMerge<X extends object>(value: DeepPartial<X>, def: X): X {
+export function deepMerge<X extends Record<PropertyKey, unknown>>(value: DeepPartial<X>, def: X): X {
if (isPureObject(value) && isPureObject(def)) {
const result = deepClone(value as Cloneable) as X;
for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
result[k] = v;
} else if (isPureObject(v) && isPureObject(result[k])) {
- const child = deepClone(result[k] as Cloneable) as DeepPartial<X[keyof X] & Record<string | number | symbol, unknown>>;
+ const child = deepClone(result[k] as Cloneable) as DeepPartial<X[keyof X] & Record<PropertyKey, unknown>>;
result[k] = deepMerge<typeof v>(child, v);
}
}
diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts
index e7a92e2d5c..dc07ad477b 100644
--- a/packages/frontend/src/scripts/misskey-api.ts
+++ b/packages/frontend/src/scripts/misskey-api.ts
@@ -9,12 +9,24 @@ import { apiUrl } from '@@/js/config.js';
import { $i } from '@/account.js';
export const pendingApiRequestsCount = ref(0);
+export type Endpoint = keyof Misskey.Endpoints;
+
+export type Request<E extends Endpoint> = Misskey.Endpoints[E]['req'];
+
+export type AnyRequest<E extends Endpoint | (string & unknown)> =
+ (E extends Endpoint ? Request<E> : never) | object;
+
+export type Response<E extends Endpoint | (string & unknown), P extends AnyRequest<E>> =
+ E extends Endpoint
+ ? P extends Request<E> ? Misskey.api.SwitchCaseResponseType<E, P> : never
+ : object;
+
// Implements Misskey.api.ApiClient.request
export function misskeyApi<
ResT = void,
- E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
- P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
- _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
+ E extends Endpoint | NonNullable<string> = Endpoint,
+ P extends AnyRequest<E> = E extends Endpoint ? Request<E> : never,
+ _ResT = ResT extends void ? Response<E, P> : ResT,
>(
endpoint: E,
data: P & { i?: string | null; } = {} as any,
diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts
index 43dcf11936..a8a330eb6d 100644
--- a/packages/frontend/src/scripts/please-login.ts
+++ b/packages/frontend/src/scripts/please-login.ts
@@ -5,6 +5,7 @@
import { defineAsyncComponent } from 'vue';
import { $i } from '@/account.js';
+import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { popup } from '@/os.js';
@@ -51,10 +52,17 @@ export function pleaseLogin(opts: {
} = {}) {
if ($i) return;
+ let _openOnRemote: OpenOnRemoteOptions | undefined = undefined;
+
+ // 連åˆã§ãã‚‹å ´åˆã¨ã€ï¼ˆé€£åˆãŒã§ããªãã¦ã‚‚)共有ã™ã‚‹å ´åˆã¯å¤–部連æºã‚ªãƒ—ションを設定
+ if (opts.openOnRemote != null && (instance.federation !== 'none' || opts.openOnRemote.type === 'share')) {
+ _openOnRemote = opts.openOnRemote;
+ }
+
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {
autoSet: true,
- message: opts.message ?? (opts.openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired),
- openOnRemote: opts.openOnRemote,
+ message: opts.message ?? (_openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired),
+ openOnRemote: _openOnRemote,
}, {
cancelled: () => {
if (opts.path) {
diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts
index b037aa8acc..c25b4d73bd 100644
--- a/packages/frontend/src/scripts/select-file.ts
+++ b/packages/frontend/src/scripts/select-file.ts
@@ -12,14 +12,28 @@ import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js';
import { uploadFile } from '@/scripts/upload.js';
-export function chooseFileFromPc(multiple: boolean, keepOriginal = false): Promise<Misskey.entities.DriveFile[]> {
+export function chooseFileFromPc(
+ multiple: boolean,
+ options?: {
+ uploadFolder?: string | null;
+ keepOriginal?: boolean;
+ nameConverter?: (file: File) => string | undefined;
+ },
+): Promise<Misskey.entities.DriveFile[]> {
+ const uploadFolder = options?.uploadFolder ?? defaultStore.state.uploadFolder;
+ const keepOriginal = options?.keepOriginal ?? defaultStore.state.keepOriginalUploading;
+ const nameConverter = options?.nameConverter ?? (() => undefined);
+
return new Promise((res, rej) => {
const input = document.createElement('input');
input.type = 'file';
input.multiple = multiple;
input.onchange = () => {
if (!input.files) return res([]);
- const promises = Array.from(input.files, file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal));
+ const promises = Array.from(
+ input.files,
+ file => uploadFile(file, uploadFolder, nameConverter(file), keepOriginal),
+ );
Promise.all(promises).then(driveFiles => {
res(driveFiles);
@@ -94,7 +108,7 @@ function select(src: HTMLElement | EventTarget | null, label: string | null, mul
}, {
text: i18n.ts.upload,
icon: 'ti ti-upload',
- action: () => chooseFileFromPc(multiple, keepOriginal.value).then(files => res(files)),
+ action: () => chooseFileFromPc(multiple, { keepOriginal: keepOriginal.value }).then(files => res(files)),
}, {
text: i18n.ts.fromDrive,
icon: 'ti ti-cloud',
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts
index 05f82fce7d..2008afe045 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/scripts/sound.ts
@@ -93,6 +93,10 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; })
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (ctx == null) {
ctx = new AudioContext();
+
+ window.addEventListener('beforeunload', () => {
+ ctx.close();
+ });
}
if (options?.useCache ?? true) {
if (cache.has(url)) {
diff --git a/packages/frontend/src/server-context.ts b/packages/frontend/src/server-context.ts
index aa44a10290..e79d3fa314 100644
--- a/packages/frontend/src/server-context.ts
+++ b/packages/frontend/src/server-context.ts
@@ -2,22 +2,20 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
+
import * as Misskey from 'misskey-js';
-import { $i } from '@/account.js';
const providedContextEl = document.getElementById('misskey_clientCtx');
export type ServerContext = {
clip?: Misskey.entities.Clip;
note?: Misskey.entities.Note;
- user?: Misskey.entities.UserLite;
+ user?: Misskey.entities.UserDetailed;
} | null;
export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null;
-export function getServerContext<K extends keyof NonNullable<ServerContext>>(entity: K): Required<Pick<NonNullable<ServerContext>, K>> | null {
- // contextã¯éžãƒ­ã‚°ã‚¤ãƒ³çŠ¶æ…‹ã®æƒ…å ±ã—ã‹ãªã„ãŸã‚ログイン時ã¯åˆ©ç”¨ã§ããªã„
- if ($i) return null;
-
- return serverContext ? (serverContext[entity] ?? null) : null;
+export function assertServerContext<K extends keyof NonNullable<ServerContext>>(ctx: ServerContext, entity: K): ctx is Required<Pick<NonNullable<ServerContext>, K>> {
+ if (ctx == null) return false;
+ return entity in ctx && ctx[entity] != null;
}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index c34e0bbf48..69fcef32c2 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -10,11 +10,11 @@ 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 { miLocalStorage } from '@/local-storage.js';
import { defaultFollowingFeedState } from '@/scripts/following-feed-utils.js';
import { Storage } from '@/pizzax.js';
-import type { Ast } from '@syuilo/aiscript';
interface PostFormAction {
title: string,
@@ -561,6 +561,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: true,
},
+ showSoftWordMutedWord: {
+ where: 'device',
+ default: false,
+ },
sound_masterVolume: {
where: 'device',
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index 8355ae3061..6ec85686db 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -56,12 +56,18 @@ export function openInstanceMenu(ev: MouseEvent) {
text: i18n.ts.customEmojis,
icon: 'ph-smiley ph-bold ph-lg',
to: '/about#emojis',
- }, {
- type: 'link',
- text: i18n.ts.federation,
- icon: 'ti ti-whirl',
- to: '/about#federation',
- }, {
+ });
+
+ if (instance.federation !== 'none') {
+ menuItems.push({
+ type: 'link',
+ text: i18n.ts.federation,
+ icon: 'ti ti-whirl',
+ to: '/about#federation',
+ });
+ }
+
+ menuItems.push({
type: 'link',
text: i18n.ts.charts,
icon: 'ti ti-chart-line',
@@ -134,7 +140,7 @@ export function openInstanceMenu(ev: MouseEvent) {
});
}
- if (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl && !instance.donationUrl) {
+ if (instance.impressumUrl != null || instance.tosUrl != null || instance.privacyPolicyUrl != null || nstance.donationUrl != null) {
menuItems.push({ type: 'divider' });
}
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 5cc0e52f77..062a8faf3f 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</component>
</template>
<div :class="$style.divider"></div>
- <MkA v-if="$i.isAdmin || $i.isModerator" v-tooltip.noDelay.right="i18n.ts.controlPanel" :class="$style.item" :activeClass="$style.active" to="/admin">
+ <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>
</MkA>
<button class="_button" :class="$style.item" @click="more">
@@ -48,10 +48,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkA>
</div>
<div :class="$style.bottom">
- <button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="os.post">
+ <button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="() => { os.post(); }">
<i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
</button>
- <button v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="_button" :class="[$style.account]" @click="openAccountMenu">
+ <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"/>
</button>
</div>
@@ -83,8 +83,12 @@ import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
+import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
-const iconOnly = ref(false);
+const forceIconOnly = ref(window.innerWidth <= 1279);
+const iconOnly = computed(() => {
+ return forceIconOnly.value || (defaultStore.reactiveState.menuDisplay.value === 'sideIcon');
+});
const menu = computed(() => defaultStore.state.menu);
const otherMenuItemIndicated = computed(() => {
@@ -95,14 +99,10 @@ const otherMenuItemIndicated = computed(() => {
return false;
});
-const forceIconOnly = window.innerWidth <= 1279;
-
function calcViewState() {
- iconOnly.value = forceIconOnly || (defaultStore.state.menuDisplay === 'sideIcon');
+ forceIconOnly.value = window.innerWidth <= 1279;
}
-calcViewState();
-
window.addEventListener('resize', calcViewState);
watch(defaultStore.reactiveState.menuDisplay, () => {
@@ -120,8 +120,10 @@ function openAccountMenu(ev: MouseEvent) {
}
function more(ev: MouseEvent) {
+ const target = getHTMLElementOrNull(ev.currentTarget ?? ev.target);
+ if (!target) return;
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
- src: ev.currentTarget ?? ev.target,
+ src: target,
}, {
closed: () => dispose(),
});
diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue
index 5f9a938017..ed881bef22 100644
--- a/packages/frontend/src/ui/_common_/statusbars.vue
+++ b/packages/frontend/src/ui/_common_/statusbars.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<span :class="$style.name">{{ x.name }}</span>
<XRss v-if="x.type === 'rss'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url" :shuffle="x.props.shuffle"/>
- <XFederation v-else-if="x.type === 'federation'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/>
+ <XFederation v-else-if="x.type === 'federation' && instance.federation !== 'none'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/>
<XUserList v-else-if="x.type === 'userList'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :userListId="x.props.userListId"/>
</div>
</div>
@@ -23,6 +23,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';
const XRss = defineAsyncComponent(() => import('./statusbar-rss.vue'));
const XFederation = defineAsyncComponent(() => import('./statusbar-federation.vue'));
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index c552b65318..e8c71f61cf 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XWidgets/>
</div>
- <button v-if="(!isDesktop || pageMetadata?.needWideArea) && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
+ <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>
diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts
index 29e4558f1e..37742f8c2e 100644
--- a/packages/frontend/src/widgets/index.ts
+++ b/packages/frontend/src/widgets/index.ts
@@ -37,6 +37,12 @@ export default function(app: App) {
app.component('WidgetBirthdayFollowings', defineAsyncComponent(() => import('./WidgetBirthdayFollowings.vue')));
}
+// 連åˆé–¢é€£ã®ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆï¼ˆé€£åˆç„¡åŠ¹æ™‚ã«éš ã™ï¼‰
+export const federationWidgets = [
+ 'federation',
+ 'instanceCloud',
+];
+
export const widgets = [
'profile',
'instanceInfo',
@@ -52,8 +58,6 @@ export const widgets = [
'photos',
'digitalClock',
'unixClock',
- 'federation',
- 'instanceCloud',
'postForm',
'slideshow',
'serverMetric',
@@ -67,4 +71,6 @@ export const widgets = [
'clicker',
'search',
'birthdayFollowings',
+
+ ...federationWidgets,
];
diff --git a/packages/frontend/test/aiscript/api.test.ts b/packages/frontend/test/aiscript/api.test.ts
new file mode 100644
index 0000000000..2a15a74249
--- /dev/null
+++ b/packages/frontend/test/aiscript/api.test.ts
@@ -0,0 +1,401 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { miLocalStorage } from '@/local-storage.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { errors, Interpreter, Parser, values } from '@syuilo/aiscript';
+import {
+ afterAll,
+ afterEach,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ test,
+ vi
+} from 'vitest';
+
+async function exe(script: string): Promise<values.Value[]> {
+ const outputs: values.Value[] = [];
+ const interpreter = new Interpreter(
+ createAiScriptEnv({ storageKey: 'widget' }),
+ {
+ in: aiScriptReadline,
+ out: (value) => {
+ outputs.push(value);
+ }
+ }
+ );
+ const ast = Parser.parse(script);
+ await interpreter.exec(ast);
+ return outputs;
+}
+
+let $iMock = vi.hoisted<Partial<typeof import('@/account.js').$i> | null >(
+ () => null
+);
+
+vi.mock('@/account.js', () => {
+ return {
+ get $i() {
+ return $iMock;
+ },
+ };
+});
+
+const osMock = vi.hoisted(() => {
+ return {
+ inputText: vi.fn(),
+ alert: vi.fn(),
+ confirm: vi.fn(),
+ };
+});
+
+vi.mock('@/os.js', () => {
+ return osMock;
+});
+
+const misskeyApiMock = vi.hoisted(() => vi.fn());
+
+vi.mock('@/scripts/misskey-api.js', () => {
+ return { misskeyApi: misskeyApiMock };
+});
+
+describe('AiScript common API', () => {
+ afterAll(() => {
+ vi.unstubAllGlobals();
+ });
+
+ describe('readline', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ test.sequential('ok', async () => {
+ osMock.inputText.mockImplementationOnce(async ({ title }) => {
+ expect(title).toBe('question');
+ return {
+ canceled: false,
+ result: 'Hello',
+ };
+ });
+ const [res] = await exe(`
+ <: readline('question')
+ `);
+ expect(res).toStrictEqual(values.STR('Hello'));
+ expect(osMock.inputText).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('cancelled', async () => {
+ osMock.inputText.mockImplementationOnce(async ({ title }) => {
+ expect(title).toBe('question');
+ return {
+ canceled: true,
+ result: undefined,
+ };
+ });
+ const [res] = await exe(`
+ <: readline('question')
+ `);
+ expect(res).toStrictEqual(values.STR(''));
+ expect(osMock.inputText).toHaveBeenCalledOnce();
+ });
+ });
+
+ describe('user constants', () => {
+ describe.sequential('logged in', () => {
+ beforeAll(() => {
+ $iMock = {
+ id: 'xxxxxxxx',
+ name: 'è—',
+ username: 'ai',
+ };
+ });
+
+ test.concurrent('USER_ID', async () => {
+ const [res] = await exe(`
+ <: USER_ID
+ `);
+ expect(res).toStrictEqual(values.STR('xxxxxxxx'));
+ });
+
+ test.concurrent('USER_NAME', async () => {
+ const [res] = await exe(`
+ <: USER_NAME
+ `);
+ expect(res).toStrictEqual(values.STR('è—'));
+ });
+
+ test.concurrent('USER_USERNAME', async () => {
+ const [res] = await exe(`
+ <: USER_USERNAME
+ `);
+ expect(res).toStrictEqual(values.STR('ai'));
+ });
+ });
+
+ describe.sequential('not logged in', () => {
+ beforeAll(() => {
+ $iMock = null;
+ });
+
+ test.concurrent('USER_ID', async () => {
+ const [res] = await exe(`
+ <: USER_ID
+ `);
+ expect(res).toStrictEqual(values.NULL);
+ });
+
+ test.concurrent('USER_NAME', async () => {
+ const [res] = await exe(`
+ <: USER_NAME
+ `);
+ expect(res).toStrictEqual(values.NULL);
+ });
+
+ test.concurrent('USER_USERNAME', async () => {
+ const [res] = await exe(`
+ <: USER_USERNAME
+ `);
+ expect(res).toStrictEqual(values.NULL);
+ });
+ });
+ });
+
+ describe('dialog', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ test.sequential('ok', async () => {
+ osMock.alert.mockImplementationOnce(async ({ type, title, text }) => {
+ expect(type).toBe('success');
+ expect(title).toBe('Hello');
+ expect(text).toBe('world');
+ });
+ const [res] = await exe(`
+ <: Mk:dialog('Hello', 'world', 'success')
+ `);
+ expect(res).toStrictEqual(values.NULL);
+ expect(osMock.alert).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('omit type', async () => {
+ osMock.alert.mockImplementationOnce(async ({ type, title, text }) => {
+ expect(type).toBe('info');
+ expect(title).toBe('Hello');
+ expect(text).toBe('world');
+ });
+ const [res] = await exe(`
+ <: Mk:dialog('Hello', 'world')
+ `);
+ expect(res).toStrictEqual(values.NULL);
+ expect(osMock.alert).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('invalid type', async () => {
+ await expect(() => exe(`
+ <: Mk:dialog('Hello', 'world', 'invalid')
+ `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
+ expect(osMock.alert).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('confirm', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ test.sequential('ok', async () => {
+ osMock.confirm.mockImplementationOnce(async ({ type, title, text }) => {
+ expect(type).toBe('success');
+ expect(title).toBe('Hello');
+ expect(text).toBe('world');
+ return { canceled: false };
+ });
+ const [res] = await exe(`
+ <: Mk:confirm('Hello', 'world', 'success')
+ `);
+ expect(res).toStrictEqual(values.TRUE);
+ expect(osMock.confirm).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('omit type', async () => {
+ osMock.confirm
+ .mockImplementationOnce(async ({ type, title, text }) => {
+ expect(type).toBe('question');
+ expect(title).toBe('Hello');
+ expect(text).toBe('world');
+ return { canceled: false };
+ });
+ const [res] = await exe(`
+ <: Mk:confirm('Hello', 'world')
+ `);
+ expect(res).toStrictEqual(values.TRUE);
+ expect(osMock.confirm).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('canceled', async () => {
+ osMock.confirm.mockImplementationOnce(async ({ type, title, text }) => {
+ expect(type).toBe('question');
+ expect(title).toBe('Hello');
+ expect(text).toBe('world');
+ return { canceled: true };
+ });
+ const [res] = await exe(`
+ <: Mk:confirm('Hello', 'world')
+ `);
+ expect(res).toStrictEqual(values.FALSE);
+ expect(osMock.confirm).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('invalid type', async () => {
+ const confirm = osMock.confirm;
+ await expect(() => exe(`
+ <: Mk:confirm('Hello', 'world', 'invalid')
+ `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
+ expect(confirm).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('api', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ test.sequential('successful', async () => {
+ misskeyApiMock.mockImplementationOnce(
+ async (endpoint, data, token) => {
+ expect(endpoint).toBe('ping');
+ expect(data).toStrictEqual({});
+ expect(token).toBeNull();
+ return { pong: 1735657200000 };
+ }
+ );
+ const [res] = await exe(`
+ <: Mk:api('ping', {})
+ `);
+ expect(res).toStrictEqual(values.OBJ(new Map([
+ ['pong', values.NUM(1735657200000)],
+ ])));
+ expect(misskeyApiMock).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('with token', async () => {
+ misskeyApiMock.mockImplementationOnce(
+ async (endpoint, data, token) => {
+ expect(endpoint).toBe('ping');
+ expect(data).toStrictEqual({});
+ expect(token).toStrictEqual('xxxxxxxx');
+ return { pong: 1735657200000 };
+ }
+ );
+ const [res] = await exe(`
+ <: Mk:api('ping', {}, 'xxxxxxxx')
+ `);
+ expect(res).toStrictEqual(values.OBJ(new Map([
+ ['pong', values.NUM(1735657200000 )],
+ ])));
+ expect(misskeyApiMock).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('request failed', async () => {
+ misskeyApiMock.mockRejectedValueOnce('Not Found');
+ const [res] = await exe(`
+ <: Mk:api('this/endpoint/should/not/be/found', {})
+ `);
+ expect(res).toStrictEqual(
+ values.ERROR('request_failed', values.STR('Not Found'))
+ );
+ expect(misskeyApiMock).toHaveBeenCalledOnce();
+ });
+
+ test.sequential('invalid endpoint', async () => {
+ await expect(() => exe(`
+ Mk:api('https://example.com/api/ping', {})
+ `)).rejects.toStrictEqual(
+ new errors.AiScriptRuntimeError('invalid endpoint'),
+ );
+ expect(misskeyApiMock).not.toHaveBeenCalled();
+ });
+
+ test.sequential('missing param', async () => {
+ await expect(() => exe(`
+ Mk:api('ping')
+ `)).rejects.toStrictEqual(
+ new errors.AiScriptRuntimeError('expected param'),
+ );
+ expect(misskeyApiMock).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('save and load', () => {
+ beforeEach(() => {
+ miLocalStorage.removeItem('aiscript:widget:key');
+ });
+
+ afterEach(() => {
+ miLocalStorage.removeItem('aiscript:widget:key');
+ });
+
+ test.sequential('successful', async () => {
+ const [res] = await exe(`
+ Mk:save('key', 'value')
+ <: Mk:load('key')
+ `);
+ expect(miLocalStorage.getItem('aiscript:widget:key')).toBe('"value"');
+ expect(res).toStrictEqual(values.STR('value'));
+ });
+
+ test.sequential('missing value to save', async () => {
+ await expect(() => exe(`
+ Mk:save('key')
+ `)).rejects.toStrictEqual(
+ new errors.AiScriptRuntimeError('Expect anything, but got nothing.'),
+ );
+ });
+
+ test.sequential('not value found to load', async () => {
+ const [res] = await exe(`
+ <: Mk:load('key')
+ `);
+ expect(res).toStrictEqual(values.NULL);
+ });
+
+ test.sequential('remove existing', async () => {
+ const res = await exe(`
+ Mk:save('key', 'value')
+ <: Mk:load('key')
+ <: Mk:remove('key')
+ <: Mk:load('key')
+ `);
+ expect(res).toStrictEqual([values.STR('value'), values.NULL, values.NULL]);
+ });
+
+ test.sequential('remove nothing', async () => {
+ const res = await exe(`
+ <: Mk:load('key')
+ <: Mk:remove('key')
+ <: Mk:load('key')
+ `);
+ expect(res).toStrictEqual([values.NULL, values.NULL, values.NULL]);
+ });
+ });
+
+ test.concurrent('url', async () => {
+ vi.stubGlobal('location', { href: 'https://example.com/' });
+ const [res] = await exe(`
+ <: Mk:url()
+ `);
+ expect(res).toStrictEqual(values.STR('https://example.com/'));
+ });
+
+ test.concurrent('nyaize', async () => {
+ const [res] = await exe(`
+ <: Mk:nyaize('ãª')
+ `);
+ expect(res).toStrictEqual(values.STR('ã«ã‚ƒ'));
+ });
+});
diff --git a/packages/frontend/test/aiscript/common.test.ts b/packages/frontend/test/aiscript/common.test.ts
new file mode 100644
index 0000000000..acc48826ea
--- /dev/null
+++ b/packages/frontend/test/aiscript/common.test.ts
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { assertStringAndIsIn } from "@/scripts/aiscript/common.js";
+import { values } from "@syuilo/aiscript";
+import { describe, expect, test } from "vitest";
+
+describe('AiScript common script', () => {
+ test('assertStringAndIsIn', () => {
+ expect(
+ () => assertStringAndIsIn(values.STR('a'), ['a', 'b'])
+ ).not.toThrow();
+ expect(
+ () => assertStringAndIsIn(values.STR('c'), ['a', 'b'])
+ ).toThrow('"c" is not in "a", "b"');
+ expect(() => assertStringAndIsIn(
+ values.STR('invalid'),
+ ['left', 'center', 'right']
+ )).toThrow('"invalid" is not in "left", "center", "right"');
+ });
+});
diff --git a/packages/frontend/test/aiscript/ui.test.ts b/packages/frontend/test/aiscript/ui.test.ts
new file mode 100644
index 0000000000..5f77edbb49
--- /dev/null
+++ b/packages/frontend/test/aiscript/ui.test.ts
@@ -0,0 +1,825 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { registerAsUiLib } from '@/scripts/aiscript/ui.js';
+import { errors, Interpreter, Parser, values } from '@syuilo/aiscript';
+import { describe, expect, test } from 'vitest';
+import { type Ref, ref } from 'vue';
+import type {
+ AsUiButton,
+ AsUiButtons,
+ AsUiComponent,
+ AsUiMfm,
+ AsUiNumberInput,
+ AsUiRoot,
+ AsUiSelect,
+ AsUiSwitch,
+ AsUiText,
+ AsUiTextarea,
+ AsUiTextInput,
+} from '@/scripts/aiscript/ui.js';
+
+type ExeResult = {
+ root: AsUiRoot;
+ get: (id: string) => AsUiComponent;
+ outputs: values.Value[];
+}
+async function exe(script: string): Promise<ExeResult> {
+ const rootRef = ref<AsUiRoot>();
+ const componentRefs = ref<Ref<AsUiComponent>[]>([]);
+ const outputs: values.Value[] = [];
+
+ const interpreter = new Interpreter(
+ registerAsUiLib(componentRefs.value, (root) => {
+ rootRef.value = root.value;
+ }),
+ {
+ out: (value) => {
+ outputs.push(value);
+ }
+ }
+ );
+ const ast = Parser.parse(script);
+ await interpreter.exec(ast);
+
+ const root = rootRef.value;
+ if (root === undefined) {
+ expect.unreachable('root must not be undefined');
+ }
+ const components = componentRefs.value.map(
+ (componentRef) => componentRef.value,
+ );
+ expect(root).toBe(components[0]);
+ expect(root.type).toBe('root');
+ const get = (id: string) => {
+ const component = componentRefs.value.find(
+ (componentRef) => componentRef.value.id === id,
+ );
+ if (component === undefined) {
+ expect.unreachable(`component "${id}" is not defined`);
+ }
+ return component.value;
+ };
+ return { root, get, outputs };
+}
+
+describe('AiScript UI API', () => {
+ test.concurrent('root', async () => {
+ const { root } = await exe('');
+ expect(root.children).toStrictEqual([]);
+ });
+
+ describe('get', () => {
+ test.concurrent('some', async () => {
+ const { outputs } = await exe(`
+ Ui:C:text({}, 'id')
+ <: Ui:get('id')
+ `);
+ const output = outputs[0] as values.VObj;
+ expect(output.type).toBe('obj');
+ expect(output.value.size).toBe(2);
+ expect(output.value.get('id')).toStrictEqual(values.STR('id'));
+ expect(output.value.get('update')!.type).toBe('fn');
+ });
+
+ test.concurrent('none', async () => {
+ const { outputs } = await exe(`
+ <: Ui:get('id')
+ `);
+ expect(outputs).toStrictEqual([values.NULL]);
+ });
+ });
+
+ describe('update', () => {
+ test.concurrent('normal', async () => {
+ const { get } = await exe(`
+ let text = Ui:C:text({ text: 'a' }, 'id')
+ text.update({ text: 'b' })
+ `);
+ const text = get('id') as AsUiText;
+ expect(text.text).toBe('b');
+ });
+
+ test.concurrent('skip unknown key', async () => {
+ const { get } = await exe(`
+ let text = Ui:C:text({ text: 'a' }, 'id')
+ text.update({
+ text: 'b'
+ unknown: null
+ })
+ `);
+ const text = get('id') as AsUiText;
+ expect(text.text).toBe('b');
+ expect('unknown' in text).toBeFalsy();
+ });
+ });
+
+ describe('container', () => {
+ test.concurrent('all options', async () => {
+ const { root, get } = await exe(`
+ let text = Ui:C:text({
+ text: 'text'
+ }, 'id1')
+ let container = Ui:C:container({
+ children: [text]
+ align: 'left'
+ bgColor: '#fff'
+ fgColor: '#000'
+ font: 'sans-serif'
+ borderWidth: 1
+ borderColor: '#f00'
+ borderStyle: 'hidden'
+ borderRadius: 2
+ padding: 3
+ rounded: true
+ hidden: false
+ }, 'id2')
+ Ui:render([container])
+ `);
+ expect(root.children).toStrictEqual(['id2']);
+ expect(get('id2')).toStrictEqual({
+ type: 'container',
+ id: 'id2',
+ children: ['id1'],
+ align: 'left',
+ bgColor: '#fff',
+ fgColor: '#000',
+ font: 'sans-serif',
+ borderColor: '#f00',
+ borderWidth: 1,
+ borderStyle: 'hidden',
+ borderRadius: 2,
+ padding: 3,
+ rounded: true,
+ hidden: false,
+ });
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:container({}, 'id')
+ `);
+ expect(get('id')).toStrictEqual({
+ type: 'container',
+ id: 'id',
+ children: [],
+ align: undefined,
+ fgColor: undefined,
+ bgColor: undefined,
+ font: undefined,
+ borderWidth: undefined,
+ borderColor: undefined,
+ borderStyle: undefined,
+ borderRadius: undefined,
+ padding: undefined,
+ rounded: undefined,
+ hidden: undefined,
+ });
+ });
+
+ test.concurrent('invalid children', async () => {
+ await expect(() => exe(`
+ Ui:C:container({
+ children: 0
+ })
+ `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
+ });
+
+ test.concurrent('invalid align', async () => {
+ await expect(() => exe(`
+ Ui:C:container({
+ align: 'invalid'
+ })
+ `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
+ });
+
+ test.concurrent('invalid font', async () => {
+ await expect(() => exe(`
+ Ui:C:container({
+ font: 'invalid'
+ })
+ `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
+ });
+
+ test.concurrent('invalid borderStyle', async () => {
+ await expect(() => exe(`
+ Ui:C:container({
+ borderStyle: 'invalid'
+ })
+ `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
+ });
+ });
+
+ describe('text', () => {
+ test.concurrent('all options', async () => {
+ const { root, get } = await exe(`
+ let text = Ui:C:text({
+ text: 'a'
+ size: 1
+ bold: true
+ color: '#000'
+ font: 'sans-serif'
+ }, 'id')
+ Ui:render([text])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ expect(get('id')).toStrictEqual({
+ type: 'text',
+ id: 'id',
+ text: 'a',
+ size: 1,
+ bold: true,
+ color: '#000',
+ font: 'sans-serif',
+ });
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:text({}, 'id')
+ `);
+ expect(get('id')).toStrictEqual({
+ type: 'text',
+ id: 'id',
+ text: undefined,
+ size: undefined,
+ bold: undefined,
+ color: undefined,
+ font: undefined,
+ });
+ });
+
+ test.concurrent('invalid font', async () => {
+ await expect(() => exe(`
+ Ui:C:text({
+ font: 'invalid'
+ })
+ `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
+ });
+ });
+
+ describe('mfm', () => {
+ test.concurrent('all options', async () => {
+ const { root, get, outputs } = await exe(`
+ let mfm = Ui:C:mfm({
+ text: 'text'
+ size: 1
+ bold: true
+ color: '#000'
+ font: 'sans-serif'
+ onClickEv: print
+ }, 'id')
+ Ui:render([mfm])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ const { onClickEv, ...mfm } = get('id') as AsUiMfm;
+ expect(mfm).toStrictEqual({
+ type: 'mfm',
+ id: 'id',
+ text: 'text',
+ size: 1,
+ bold: true,
+ color: '#000',
+ font: 'sans-serif',
+ });
+ await onClickEv!('a');
+ expect(outputs).toStrictEqual([values.STR('a')]);
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:mfm({}, 'id')
+ `);
+ const { onClickEv, ...mfm } = get('id') as AsUiMfm;
+ expect(onClickEv).toBeTypeOf('function');
+ expect(mfm).toStrictEqual({
+ type: 'mfm',
+ id: 'id',
+ text: undefined,
+ size: undefined,
+ bold: undefined,
+ color: undefined,
+ font: undefined,
+ });
+ });
+
+ test.concurrent('invalid font', async () => {
+ await expect(() => exe(`
+ Ui:C:mfm({
+ font: 'invalid'
+ })
+ `)).rejects.toBeInstanceOf(errors.AiScriptRuntimeError);
+ });
+ });
+
+ describe('textInput', () => {
+ test.concurrent('all options', async () => {
+ const { root, get, outputs } = await exe(`
+ let text_input = Ui:C:textInput({
+ onInput: print
+ default: 'a'
+ label: 'b'
+ caption: 'c'
+ }, 'id')
+ Ui:render([text_input])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ const { onInput, ...textInput } = get('id') as AsUiTextInput;
+ expect(textInput).toStrictEqual({
+ type: 'textInput',
+ id: 'id',
+ default: 'a',
+ label: 'b',
+ caption: 'c',
+ });
+ await onInput!('d');
+ expect(outputs).toStrictEqual([values.STR('d')]);
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:textInput({}, 'id')
+ `);
+ const { onInput, ...textInput } = get('id') as AsUiTextInput;
+ expect(onInput).toBeTypeOf('function');
+ expect(textInput).toStrictEqual({
+ type: 'textInput',
+ id: 'id',
+ default: undefined,
+ label: undefined,
+ caption: undefined,
+ });
+ });
+ });
+
+ describe('textarea', () => {
+ test.concurrent('all options', async () => {
+ const { root, get, outputs } = await exe(`
+ let textarea = Ui:C:textarea({
+ onInput: print
+ default: 'a'
+ label: 'b'
+ caption: 'c'
+ }, 'id')
+ Ui:render([textarea])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ const { onInput, ...textarea } = get('id') as AsUiTextarea;
+ expect(textarea).toStrictEqual({
+ type: 'textarea',
+ id: 'id',
+ default: 'a',
+ label: 'b',
+ caption: 'c',
+ });
+ await onInput!('d');
+ expect(outputs).toStrictEqual([values.STR('d')]);
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:textarea({}, 'id')
+ `);
+ const { onInput, ...textarea } = get('id') as AsUiTextarea;
+ expect(onInput).toBeTypeOf('function');
+ expect(textarea).toStrictEqual({
+ type: 'textarea',
+ id: 'id',
+ default: undefined,
+ label: undefined,
+ caption: undefined,
+ });
+ });
+ });
+
+ describe('numberInput', () => {
+ test.concurrent('all options', async () => {
+ const { root, get, outputs } = await exe(`
+ let number_input = Ui:C:numberInput({
+ onInput: print
+ default: 1
+ label: 'a'
+ caption: 'b'
+ }, 'id')
+ Ui:render([number_input])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ const { onInput, ...numberInput } = get('id') as AsUiNumberInput;
+ expect(numberInput).toStrictEqual({
+ type: 'numberInput',
+ id: 'id',
+ default: 1,
+ label: 'a',
+ caption: 'b',
+ });
+ await onInput!(2);
+ expect(outputs).toStrictEqual([values.NUM(2)]);
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:numberInput({}, 'id')
+ `);
+ const { onInput, ...numberInput } = get('id') as AsUiNumberInput;
+ expect(onInput).toBeTypeOf('function');
+ expect(numberInput).toStrictEqual({
+ type: 'numberInput',
+ id: 'id',
+ default: undefined,
+ label: undefined,
+ caption: undefined,
+ });
+ });
+ });
+
+ describe('button', () => {
+ test.concurrent('all options', async () => {
+ const { root, get, outputs } = await exe(`
+ let button = Ui:C:button({
+ text: 'a'
+ onClick: @() { <: 'clicked' }
+ primary: true
+ rounded: false
+ disabled: false
+ }, 'id')
+ Ui:render([button])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ const { onClick, ...button } = get('id') as AsUiButton;
+ expect(button).toStrictEqual({
+ type: 'button',
+ id: 'id',
+ text: 'a',
+ primary: true,
+ rounded: false,
+ disabled: false,
+ });
+ await onClick!();
+ expect(outputs).toStrictEqual([values.STR('clicked')]);
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:button({}, 'id')
+ `);
+ const { onClick, ...button } = get('id') as AsUiButton;
+ expect(onClick).toBeTypeOf('function');
+ expect(button).toStrictEqual({
+ type: 'button',
+ id: 'id',
+ text: undefined,
+ primary: undefined,
+ rounded: undefined,
+ disabled: undefined,
+ });
+ });
+ });
+
+ describe('buttons', () => {
+ test.concurrent('all options', async () => {
+ const { root, get } = await exe(`
+ let buttons = Ui:C:buttons({
+ buttons: []
+ }, 'id')
+ Ui:render([buttons])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ expect(get('id')).toStrictEqual({
+ type: 'buttons',
+ id: 'id',
+ buttons: [],
+ });
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:buttons({}, 'id')
+ `);
+ expect(get('id')).toStrictEqual({
+ type: 'buttons',
+ id: 'id',
+ buttons: [],
+ });
+ });
+
+ test.concurrent('some buttons', async () => {
+ const { root, get, outputs } = await exe(`
+ let buttons = Ui:C:buttons({
+ buttons: [
+ {
+ text: 'a'
+ onClick: @() { <: 'clicked a' }
+ primary: true
+ rounded: false
+ disabled: false
+ }
+ {
+ text: 'b'
+ onClick: @() { <: 'clicked b' }
+ primary: true
+ rounded: false
+ disabled: false
+ }
+ ]
+ }, 'id')
+ Ui:render([buttons])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ const { buttons, ...buttonsOptions } = get('id') as AsUiButtons;
+ expect(buttonsOptions).toStrictEqual({
+ type: 'buttons',
+ id: 'id',
+ });
+ expect(buttons!.length).toBe(2);
+ const { onClick: onClickA, ...buttonA } = buttons![0];
+ expect(buttonA).toStrictEqual({
+ text: 'a',
+ primary: true,
+ rounded: false,
+ disabled: false,
+ });
+ const { onClick: onClickB, ...buttonB } = buttons![1];
+ expect(buttonB).toStrictEqual({
+ text: 'b',
+ primary: true,
+ rounded: false,
+ disabled: false,
+ });
+ await onClickA!();
+ await onClickB!();
+ expect(outputs).toStrictEqual(
+ [values.STR('clicked a'), values.STR('clicked b')]
+ );
+ });
+ });
+
+ describe('switch', () => {
+ test.concurrent('all options', async () => {
+ const { root, get, outputs } = await exe(`
+ let switch = Ui:C:switch({
+ onChange: print
+ default: false
+ label: 'a'
+ caption: 'b'
+ }, 'id')
+ Ui:render([switch])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ const { onChange, ...switchOptions } = get('id') as AsUiSwitch;
+ expect(switchOptions).toStrictEqual({
+ type: 'switch',
+ id: 'id',
+ default: false,
+ label: 'a',
+ caption: 'b',
+ });
+ await onChange!(true);
+ expect(outputs).toStrictEqual([values.TRUE]);
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:switch({}, 'id')
+ `);
+ const { onChange, ...switchOptions } = get('id') as AsUiSwitch;
+ expect(onChange).toBeTypeOf('function');
+ expect(switchOptions).toStrictEqual({
+ type: 'switch',
+ id: 'id',
+ default: undefined,
+ label: undefined,
+ caption: undefined,
+ });
+ });
+ });
+
+ describe('select', () => {
+ test.concurrent('all options', async () => {
+ const { root, get, outputs } = await exe(`
+ let select = Ui:C:select({
+ items: [
+ { text: 'A', value: 'a' }
+ { text: 'B', value: 'b' }
+ ]
+ onChange: print
+ default: 'a'
+ label: 'c'
+ caption: 'd'
+ }, 'id')
+ Ui:render([select])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ const { onChange, ...select } = get('id') as AsUiSelect;
+ expect(select).toStrictEqual({
+ type: 'select',
+ id: 'id',
+ items: [
+ { text: 'A', value: 'a' },
+ { text: 'B', value: 'b' },
+ ],
+ default: 'a',
+ label: 'c',
+ caption: 'd',
+ });
+ await onChange!('b');
+ expect(outputs).toStrictEqual([values.STR('b')]);
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:select({}, 'id')
+ `);
+ const { onChange, ...select } = get('id') as AsUiSelect;
+ expect(onChange).toBeTypeOf('function');
+ expect(select).toStrictEqual({
+ type: 'select',
+ id: 'id',
+ items: [],
+ default: undefined,
+ label: undefined,
+ caption: undefined,
+ });
+ });
+
+ test.concurrent('omit item values', async () => {
+ const { get } = await exe(`
+ let select = Ui:C:select({
+ items: [
+ { text: 'A' }
+ { text: 'B' }
+ ]
+ }, 'id')
+ `);
+ const { onChange, ...select } = get('id') as AsUiSelect;
+ expect(onChange).toBeTypeOf('function');
+ expect(select).toStrictEqual({
+ type: 'select',
+ id: 'id',
+ items: [
+ { text: 'A', value: 'A' },
+ { text: 'B', value: 'B' },
+ ],
+ default: undefined,
+ label: undefined,
+ caption: undefined,
+ });
+ });
+ });
+
+ describe('folder', () => {
+ test.concurrent('all options', async () => {
+ const { root, get } = await exe(`
+ let folder = Ui:C:folder({
+ children: []
+ title: 'a'
+ opened: true
+ }, 'id')
+ Ui:render([folder])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ expect(get('id')).toStrictEqual({
+ type: 'folder',
+ id: 'id',
+ children: [],
+ title: 'a',
+ opened: true,
+ });
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:folder({}, 'id')
+ `);
+ expect(get('id')).toStrictEqual({
+ type: 'folder',
+ id: 'id',
+ children: [],
+ title: '',
+ opened: true,
+ });
+ });
+
+ test.concurrent('some children', async () => {
+ const { get } = await exe(`
+ let text = Ui:C:text({
+ text: 'text'
+ }, 'id1')
+ Ui:C:folder({
+ children: [text]
+ }, 'id2')
+ `);
+ expect(get('id2')).toStrictEqual({
+ type: 'folder',
+ id: 'id2',
+ children: ['id1'],
+ title: '',
+ opened: true,
+ });
+ });
+ });
+
+ describe('postFormButton', () => {
+ test.concurrent('all options', async () => {
+ const { root, get } = await exe(`
+ let post_form_button = Ui:C:postFormButton({
+ text: 'a'
+ primary: true
+ rounded: false
+ form: {
+ text: 'b'
+ cw: 'c'
+ visibility: 'public'
+ localOnly: true
+ }
+ }, 'id')
+ Ui:render([post_form_button])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ expect(get('id')).toStrictEqual({
+ type: 'postFormButton',
+ id: 'id',
+ text: 'a',
+ primary: true,
+ rounded: false,
+ form: {
+ text: 'b',
+ cw: 'c',
+ visibility: 'public',
+ localOnly: true,
+ },
+ });
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:postFormButton({}, 'id')
+ `);
+ expect(get('id')).toStrictEqual({
+ type: 'postFormButton',
+ id: 'id',
+ text: undefined,
+ primary: undefined,
+ rounded: undefined,
+ form: { text: '' },
+ });
+ });
+ });
+
+ describe('postForm', () => {
+ test.concurrent('all options', async () => {
+ const { root, get } = await exe(`
+ let post_form = Ui:C:postForm({
+ form: {
+ text: 'a'
+ cw: 'b'
+ visibility: 'public'
+ localOnly: true
+ }
+ }, 'id')
+ Ui:render([post_form])
+ `);
+ expect(root.children).toStrictEqual(['id']);
+ expect(get('id')).toStrictEqual({
+ type: 'postForm',
+ id: 'id',
+ form: {
+ text: 'a',
+ cw: 'b',
+ visibility: 'public',
+ localOnly: true,
+ },
+ });
+ });
+
+ test.concurrent('minimum options', async () => {
+ const { get } = await exe(`
+ Ui:C:postForm({}, 'id')
+ `);
+ expect(get('id')).toStrictEqual({
+ type: 'postForm',
+ id: 'id',
+ form: { text: '' },
+ });
+ });
+
+ test.concurrent('minimum options for form', async () => {
+ const { get } = await exe(`
+ Ui:C:postForm({
+ form: { text: '' }
+ }, 'id')
+ `);
+ expect(get('id')).toStrictEqual({
+ type: 'postForm',
+ id: 'id',
+ form: {
+ text: '',
+ cw: undefined,
+ visibility: undefined,
+ localOnly: undefined,
+ },
+ });
+ });
+ });
+});
diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json
index bbf9d653cf..169bd5029c 100644
--- a/packages/frontend/tsconfig.json
+++ b/packages/frontend/tsconfig.json
@@ -10,8 +10,8 @@
"declaration": false,
"sourceMap": false,
"target": "ES2022",
- "module": "nodenext",
- "moduleResolution": "nodenext",
+ "module": "ES2022",
+ "moduleResolution": "Bundler",
"removeComments": false,
"noLib": false,
"strict": true,
diff --git a/packages/frontend/vite.config.local-dev.ts b/packages/frontend/vite.config.local-dev.ts
deleted file mode 100644
index d588f83138..0000000000
--- a/packages/frontend/vite.config.local-dev.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import dns from 'dns';
-import { readFile } from 'node:fs/promises';
-import type { IncomingMessage } from 'node:http';
-import { defineConfig } from 'vite';
-import type { UserConfig } from 'vite';
-import * as yaml from 'js-yaml';
-import locales from '../../locales/index.js';
-import { getConfig } from './vite.config.js';
-
-dns.setDefaultResultOrder('ipv4first');
-
-const defaultConfig = getConfig();
-
-const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8'));
-
-const httpUrl = `http://localhost:${port}/`;
-const websocketUrl = `ws://localhost:${port}/`;
-const embedUrl = `http://localhost:5174/`;
-
-// activitypubリクエストã¯Proxyを通ã—ã€ãれ以外ã¯Viteã®é–‹ç™ºã‚µãƒ¼ãƒãƒ¼ã‚’è¿”ã™
-function varyHandler(req: IncomingMessage) {
- if (req.headers.accept?.includes('application/activity+json')) {
- return null;
- }
- return '/index.html';
-}
-
-const devConfig: UserConfig = {
- // 基本ã®è¨­å®šã¯ vite.config.js ã‹ã‚‰å¼•ãç¶™ã
- ...defaultConfig,
- root: 'src',
- publicDir: '../assets',
- base: './',
- server: {
- host: 'localhost',
- port: 5173,
- proxy: {
- '/api': {
- changeOrigin: true,
- target: httpUrl,
- },
- '/assets': httpUrl,
- '/static-assets': httpUrl,
- '/client-assets': httpUrl,
- '/files': httpUrl,
- '/twemoji': httpUrl,
- '/fluent-emoji': httpUrl,
- '/tossface': httpUrl,
- '/sw.js': httpUrl,
- '/streaming': {
- target: websocketUrl,
- ws: true,
- },
- '/favicon.ico': httpUrl,
- '/robots.txt': httpUrl,
- '/embed.js': httpUrl,
- '/embed': {
- target: embedUrl,
- ws: true,
- },
- '/identicon': {
- target: httpUrl,
- rewrite(path) {
- return path.replace('@localhost:5173', '');
- },
- },
- '/url': httpUrl,
- '/proxy': httpUrl,
- '/_info_card_': httpUrl,
- '/bios': httpUrl,
- '/cli': httpUrl,
- '/inbox': httpUrl,
- '/emoji/': httpUrl,
- '/notes': {
- target: httpUrl,
- bypass: varyHandler,
- },
- '/users': {
- target: httpUrl,
- bypass: varyHandler,
- },
- '/.well-known': {
- target: httpUrl,
- },
- },
- },
- build: {
- ...defaultConfig.build,
- rollupOptions: {
- ...defaultConfig.build?.rollupOptions,
- input: 'index.html',
- },
- },
-
- define: {
- ...defaultConfig.define,
- _LANGS_FULL_: JSON.stringify(Object.entries(locales)),
- },
-};
-
-export default defineConfig(({ command, mode }) => devConfig);
-
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 2ba63c010f..51940486bd 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -3,6 +3,9 @@ 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 * as yaml from 'js-yaml';
+import { promises as fsp } from 'fs';
+
import locales from '../../locales/index.js';
import meta from '../../package.json';
import packageInfo from './package.json' with { type: 'json' };
@@ -10,6 +13,9 @@ import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-modul
import pluginJson5 from './vite.json5.js';
import { pluginReplaceIcons } from './vite.replaceIcons.js';
+const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null;
+const host = url ? (new URL(url)).hostname : undefined;
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue', '.wasm'];
/**
@@ -76,7 +82,14 @@ export function getConfig(): UserConfig {
base: '/vite/',
server: {
+ host,
port: 5173,
+ hmr: {
+ // ãƒãƒƒã‚¯ã‚¨ãƒ³ãƒ‰çµŒç”±ã§ã®èµ·å‹•時ã€Viteã¯5173経由ã§ã‚¢ã‚»ãƒƒãƒˆã‚’å‚ç…§ã—ã¦ã„ã‚‹ã¨æ€ã„込んã§ã„ã‚‹ãŒå®Ÿéš›ã¯3000ã‹ã‚‰é…ä¿¡ã•れる
+ // ãã®ãŸã‚ã€ãƒãƒƒã‚¯ã‚¨ãƒ³ãƒ‰ã®WSサーãƒãƒ¼ã«HMRã®WSリクエストãŒå¸åŽã•れã¦ã—ã¾ã„ã€æ­£ã—ãHMRãŒæ©Ÿèƒ½ã—ãªã„
+ // クライアントå´ã®WSãƒãƒ¼ãƒˆã‚’Viteサーãƒãƒ¼ã®ãƒãƒ¼ãƒˆã«å¼·åˆ¶ã•ã›ã‚‹ã“ã¨ã§ã€æ­£ã—ãHMRãŒæ©Ÿèƒ½ã™ã‚‹ã‚ˆã†ã«ãªã‚‹
+ clientPort: 5173,
+ },
headers: { // ãªã‚“ã‹åйã‹ãªã„
'X-Frame-Options': 'DENY',
},
diff --git a/packages/misskey-bubble-game/build.js b/packages/misskey-bubble-game/build.js
index a80b71646f..5d534cc6fd 100644
--- a/packages/misskey-bubble-game/build.js
+++ b/packages/misskey-bubble-game/build.js
@@ -23,10 +23,14 @@ const options = {
sourcemap: 'linked',
};
+const args = process.argv.slice(2).map(arg => arg.toLowerCase());
+
// builté…下をã™ã¹ã¦å‰Šé™¤ã™ã‚‹
-fs.rmSync('./built', { recursive: true, force: true });
+if (!args.includes('--no-clean')) {
+ fs.rmSync('./built', { recursive: true, force: true });
+}
-if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) {
+if (args.includes('--watch')) {
await watchSrc();
} else {
await buildSrc();
diff --git a/packages/misskey-js/LICENSE b/packages/misskey-js/LICENSE
index 63762b85d8..16352625db 100644
--- a/packages/misskey-js/LICENSE
+++ b/packages/misskey-js/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021-2024 syuilo and other contributors
+Copyright (c) 2021-2025 syuilo and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/misskey-js/api-extractor.json b/packages/misskey-js/api-extractor.json
index a95281a6d5..627a245a49 100644
--- a/packages/misskey-js/api-extractor.json
+++ b/packages/misskey-js/api-extractor.json
@@ -62,6 +62,8 @@
*/
"bundledPackages": [],
+ "newlineKind": "lf",
+
/**
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
*/
diff --git a/packages/misskey-js/build.js b/packages/misskey-js/build.js
index a80b71646f..b794592815 100644
--- a/packages/misskey-js/build.js
+++ b/packages/misskey-js/build.js
@@ -24,9 +24,14 @@ const options = {
};
// builté…下をã™ã¹ã¦å‰Šé™¤ã™ã‚‹
-fs.rmSync('./built', { recursive: true, force: true });
+const args = process.argv.slice(2).map(arg => arg.toLowerCase());
-if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) {
+// builté…下をã™ã¹ã¦å‰Šé™¤ã™ã‚‹
+if (!args.includes('--no-clean')) {
+ fs.rmSync('./built', { recursive: true, force: true });
+}
+
+if (args.includes('--watch')) {
await watchSrc();
} else {
await buildSrc();
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 8ae9791d7f..1b4675b938 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
- "version": "2025.1.0-dev",
+ "version": "2025.2.0-dev",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
diff --git a/packages/misskey-js/src/api.ts b/packages/misskey-js/src/api.ts
index ed1282957f..e663d712a7 100644
--- a/packages/misskey-js/src/api.ts
+++ b/packages/misskey-js/src/api.ts
@@ -44,7 +44,7 @@ export class APIClient {
credential?: APIClient['credential'];
fetch?: APIClient['fetch'] | null | undefined;
}) {
- this.origin = opts.origin;
+ this.origin = opts.origin.replace(/\/$/, '');
this.credential = opts.credential;
// ãƒã‚¤ãƒ†ã‚£ãƒ–関数をãã®ã¾ã¾å¤‰æ•°ã«ä»£å…¥ã—ã¦ä½¿ãŠã†ã¨ã™ã‚‹ã¨Chromiumã§ã¯Illegal invocationエラーãŒç™ºç”Ÿã™ã‚‹ãŸã‚ã€
// 環境ã§å®Ÿè£…ã•れã¦ã„ã‚‹fetchを使ã†å ´åˆã¯ç„¡å関数ã§ãƒ©ãƒƒãƒ—ã—ã¦ä½¿ç”¨ã™ã‚‹
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index ccb513b7f9..faabf10c43 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -5,7 +5,7 @@ declare module '../api.js' {
export interface APIClient {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:meta*
*/
request<E extends 'admin/meta', P extends Endpoints[E]['req']>(
@@ -16,7 +16,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports*
*/
request<E extends 'admin/abuse-user-reports', P extends Endpoints[E]['req']>(
@@ -27,7 +27,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *read:admin:abuse-report:notification-recipient*
*/
@@ -39,7 +39,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *read:admin:abuse-report:notification-recipient*
*/
@@ -51,7 +51,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *write:admin:abuse-report:notification-recipient*
*/
@@ -63,7 +63,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *write:admin:abuse-report:notification-recipient*
*/
@@ -75,7 +75,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *write:admin:abuse-report:notification-recipient*
*/
@@ -87,7 +87,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'admin/accounts/create', P extends Endpoints[E]['req']>(
@@ -98,7 +98,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:account*
*/
request<E extends 'admin/accounts/delete', P extends Endpoints[E]['req']>(
@@ -109,7 +109,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:account*
*/
request<E extends 'admin/accounts/find-by-email', P extends Endpoints[E]['req']>(
@@ -120,7 +120,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:ad*
*/
request<E extends 'admin/ad/create', P extends Endpoints[E]['req']>(
@@ -131,7 +131,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:ad*
*/
request<E extends 'admin/ad/delete', P extends Endpoints[E]['req']>(
@@ -142,7 +142,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:ad*
*/
request<E extends 'admin/ad/list', P extends Endpoints[E]['req']>(
@@ -153,7 +153,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:ad*
*/
request<E extends 'admin/ad/update', P extends Endpoints[E]['req']>(
@@ -164,7 +164,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
*/
request<E extends 'admin/announcements/create', P extends Endpoints[E]['req']>(
@@ -175,7 +175,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
*/
request<E extends 'admin/announcements/delete', P extends Endpoints[E]['req']>(
@@ -186,7 +186,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:announcements*
*/
request<E extends 'admin/announcements/list', P extends Endpoints[E]['req']>(
@@ -197,7 +197,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
*/
request<E extends 'admin/announcements/update', P extends Endpoints[E]['req']>(
@@ -208,7 +208,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations*
*/
request<E extends 'admin/avatar-decorations/create', P extends Endpoints[E]['req']>(
@@ -219,7 +219,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations*
*/
request<E extends 'admin/avatar-decorations/delete', P extends Endpoints[E]['req']>(
@@ -230,7 +230,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:avatar-decorations*
*/
request<E extends 'admin/avatar-decorations/list', P extends Endpoints[E]['req']>(
@@ -241,7 +241,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations*
*/
request<E extends 'admin/avatar-decorations/update', P extends Endpoints[E]['req']>(
@@ -252,7 +252,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user*
*/
request<E extends 'admin/delete-all-files-of-a-user', P extends Endpoints[E]['req']>(
@@ -263,7 +263,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar*
*/
request<E extends 'admin/unset-user-avatar', P extends Endpoints[E]['req']>(
@@ -274,7 +274,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner*
*/
request<E extends 'admin/unset-user-banner', P extends Endpoints[E]['req']>(
@@ -285,7 +285,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:drive*
*/
request<E extends 'admin/drive/clean-remote-files', P extends Endpoints[E]['req']>(
@@ -296,7 +296,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:drive*
*/
request<E extends 'admin/drive/cleanup', P extends Endpoints[E]['req']>(
@@ -307,7 +307,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:drive*
*/
request<E extends 'admin/drive/files', P extends Endpoints[E]['req']>(
@@ -318,7 +318,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:drive*
*/
request<E extends 'admin/drive/show-file', P extends Endpoints[E]['req']>(
@@ -329,7 +329,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/add-aliases-bulk', P extends Endpoints[E]['req']>(
@@ -340,7 +340,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/add', P extends Endpoints[E]['req']>(
@@ -351,7 +351,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/copy', P extends Endpoints[E]['req']>(
@@ -362,7 +362,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/delete-bulk', P extends Endpoints[E]['req']>(
@@ -373,7 +373,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/delete', P extends Endpoints[E]['req']>(
@@ -384,7 +384,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -396,7 +396,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:emoji*
*/
request<E extends 'admin/emoji/list-remote', P extends Endpoints[E]['req']>(
@@ -407,7 +407,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:emoji*
*/
request<E extends 'admin/emoji/list', P extends Endpoints[E]['req']>(
@@ -418,7 +418,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/remove-aliases-bulk', P extends Endpoints[E]['req']>(
@@ -429,7 +429,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/set-aliases-bulk', P extends Endpoints[E]['req']>(
@@ -440,7 +440,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/set-category-bulk', P extends Endpoints[E]['req']>(
@@ -451,7 +451,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/set-license-bulk', P extends Endpoints[E]['req']>(
@@ -462,7 +462,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
*/
request<E extends 'admin/emoji/update', P extends Endpoints[E]['req']>(
@@ -473,7 +473,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:federation*
*/
request<E extends 'admin/federation/delete-all-files', P extends Endpoints[E]['req']>(
@@ -484,7 +484,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:federation*
*/
request<E extends 'admin/federation/refresh-remote-instance-metadata', P extends Endpoints[E]['req']>(
@@ -495,7 +495,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:federation*
*/
request<E extends 'admin/federation/remove-all-following', P extends Endpoints[E]['req']>(
@@ -506,7 +506,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:federation*
*/
request<E extends 'admin/federation/update-instance', P extends Endpoints[E]['req']>(
@@ -517,7 +517,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:index-stats*
*/
request<E extends 'admin/get-index-stats', P extends Endpoints[E]['req']>(
@@ -528,7 +528,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:table-stats*
*/
request<E extends 'admin/get-table-stats', P extends Endpoints[E]['req']>(
@@ -539,7 +539,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:user-ips*
*/
request<E extends 'admin/get-user-ips', P extends Endpoints[E]['req']>(
@@ -550,7 +550,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:invite-codes*
*/
request<E extends 'admin/invite/create', P extends Endpoints[E]['req']>(
@@ -561,7 +561,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:invite-codes*
*/
request<E extends 'admin/invite/list', P extends Endpoints[E]['req']>(
@@ -572,7 +572,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:promo*
*/
request<E extends 'admin/promo/create', P extends Endpoints[E]['req']>(
@@ -583,7 +583,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:queue*
*/
request<E extends 'admin/queue/clear', P extends Endpoints[E]['req']>(
@@ -594,7 +594,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:queue*
*/
request<E extends 'admin/queue/deliver-delayed', P extends Endpoints[E]['req']>(
@@ -605,7 +605,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:queue*
*/
request<E extends 'admin/queue/inbox-delayed', P extends Endpoints[E]['req']>(
@@ -616,7 +616,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:queue*
*/
request<E extends 'admin/queue/promote', P extends Endpoints[E]['req']>(
@@ -627,7 +627,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:emoji*
*/
request<E extends 'admin/queue/stats', P extends Endpoints[E]['req']>(
@@ -638,7 +638,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:relays*
*/
request<E extends 'admin/relays/add', P extends Endpoints[E]['req']>(
@@ -649,7 +649,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:relays*
*/
request<E extends 'admin/relays/list', P extends Endpoints[E]['req']>(
@@ -660,7 +660,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:relays*
*/
request<E extends 'admin/relays/remove', P extends Endpoints[E]['req']>(
@@ -671,7 +671,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:reset-password*
*/
request<E extends 'admin/reset-password', P extends Endpoints[E]['req']>(
@@ -682,7 +682,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
*/
request<E extends 'admin/resolve-abuse-user-report', P extends Endpoints[E]['req']>(
@@ -693,7 +693,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
*/
request<E extends 'admin/forward-abuse-user-report', P extends Endpoints[E]['req']>(
@@ -704,7 +704,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
*/
request<E extends 'admin/update-abuse-user-report', P extends Endpoints[E]['req']>(
@@ -715,7 +715,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:send-email*
*/
request<E extends 'admin/send-email', P extends Endpoints[E]['req']>(
@@ -726,7 +726,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:server-info*
*/
request<E extends 'admin/server-info', P extends Endpoints[E]['req']>(
@@ -737,7 +737,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log*
*/
request<E extends 'admin/show-moderation-logs', P extends Endpoints[E]['req']>(
@@ -748,7 +748,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:show-user*
*/
request<E extends 'admin/show-user', P extends Endpoints[E]['req']>(
@@ -759,7 +759,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:show-user*
*/
request<E extends 'admin/show-users', P extends Endpoints[E]['req']>(
@@ -770,7 +770,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user*
*/
request<E extends 'admin/nsfw-user', P extends Endpoints[E]['req']>(
@@ -781,7 +781,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user*
*/
request<E extends 'admin/unnsfw-user', P extends Endpoints[E]['req']>(
@@ -792,7 +792,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:silence-user*
*/
request<E extends 'admin/silence-user', P extends Endpoints[E]['req']>(
@@ -803,7 +803,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user*
*/
request<E extends 'admin/unsilence-user', P extends Endpoints[E]['req']>(
@@ -814,7 +814,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user*
*/
request<E extends 'admin/suspend-user', P extends Endpoints[E]['req']>(
@@ -825,7 +825,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:approve-user*
*/
request<E extends 'admin/approve-user', P extends Endpoints[E]['req']>(
@@ -836,7 +836,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:decline-user*
*/
request<E extends 'admin/decline-user', P extends Endpoints[E]['req']>(
@@ -847,7 +847,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user*
*/
request<E extends 'admin/unsuspend-user', P extends Endpoints[E]['req']>(
@@ -858,7 +858,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:meta*
*/
request<E extends 'admin/update-meta', P extends Endpoints[E]['req']>(
@@ -869,7 +869,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:delete-account*
*/
request<E extends 'admin/delete-account', P extends Endpoints[E]['req']>(
@@ -880,7 +880,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:user-note*
*/
request<E extends 'admin/update-user-note', P extends Endpoints[E]['req']>(
@@ -891,7 +891,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
*/
request<E extends 'admin/roles/create', P extends Endpoints[E]['req']>(
@@ -902,7 +902,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
*/
request<E extends 'admin/roles/delete', P extends Endpoints[E]['req']>(
@@ -913,7 +913,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:roles*
*/
request<E extends 'admin/roles/list', P extends Endpoints[E]['req']>(
@@ -924,7 +924,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:admin:roles*
*/
request<E extends 'admin/roles/show', P extends Endpoints[E]['req']>(
@@ -935,7 +935,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
*/
request<E extends 'admin/roles/update', P extends Endpoints[E]['req']>(
@@ -946,7 +946,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
*/
request<E extends 'admin/roles/assign', P extends Endpoints[E]['req']>(
@@ -957,7 +957,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
*/
request<E extends 'admin/roles/unassign', P extends Endpoints[E]['req']>(
@@ -968,7 +968,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
*/
request<E extends 'admin/roles/update-default-policies', P extends Endpoints[E]['req']>(
@@ -979,7 +979,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No* / **Permission**: *read:admin:roles*
*/
request<E extends 'admin/roles/users', P extends Endpoints[E]['req']>(
@@ -990,7 +990,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *write:admin:system-webhook*
*/
@@ -1002,7 +1002,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *write:admin:system-webhook*
*/
@@ -1014,7 +1014,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *write:admin:system-webhook*
*/
@@ -1026,7 +1026,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *write:admin:system-webhook*
*/
@@ -1038,7 +1038,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *write:admin:system-webhook*
*/
@@ -1050,7 +1050,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *read:admin:system-webhook*
*/
@@ -1062,7 +1062,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'announcements', P extends Endpoints[E]['req']>(
@@ -1073,7 +1073,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'announcements/show', P extends Endpoints[E]['req']>(
@@ -1084,7 +1084,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'antennas/create', P extends Endpoints[E]['req']>(
@@ -1095,7 +1095,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'antennas/delete', P extends Endpoints[E]['req']>(
@@ -1106,7 +1106,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'antennas/list', P extends Endpoints[E]['req']>(
@@ -1117,7 +1117,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'antennas/notes', P extends Endpoints[E]['req']>(
@@ -1128,7 +1128,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'antennas/show', P extends Endpoints[E]['req']>(
@@ -1139,7 +1139,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'antennas/update', P extends Endpoints[E]['req']>(
@@ -1150,7 +1150,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:federation*
*/
request<E extends 'ap/get', P extends Endpoints[E]['req']>(
@@ -1161,7 +1161,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'ap/show', P extends Endpoints[E]['req']>(
@@ -1172,7 +1172,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'app/create', P extends Endpoints[E]['req']>(
@@ -1183,7 +1183,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'app/show', P extends Endpoints[E]['req']>(
@@ -1194,7 +1194,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -1206,7 +1206,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'auth/session/generate', P extends Endpoints[E]['req']>(
@@ -1217,7 +1217,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'auth/session/show', P extends Endpoints[E]['req']>(
@@ -1228,7 +1228,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'auth/session/userkey', P extends Endpoints[E]['req']>(
@@ -1239,7 +1239,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:blocks*
*/
request<E extends 'blocking/create', P extends Endpoints[E]['req']>(
@@ -1250,7 +1250,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:blocks*
*/
request<E extends 'blocking/delete', P extends Endpoints[E]['req']>(
@@ -1261,7 +1261,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:blocks*
*/
request<E extends 'blocking/list', P extends Endpoints[E]['req']>(
@@ -1272,7 +1272,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:channels*
*/
request<E extends 'channels/create', P extends Endpoints[E]['req']>(
@@ -1283,7 +1283,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'channels/featured', P extends Endpoints[E]['req']>(
@@ -1294,7 +1294,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:channels*
*/
request<E extends 'channels/follow', P extends Endpoints[E]['req']>(
@@ -1305,7 +1305,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:channels*
*/
request<E extends 'channels/followed', P extends Endpoints[E]['req']>(
@@ -1316,7 +1316,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:channels*
*/
request<E extends 'channels/owned', P extends Endpoints[E]['req']>(
@@ -1327,7 +1327,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'channels/show', P extends Endpoints[E]['req']>(
@@ -1338,7 +1338,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'channels/timeline', P extends Endpoints[E]['req']>(
@@ -1349,7 +1349,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:channels*
*/
request<E extends 'channels/unfollow', P extends Endpoints[E]['req']>(
@@ -1360,7 +1360,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:channels*
*/
request<E extends 'channels/update', P extends Endpoints[E]['req']>(
@@ -1371,7 +1371,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:channels*
*/
request<E extends 'channels/favorite', P extends Endpoints[E]['req']>(
@@ -1382,7 +1382,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:channels*
*/
request<E extends 'channels/unfavorite', P extends Endpoints[E]['req']>(
@@ -1393,7 +1393,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:channels*
*/
request<E extends 'channels/my-favorites', P extends Endpoints[E]['req']>(
@@ -1404,7 +1404,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'channels/search', P extends Endpoints[E]['req']>(
@@ -1415,7 +1415,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/active-users', P extends Endpoints[E]['req']>(
@@ -1426,7 +1426,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/ap-request', P extends Endpoints[E]['req']>(
@@ -1437,7 +1437,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/drive', P extends Endpoints[E]['req']>(
@@ -1448,7 +1448,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/federation', P extends Endpoints[E]['req']>(
@@ -1459,7 +1459,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/instance', P extends Endpoints[E]['req']>(
@@ -1470,7 +1470,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/notes', P extends Endpoints[E]['req']>(
@@ -1481,7 +1481,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/user/drive', P extends Endpoints[E]['req']>(
@@ -1492,7 +1492,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/user/following', P extends Endpoints[E]['req']>(
@@ -1503,7 +1503,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/user/notes', P extends Endpoints[E]['req']>(
@@ -1514,7 +1514,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/user/pv', P extends Endpoints[E]['req']>(
@@ -1525,7 +1525,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/user/reactions', P extends Endpoints[E]['req']>(
@@ -1536,7 +1536,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'charts/users', P extends Endpoints[E]['req']>(
@@ -1547,7 +1547,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'clips/add-note', P extends Endpoints[E]['req']>(
@@ -1558,7 +1558,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'clips/remove-note', P extends Endpoints[E]['req']>(
@@ -1569,7 +1569,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'clips/create', P extends Endpoints[E]['req']>(
@@ -1580,7 +1580,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'clips/delete', P extends Endpoints[E]['req']>(
@@ -1591,7 +1591,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'clips/list', P extends Endpoints[E]['req']>(
@@ -1602,7 +1602,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No* / **Permission**: *read:account*
*/
request<E extends 'clips/notes', P extends Endpoints[E]['req']>(
@@ -1613,7 +1613,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No* / **Permission**: *read:account*
*/
request<E extends 'clips/show', P extends Endpoints[E]['req']>(
@@ -1624,7 +1624,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'clips/update', P extends Endpoints[E]['req']>(
@@ -1635,7 +1635,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:clip-favorite*
*/
request<E extends 'clips/favorite', P extends Endpoints[E]['req']>(
@@ -1646,7 +1646,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:clip-favorite*
*/
request<E extends 'clips/unfavorite', P extends Endpoints[E]['req']>(
@@ -1657,7 +1657,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:clip-favorite*
*/
request<E extends 'clips/my-favorites', P extends Endpoints[E]['req']>(
@@ -1668,7 +1668,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive', P extends Endpoints[E]['req']>(
@@ -1679,7 +1679,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/files', P extends Endpoints[E]['req']>(
@@ -1690,7 +1690,7 @@ declare module '../api.js' {
/**
* Find the notes to which the given file is attached.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/files/attached-notes', P extends Endpoints[E]['req']>(
@@ -1701,7 +1701,7 @@ declare module '../api.js' {
/**
* Check if a given file exists.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/files/check-existence', P extends Endpoints[E]['req']>(
@@ -1712,7 +1712,7 @@ declare module '../api.js' {
/**
* Upload a new drive file.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:drive*
*/
request<E extends 'drive/files/create', P extends Endpoints[E]['req']>(
@@ -1723,7 +1723,7 @@ declare module '../api.js' {
/**
* Delete an existing drive file.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:drive*
*/
request<E extends 'drive/files/delete', P extends Endpoints[E]['req']>(
@@ -1734,7 +1734,7 @@ declare module '../api.js' {
/**
* Search for a drive file by a hash of the contents.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/files/find-by-hash', P extends Endpoints[E]['req']>(
@@ -1745,7 +1745,7 @@ declare module '../api.js' {
/**
* Search for a drive file by the given parameters.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/files/find', P extends Endpoints[E]['req']>(
@@ -1756,7 +1756,7 @@ declare module '../api.js' {
/**
* Show the properties of a drive file.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/files/show', P extends Endpoints[E]['req']>(
@@ -1767,7 +1767,7 @@ declare module '../api.js' {
/**
* Update the properties of a drive file.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:drive*
*/
request<E extends 'drive/files/update', P extends Endpoints[E]['req']>(
@@ -1778,7 +1778,7 @@ declare module '../api.js' {
/**
* Request the server to download a new drive file from the specified URL.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:drive*
*/
request<E extends 'drive/files/upload-from-url', P extends Endpoints[E]['req']>(
@@ -1789,7 +1789,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/folders', P extends Endpoints[E]['req']>(
@@ -1800,7 +1800,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:drive*
*/
request<E extends 'drive/folders/create', P extends Endpoints[E]['req']>(
@@ -1811,7 +1811,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:drive*
*/
request<E extends 'drive/folders/delete', P extends Endpoints[E]['req']>(
@@ -1822,7 +1822,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/folders/find', P extends Endpoints[E]['req']>(
@@ -1833,7 +1833,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/folders/show', P extends Endpoints[E]['req']>(
@@ -1844,7 +1844,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:drive*
*/
request<E extends 'drive/folders/update', P extends Endpoints[E]['req']>(
@@ -1855,7 +1855,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:drive*
*/
request<E extends 'drive/stream', P extends Endpoints[E]['req']>(
@@ -1866,7 +1866,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'email-address/available', P extends Endpoints[E]['req']>(
@@ -1877,7 +1877,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'endpoint', P extends Endpoints[E]['req']>(
@@ -1888,7 +1888,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'endpoints', P extends Endpoints[E]['req']>(
@@ -1899,7 +1899,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -1911,7 +1911,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'federation/followers', P extends Endpoints[E]['req']>(
@@ -1922,7 +1922,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'federation/following', P extends Endpoints[E]['req']>(
@@ -1933,7 +1933,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'federation/instances', P extends Endpoints[E]['req']>(
@@ -1944,7 +1944,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'federation/show-instance', P extends Endpoints[E]['req']>(
@@ -1955,7 +1955,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'federation/update-remote-user', P extends Endpoints[E]['req']>(
@@ -1966,7 +1966,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'federation/users', P extends Endpoints[E]['req']>(
@@ -1977,7 +1977,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'federation/stats', P extends Endpoints[E]['req']>(
@@ -1988,7 +1988,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:following*
*/
request<E extends 'following/create', P extends Endpoints[E]['req']>(
@@ -1999,7 +1999,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:following*
*/
request<E extends 'following/delete', P extends Endpoints[E]['req']>(
@@ -2010,7 +2010,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:following*
*/
request<E extends 'following/update', P extends Endpoints[E]['req']>(
@@ -2021,7 +2021,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:following*
*/
request<E extends 'following/update-all', P extends Endpoints[E]['req']>(
@@ -2032,7 +2032,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:following*
*/
request<E extends 'following/invalidate', P extends Endpoints[E]['req']>(
@@ -2043,7 +2043,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:following*
*/
request<E extends 'following/requests/accept', P extends Endpoints[E]['req']>(
@@ -2054,7 +2054,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:following*
*/
request<E extends 'following/requests/cancel', P extends Endpoints[E]['req']>(
@@ -2065,7 +2065,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:following*
*/
request<E extends 'following/requests/list', P extends Endpoints[E]['req']>(
@@ -2076,7 +2076,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:following*
*/
request<E extends 'following/requests/sent', P extends Endpoints[E]['req']>(
@@ -2087,7 +2087,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:following*
*/
request<E extends 'following/requests/reject', P extends Endpoints[E]['req']>(
@@ -2098,7 +2098,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'gallery/featured', P extends Endpoints[E]['req']>(
@@ -2109,7 +2109,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'gallery/popular', P extends Endpoints[E]['req']>(
@@ -2120,7 +2120,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'gallery/posts', P extends Endpoints[E]['req']>(
@@ -2131,7 +2131,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:gallery*
*/
request<E extends 'gallery/posts/create', P extends Endpoints[E]['req']>(
@@ -2142,7 +2142,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:gallery*
*/
request<E extends 'gallery/posts/delete', P extends Endpoints[E]['req']>(
@@ -2153,7 +2153,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:gallery-likes*
*/
request<E extends 'gallery/posts/like', P extends Endpoints[E]['req']>(
@@ -2164,7 +2164,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'gallery/posts/show', P extends Endpoints[E]['req']>(
@@ -2175,7 +2175,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:gallery-likes*
*/
request<E extends 'gallery/posts/unlike', P extends Endpoints[E]['req']>(
@@ -2186,7 +2186,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:gallery*
*/
request<E extends 'gallery/posts/update', P extends Endpoints[E]['req']>(
@@ -2197,7 +2197,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'get-online-users-count', P extends Endpoints[E]['req']>(
@@ -2208,7 +2208,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'get-avatar-decorations', P extends Endpoints[E]['req']>(
@@ -2219,7 +2219,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'hashtags/list', P extends Endpoints[E]['req']>(
@@ -2230,7 +2230,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'hashtags/search', P extends Endpoints[E]['req']>(
@@ -2241,7 +2241,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'hashtags/show', P extends Endpoints[E]['req']>(
@@ -2252,7 +2252,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'hashtags/trend', P extends Endpoints[E]['req']>(
@@ -2263,7 +2263,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'hashtags/users', P extends Endpoints[E]['req']>(
@@ -2274,7 +2274,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i', P extends Endpoints[E]['req']>(
@@ -2285,7 +2285,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2297,7 +2297,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2309,7 +2309,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2321,7 +2321,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2333,7 +2333,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2345,7 +2345,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2357,7 +2357,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2369,7 +2369,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2381,7 +2381,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2393,7 +2393,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2405,7 +2405,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/claim-achievement', P extends Endpoints[E]['req']>(
@@ -2416,7 +2416,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2428,7 +2428,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2440,7 +2440,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2452,7 +2452,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2464,7 +2464,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2476,7 +2476,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2488,7 +2488,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2500,7 +2500,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2512,7 +2512,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2524,7 +2524,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2536,7 +2536,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2548,7 +2548,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:favorites*
*/
request<E extends 'i/favorites', P extends Endpoints[E]['req']>(
@@ -2559,7 +2559,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:gallery-likes*
*/
request<E extends 'i/gallery/likes', P extends Endpoints[E]['req']>(
@@ -2570,7 +2570,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:gallery*
*/
request<E extends 'i/gallery/posts', P extends Endpoints[E]['req']>(
@@ -2581,7 +2581,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2593,7 +2593,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2605,7 +2605,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2617,7 +2617,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2629,7 +2629,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2641,7 +2641,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2653,7 +2653,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:notifications*
*/
request<E extends 'i/notifications', P extends Endpoints[E]['req']>(
@@ -2664,7 +2664,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:notifications*
*/
request<E extends 'i/notifications-grouped', P extends Endpoints[E]['req']>(
@@ -2675,7 +2675,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:page-likes*
*/
request<E extends 'i/page-likes', P extends Endpoints[E]['req']>(
@@ -2686,7 +2686,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:pages*
*/
request<E extends 'i/pages', P extends Endpoints[E]['req']>(
@@ -2697,7 +2697,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/pin', P extends Endpoints[E]['req']>(
@@ -2708,7 +2708,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/read-all-unread-notes', P extends Endpoints[E]['req']>(
@@ -2719,7 +2719,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/read-announcement', P extends Endpoints[E]['req']>(
@@ -2730,7 +2730,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2742,7 +2742,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/registry/get-all', P extends Endpoints[E]['req']>(
@@ -2753,7 +2753,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/registry/get-unsecure', P extends Endpoints[E]['req']>(
@@ -2764,7 +2764,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/registry/get-detail', P extends Endpoints[E]['req']>(
@@ -2775,7 +2775,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/registry/get', P extends Endpoints[E]['req']>(
@@ -2786,7 +2786,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/registry/keys-with-type', P extends Endpoints[E]['req']>(
@@ -2797,7 +2797,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/registry/keys', P extends Endpoints[E]['req']>(
@@ -2808,7 +2808,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/registry/remove', P extends Endpoints[E]['req']>(
@@ -2819,7 +2819,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2831,7 +2831,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/registry/set', P extends Endpoints[E]['req']>(
@@ -2842,7 +2842,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2854,7 +2854,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2866,7 +2866,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/unpin', P extends Endpoints[E]['req']>(
@@ -2877,7 +2877,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2889,7 +2889,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/update', P extends Endpoints[E]['req']>(
@@ -2900,7 +2900,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -2912,7 +2912,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/webhooks/create', P extends Endpoints[E]['req']>(
@@ -2923,7 +2923,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/webhooks/list', P extends Endpoints[E]['req']>(
@@ -2934,7 +2934,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/webhooks/show', P extends Endpoints[E]['req']>(
@@ -2945,7 +2945,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/webhooks/update', P extends Endpoints[E]['req']>(
@@ -2956,7 +2956,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'i/webhooks/delete', P extends Endpoints[E]['req']>(
@@ -2967,7 +2967,7 @@ declare module '../api.js' {
/**
* 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* / **Permission**: *read:account*
*/
@@ -2979,7 +2979,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:invite-codes*
*/
request<E extends 'invite/create', P extends Endpoints[E]['req']>(
@@ -2990,7 +2990,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:invite-codes*
*/
request<E extends 'invite/delete', P extends Endpoints[E]['req']>(
@@ -3001,7 +3001,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:invite-codes*
*/
request<E extends 'invite/list', P extends Endpoints[E]['req']>(
@@ -3012,7 +3012,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:invite-codes*
*/
request<E extends 'invite/limit', P extends Endpoints[E]['req']>(
@@ -3023,7 +3023,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'meta', P extends Endpoints[E]['req']>(
@@ -3034,7 +3034,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'emojis', P extends Endpoints[E]['req']>(
@@ -3045,7 +3045,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'emoji', P extends Endpoints[E]['req']>(
@@ -3056,7 +3056,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -3068,7 +3068,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:mutes*
*/
request<E extends 'mute/create', P extends Endpoints[E]['req']>(
@@ -3079,7 +3079,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:mutes*
*/
request<E extends 'mute/delete', P extends Endpoints[E]['req']>(
@@ -3090,7 +3090,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:mutes*
*/
request<E extends 'mute/list', P extends Endpoints[E]['req']>(
@@ -3101,7 +3101,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:mutes*
*/
request<E extends 'renote-mute/create', P extends Endpoints[E]['req']>(
@@ -3112,7 +3112,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:mutes*
*/
request<E extends 'renote-mute/delete', P extends Endpoints[E]['req']>(
@@ -3123,7 +3123,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:mutes*
*/
request<E extends 'renote-mute/list', P extends Endpoints[E]['req']>(
@@ -3134,7 +3134,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'my/apps', P extends Endpoints[E]['req']>(
@@ -3145,7 +3145,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes', P extends Endpoints[E]['req']>(
@@ -3156,7 +3156,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/children', P extends Endpoints[E]['req']>(
@@ -3167,7 +3167,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/clips', P extends Endpoints[E]['req']>(
@@ -3178,7 +3178,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/conversation', P extends Endpoints[E]['req']>(
@@ -3189,7 +3189,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notes*
*/
request<E extends 'notes/create', P extends Endpoints[E]['req']>(
@@ -3200,7 +3200,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notes*
*/
request<E extends 'notes/delete', P extends Endpoints[E]['req']>(
@@ -3211,7 +3211,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:favorites*
*/
request<E extends 'notes/favorites/create', P extends Endpoints[E]['req']>(
@@ -3222,7 +3222,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:favorites*
*/
request<E extends 'notes/favorites/delete', P extends Endpoints[E]['req']>(
@@ -3233,7 +3233,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/featured', P extends Endpoints[E]['req']>(
@@ -3244,7 +3244,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/following', P extends Endpoints[E]['req']>(
@@ -3255,7 +3255,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/global-timeline', P extends Endpoints[E]['req']>(
@@ -3266,7 +3266,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/bubble-timeline', P extends Endpoints[E]['req']>(
@@ -3277,7 +3277,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/hybrid-timeline', P extends Endpoints[E]['req']>(
@@ -3288,7 +3288,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/local-timeline', P extends Endpoints[E]['req']>(
@@ -3299,7 +3299,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/mentions', P extends Endpoints[E]['req']>(
@@ -3310,7 +3310,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/polls/recommendation', P extends Endpoints[E]['req']>(
@@ -3321,7 +3321,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:votes*
*/
request<E extends 'notes/polls/vote', P extends Endpoints[E]['req']>(
@@ -3332,7 +3332,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:federation*
*/
request<E extends 'notes/polls/refresh', P extends Endpoints[E]['req']>(
@@ -3343,7 +3343,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/reactions', P extends Endpoints[E]['req']>(
@@ -3354,7 +3354,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:reactions*
*/
request<E extends 'notes/reactions/create', P extends Endpoints[E]['req']>(
@@ -3365,7 +3365,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:reactions*
*/
request<E extends 'notes/reactions/delete', P extends Endpoints[E]['req']>(
@@ -3376,7 +3376,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:reactions*
*/
request<E extends 'notes/like', P extends Endpoints[E]['req']>(
@@ -3387,7 +3387,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/renotes', P extends Endpoints[E]['req']>(
@@ -3398,7 +3398,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/replies', P extends Endpoints[E]['req']>(
@@ -3409,7 +3409,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
*/
request<E extends 'notes/schedule/create', P extends Endpoints[E]['req']>(
@@ -3420,7 +3420,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notes-schedule*
*/
request<E extends 'notes/schedule/delete', P extends Endpoints[E]['req']>(
@@ -3431,7 +3431,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:notes-schedule*
*/
request<E extends 'notes/schedule/list', P extends Endpoints[E]['req']>(
@@ -3442,7 +3442,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/search-by-tag', P extends Endpoints[E]['req']>(
@@ -3453,7 +3453,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/search', P extends Endpoints[E]['req']>(
@@ -3464,7 +3464,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/show', P extends Endpoints[E]['req']>(
@@ -3475,7 +3475,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/state', P extends Endpoints[E]['req']>(
@@ -3486,7 +3486,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'notes/thread-muting/create', P extends Endpoints[E]['req']>(
@@ -3497,7 +3497,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'notes/thread-muting/delete', P extends Endpoints[E]['req']>(
@@ -3508,7 +3508,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/timeline', P extends Endpoints[E]['req']>(
@@ -3519,7 +3519,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/translate', P extends Endpoints[E]['req']>(
@@ -3530,7 +3530,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notes*
*/
request<E extends 'notes/unrenote', P extends Endpoints[E]['req']>(
@@ -3541,7 +3541,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'notes/user-list-timeline', P extends Endpoints[E]['req']>(
@@ -3552,7 +3552,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notes*
*/
request<E extends 'notes/edit', P extends Endpoints[E]['req']>(
@@ -3563,7 +3563,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'notes/versions', P extends Endpoints[E]['req']>(
@@ -3574,7 +3574,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notifications*
*/
request<E extends 'notifications/create', P extends Endpoints[E]['req']>(
@@ -3585,7 +3585,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notifications*
*/
request<E extends 'notifications/flush', P extends Endpoints[E]['req']>(
@@ -3596,7 +3596,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notifications*
*/
request<E extends 'notifications/mark-all-as-read', P extends Endpoints[E]['req']>(
@@ -3607,7 +3607,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:notifications*
*/
request<E extends 'notifications/test-notification', P extends Endpoints[E]['req']>(
@@ -3618,7 +3618,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -3630,7 +3630,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:pages*
*/
request<E extends 'pages/create', P extends Endpoints[E]['req']>(
@@ -3641,7 +3641,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:pages*
*/
request<E extends 'pages/delete', P extends Endpoints[E]['req']>(
@@ -3652,7 +3652,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'pages/featured', P extends Endpoints[E]['req']>(
@@ -3663,7 +3663,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:page-likes*
*/
request<E extends 'pages/like', P extends Endpoints[E]['req']>(
@@ -3674,7 +3674,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'pages/show', P extends Endpoints[E]['req']>(
@@ -3685,7 +3685,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:page-likes*
*/
request<E extends 'pages/unlike', P extends Endpoints[E]['req']>(
@@ -3696,7 +3696,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:pages*
*/
request<E extends 'pages/update', P extends Endpoints[E]['req']>(
@@ -3707,7 +3707,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:flash*
*/
request<E extends 'flash/create', P extends Endpoints[E]['req']>(
@@ -3718,7 +3718,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:flash*
*/
request<E extends 'flash/delete', P extends Endpoints[E]['req']>(
@@ -3729,7 +3729,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'flash/featured', P extends Endpoints[E]['req']>(
@@ -3740,7 +3740,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:flash-likes*
*/
request<E extends 'flash/like', P extends Endpoints[E]['req']>(
@@ -3751,7 +3751,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'flash/show', P extends Endpoints[E]['req']>(
@@ -3762,7 +3762,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:flash-likes*
*/
request<E extends 'flash/unlike', P extends Endpoints[E]['req']>(
@@ -3773,7 +3773,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:flash*
*/
request<E extends 'flash/update', P extends Endpoints[E]['req']>(
@@ -3784,7 +3784,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:flash*
*/
request<E extends 'flash/my', P extends Endpoints[E]['req']>(
@@ -3795,7 +3795,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:flash-likes*
*/
request<E extends 'flash/my-likes', P extends Endpoints[E]['req']>(
@@ -3806,7 +3806,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'ping', P extends Endpoints[E]['req']>(
@@ -3817,7 +3817,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'pinned-users', P extends Endpoints[E]['req']>(
@@ -3828,7 +3828,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'promo/read', P extends Endpoints[E]['req']>(
@@ -3839,7 +3839,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'roles/list', P extends Endpoints[E]['req']>(
@@ -3850,7 +3850,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'roles/show', P extends Endpoints[E]['req']>(
@@ -3861,7 +3861,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'roles/users', P extends Endpoints[E]['req']>(
@@ -3872,7 +3872,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'roles/notes', P extends Endpoints[E]['req']>(
@@ -3883,7 +3883,7 @@ declare module '../api.js' {
/**
* Request a users password to be reset.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'request-reset-password', P extends Endpoints[E]['req']>(
@@ -3894,7 +3894,7 @@ declare module '../api.js' {
/**
* Only available when running with <code>NODE_ENV=testing</code>. Reset the database and flush Redis.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'reset-db', P extends Endpoints[E]['req']>(
@@ -3905,7 +3905,7 @@ declare module '../api.js' {
/**
* Complete the password reset that was previously requested.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'reset-password', P extends Endpoints[E]['req']>(
@@ -3916,7 +3916,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'server-info', P extends Endpoints[E]['req']>(
@@ -3927,7 +3927,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'stats', P extends Endpoints[E]['req']>(
@@ -3938,7 +3938,7 @@ declare module '../api.js' {
/**
* Check push notification registration exists.
- *
+ *
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
* **Credential required**: *Yes*
*/
@@ -3950,7 +3950,7 @@ declare module '../api.js' {
/**
* Update push notification registration.
- *
+ *
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
* **Credential required**: *Yes*
*/
@@ -3962,7 +3962,7 @@ declare module '../api.js' {
/**
* Register to receive push notifications.
- *
+ *
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
* **Credential required**: *Yes*
*/
@@ -3974,7 +3974,7 @@ declare module '../api.js' {
/**
* Unregister from receiving push notifications.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'sw/unregister', P extends Endpoints[E]['req']>(
@@ -3985,7 +3985,7 @@ declare module '../api.js' {
/**
* Endpoint for testing input validation.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'test', P extends Endpoints[E]['req']>(
@@ -3996,7 +3996,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'username/available', P extends Endpoints[E]['req']>(
@@ -4007,7 +4007,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users', P extends Endpoints[E]['req']>(
@@ -4018,7 +4018,7 @@ declare module '../api.js' {
/**
* Show all clips this user owns.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/clips', P extends Endpoints[E]['req']>(
@@ -4029,7 +4029,7 @@ declare module '../api.js' {
/**
* Show everyone that follows this user.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/followers', P extends Endpoints[E]['req']>(
@@ -4040,7 +4040,7 @@ declare module '../api.js' {
/**
* Show everyone that this user is following.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/following', P extends Endpoints[E]['req']>(
@@ -4051,7 +4051,7 @@ declare module '../api.js' {
/**
* Show all gallery posts by the given user.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/gallery/posts', P extends Endpoints[E]['req']>(
@@ -4062,7 +4062,7 @@ declare module '../api.js' {
/**
* Get a list of other users that the specified user frequently replies to.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/get-frequently-replied-users', P extends Endpoints[E]['req']>(
@@ -4073,7 +4073,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/featured-notes', P extends Endpoints[E]['req']>(
@@ -4084,7 +4084,7 @@ declare module '../api.js' {
/**
* Create a new list of users.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/create', P extends Endpoints[E]['req']>(
@@ -4095,7 +4095,7 @@ declare module '../api.js' {
/**
* Delete an existing list of users.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/delete', P extends Endpoints[E]['req']>(
@@ -4106,7 +4106,7 @@ declare module '../api.js' {
/**
* Show all lists that the authenticated user has created.
- *
+ *
* **Credential required**: *No* / **Permission**: *read:account*
*/
request<E extends 'users/lists/list', P extends Endpoints[E]['req']>(
@@ -4117,7 +4117,7 @@ declare module '../api.js' {
/**
* Remove a user from a list.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/pull', P extends Endpoints[E]['req']>(
@@ -4128,7 +4128,7 @@ declare module '../api.js' {
/**
* Add a user to an existing list.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/push', P extends Endpoints[E]['req']>(
@@ -4139,7 +4139,7 @@ declare module '../api.js' {
/**
* Show the properties of a list.
- *
+ *
* **Credential required**: *No* / **Permission**: *read:account*
*/
request<E extends 'users/lists/show', P extends Endpoints[E]['req']>(
@@ -4150,7 +4150,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/favorite', P extends Endpoints[E]['req']>(
@@ -4161,7 +4161,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/unfavorite', P extends Endpoints[E]['req']>(
@@ -4172,7 +4172,7 @@ declare module '../api.js' {
/**
* Update the properties of a list.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/update', P extends Endpoints[E]['req']>(
@@ -4183,7 +4183,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/create-from-public', P extends Endpoints[E]['req']>(
@@ -4194,7 +4194,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/lists/update-membership', P extends Endpoints[E]['req']>(
@@ -4205,7 +4205,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No* / **Permission**: *read:account*
*/
request<E extends 'users/lists/get-memberships', P extends Endpoints[E]['req']>(
@@ -4216,7 +4216,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/notes', P extends Endpoints[E]['req']>(
@@ -4227,7 +4227,7 @@ declare module '../api.js' {
/**
* Show all pages this user created.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/pages', P extends Endpoints[E]['req']>(
@@ -4238,7 +4238,7 @@ declare module '../api.js' {
/**
* Show all flashs this user created.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/flashs', P extends Endpoints[E]['req']>(
@@ -4249,7 +4249,7 @@ declare module '../api.js' {
/**
* Show all reactions this user made.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/reactions', P extends Endpoints[E]['req']>(
@@ -4260,7 +4260,7 @@ declare module '../api.js' {
/**
* Show users that the authenticated user might be interested to follow.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'users/recommendation', P extends Endpoints[E]['req']>(
@@ -4271,7 +4271,7 @@ declare module '../api.js' {
/**
* Show the different kinds of relations between the authenticated user and the specified user(s).
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'users/relation', P extends Endpoints[E]['req']>(
@@ -4282,7 +4282,7 @@ declare module '../api.js' {
/**
* File a report.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:report-abuse*
*/
request<E extends 'users/report-abuse', P extends Endpoints[E]['req']>(
@@ -4293,7 +4293,7 @@ declare module '../api.js' {
/**
* Search for a user by username and/or host.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/search-by-username-and-host', P extends Endpoints[E]['req']>(
@@ -4304,7 +4304,7 @@ declare module '../api.js' {
/**
* Search for users.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/search', P extends Endpoints[E]['req']>(
@@ -4315,7 +4315,7 @@ declare module '../api.js' {
/**
* Show the properties of a user.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/show', P extends Endpoints[E]['req']>(
@@ -4326,7 +4326,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'users/achievements', P extends Endpoints[E]['req']>(
@@ -4337,7 +4337,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'users/update-memo', P extends Endpoints[E]['req']>(
@@ -4348,7 +4348,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'fetch-rss', P extends Endpoints[E]['req']>(
@@ -4359,7 +4359,7 @@ declare module '../api.js' {
/**
* 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*
*/
@@ -4371,7 +4371,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'retention', P extends Endpoints[E]['req']>(
@@ -4382,7 +4382,7 @@ declare module '../api.js' {
/**
* Get Sharkey Sponsors or Instance Sponsors
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'sponsors', P extends Endpoints[E]['req']>(
@@ -4393,7 +4393,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'bubble-game/register', P extends Endpoints[E]['req']>(
@@ -4404,7 +4404,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'bubble-game/ranking', P extends Endpoints[E]['req']>(
@@ -4415,7 +4415,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'reversi/cancel-match', P extends Endpoints[E]['req']>(
@@ -4426,7 +4426,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'reversi/games', P extends Endpoints[E]['req']>(
@@ -4437,7 +4437,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'reversi/match', P extends Endpoints[E]['req']>(
@@ -4448,7 +4448,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'reversi/invitations', P extends Endpoints[E]['req']>(
@@ -4459,7 +4459,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'reversi/show-game', P extends Endpoints[E]['req']>(
@@ -4470,7 +4470,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *Yes* / **Permission**: *write:account*
*/
request<E extends 'reversi/surrender', P extends Endpoints[E]['req']>(
@@ -4481,7 +4481,7 @@ declare module '../api.js' {
/**
* No description provided.
- *
+ *
* **Credential required**: *No*
*/
request<E extends 'reversi/verify', P extends Endpoints[E]['req']>(
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index 9166bb701f..61c9ed1260 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -4,18 +4,17 @@ import { operations } from './types.js';
export type EmptyRequest = Record<string, unknown> | undefined;
export type EmptyResponse = Record<string, unknown> | undefined;
-export type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json'];
-export type AdminAbuseUserReportsRequest = operations['admin___abuse-user-reports']['requestBody']['content']['application/json'];
-export type AdminAbuseUserReportsResponse = operations['admin___abuse-user-reports']['responses']['200']['content']['application/json'];
+export type AdminAbuseReportNotificationRecipientCreateRequest = operations['admin___abuse-report___notification-recipient___create']['requestBody']['content']['application/json'];
+export type AdminAbuseReportNotificationRecipientCreateResponse = operations['admin___abuse-report___notification-recipient___create']['responses']['200']['content']['application/json'];
+export type AdminAbuseReportNotificationRecipientDeleteRequest = operations['admin___abuse-report___notification-recipient___delete']['requestBody']['content']['application/json'];
export type AdminAbuseReportNotificationRecipientListRequest = operations['admin___abuse-report___notification-recipient___list']['requestBody']['content']['application/json'];
export type AdminAbuseReportNotificationRecipientListResponse = operations['admin___abuse-report___notification-recipient___list']['responses']['200']['content']['application/json'];
export type AdminAbuseReportNotificationRecipientShowRequest = operations['admin___abuse-report___notification-recipient___show']['requestBody']['content']['application/json'];
export type AdminAbuseReportNotificationRecipientShowResponse = operations['admin___abuse-report___notification-recipient___show']['responses']['200']['content']['application/json'];
-export type AdminAbuseReportNotificationRecipientCreateRequest = operations['admin___abuse-report___notification-recipient___create']['requestBody']['content']['application/json'];
-export type AdminAbuseReportNotificationRecipientCreateResponse = operations['admin___abuse-report___notification-recipient___create']['responses']['200']['content']['application/json'];
export type AdminAbuseReportNotificationRecipientUpdateRequest = operations['admin___abuse-report___notification-recipient___update']['requestBody']['content']['application/json'];
export type AdminAbuseReportNotificationRecipientUpdateResponse = operations['admin___abuse-report___notification-recipient___update']['responses']['200']['content']['application/json'];
-export type AdminAbuseReportNotificationRecipientDeleteRequest = operations['admin___abuse-report___notification-recipient___delete']['requestBody']['content']['application/json'];
+export type AdminAbuseUserReportsRequest = operations['admin___abuse-user-reports']['requestBody']['content']['application/json'];
+export type AdminAbuseUserReportsResponse = operations['admin___abuse-user-reports']['responses']['200']['content']['application/json'];
export type AdminAccountsCreateRequest = operations['admin___accounts___create']['requestBody']['content']['application/json'];
export type AdminAccountsCreateResponse = operations['admin___accounts___create']['responses']['200']['content']['application/json'];
export type AdminAccountsDeleteRequest = operations['admin___accounts___delete']['requestBody']['content']['application/json'];
@@ -33,31 +32,34 @@ export type AdminAnnouncementsDeleteRequest = operations['admin___announcements_
export type AdminAnnouncementsListRequest = operations['admin___announcements___list']['requestBody']['content']['application/json'];
export type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json'];
export type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
+export type AdminApproveUserRequest = operations['admin___approve-user']['requestBody']['content']['application/json'];
export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
export type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json'];
export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json'];
export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json'];
export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json'];
export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json'];
+export type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['responses']['200']['content']['application/json'];
+export type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json'];
+export type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json'];
+export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json'];
export type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json'];
-export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json'];
-export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json'];
export type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json'];
export type AdminDriveFilesResponse = operations['admin___drive___files']['responses']['200']['content']['application/json'];
export type AdminDriveShowFileRequest = operations['admin___drive___show-file']['requestBody']['content']['application/json'];
export type AdminDriveShowFileResponse = operations['admin___drive___show-file']['responses']['200']['content']['application/json'];
-export type AdminEmojiAddAliasesBulkRequest = operations['admin___emoji___add-aliases-bulk']['requestBody']['content']['application/json'];
export type AdminEmojiAddRequest = operations['admin___emoji___add']['requestBody']['content']['application/json'];
export type AdminEmojiAddResponse = operations['admin___emoji___add']['responses']['200']['content']['application/json'];
+export type AdminEmojiAddAliasesBulkRequest = operations['admin___emoji___add-aliases-bulk']['requestBody']['content']['application/json'];
export type AdminEmojiCopyRequest = operations['admin___emoji___copy']['requestBody']['content']['application/json'];
export type AdminEmojiCopyResponse = operations['admin___emoji___copy']['responses']['200']['content']['application/json'];
-export type AdminEmojiDeleteBulkRequest = operations['admin___emoji___delete-bulk']['requestBody']['content']['application/json'];
export type AdminEmojiDeleteRequest = operations['admin___emoji___delete']['requestBody']['content']['application/json'];
+export type AdminEmojiDeleteBulkRequest = operations['admin___emoji___delete-bulk']['requestBody']['content']['application/json'];
export type AdminEmojiImportZipRequest = operations['admin___emoji___import-zip']['requestBody']['content']['application/json'];
-export type AdminEmojiListRemoteRequest = operations['admin___emoji___list-remote']['requestBody']['content']['application/json'];
-export type AdminEmojiListRemoteResponse = operations['admin___emoji___list-remote']['responses']['200']['content']['application/json'];
export type AdminEmojiListRequest = operations['admin___emoji___list']['requestBody']['content']['application/json'];
export type AdminEmojiListResponse = operations['admin___emoji___list']['responses']['200']['content']['application/json'];
+export type AdminEmojiListRemoteRequest = operations['admin___emoji___list-remote']['requestBody']['content']['application/json'];
+export type AdminEmojiListRemoteResponse = operations['admin___emoji___list-remote']['responses']['200']['content']['application/json'];
export type AdminEmojiRemoveAliasesBulkRequest = operations['admin___emoji___remove-aliases-bulk']['requestBody']['content']['application/json'];
export type AdminEmojiSetAliasesBulkRequest = operations['admin___emoji___set-aliases-bulk']['requestBody']['content']['application/json'];
export type AdminEmojiSetCategoryBulkRequest = operations['admin___emoji___set-category-bulk']['requestBody']['content']['application/json'];
@@ -67,6 +69,7 @@ export type AdminFederationDeleteAllFilesRequest = operations['admin___federatio
export type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin___federation___refresh-remote-instance-metadata']['requestBody']['content']['application/json'];
export type AdminFederationRemoveAllFollowingRequest = operations['admin___federation___remove-all-following']['requestBody']['content']['application/json'];
export type AdminFederationUpdateInstanceRequest = operations['admin___federation___update-instance']['requestBody']['content']['application/json'];
+export type AdminForwardAbuseUserReportRequest = operations['admin___forward-abuse-user-report']['requestBody']['content']['application/json'];
export type AdminGetIndexStatsResponse = operations['admin___get-index-stats']['responses']['200']['content']['application/json'];
export type AdminGetTableStatsResponse = operations['admin___get-table-stats']['responses']['200']['content']['application/json'];
export type AdminGetUserIpsRequest = operations['admin___get-user-ips']['requestBody']['content']['application/json'];
@@ -75,6 +78,8 @@ export type AdminInviteCreateRequest = operations['admin___invite___create']['re
export type AdminInviteCreateResponse = operations['admin___invite___create']['responses']['200']['content']['application/json'];
export type AdminInviteListRequest = operations['admin___invite___list']['requestBody']['content']['application/json'];
export type AdminInviteListResponse = operations['admin___invite___list']['responses']['200']['content']['application/json'];
+export type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json'];
+export type AdminNsfwUserRequest = operations['admin___nsfw-user']['requestBody']['content']['application/json'];
export type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json'];
export type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-delayed']['responses']['200']['content']['application/json'];
export type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json'];
@@ -87,39 +92,28 @@ export type AdminRelaysRemoveRequest = operations['admin___relays___remove']['re
export type AdminResetPasswordRequest = operations['admin___reset-password']['requestBody']['content']['application/json'];
export type AdminResetPasswordResponse = operations['admin___reset-password']['responses']['200']['content']['application/json'];
export type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user-report']['requestBody']['content']['application/json'];
-export type AdminForwardAbuseUserReportRequest = operations['admin___forward-abuse-user-report']['requestBody']['content']['application/json'];
-export type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json'];
-export type AdminSendEmailRequest = operations['admin___send-email']['requestBody']['content']['application/json'];
-export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json'];
-export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json'];
-export type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json'];
-export type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json'];
-export type AdminShowUserResponse = operations['admin___show-user']['responses']['200']['content']['application/json'];
-export type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['content']['application/json'];
-export type AdminShowUsersResponse = operations['admin___show-users']['responses']['200']['content']['application/json'];
-export type AdminNsfwUserRequest = operations['admin___nsfw-user']['requestBody']['content']['application/json'];
-export type AdminUnnsfwUserRequest = operations['admin___unnsfw-user']['requestBody']['content']['application/json'];
-export type AdminSilenceUserRequest = operations['admin___silence-user']['requestBody']['content']['application/json'];
-export type AdminUnsilenceUserRequest = operations['admin___unsilence-user']['requestBody']['content']['application/json'];
-export type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json'];
-export type AdminApproveUserRequest = operations['admin___approve-user']['requestBody']['content']['application/json'];
-export type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json'];
-export type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
-export type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
-export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json'];
-export type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json'];
+export type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json'];
export type AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json'];
export type AdminRolesCreateResponse = operations['admin___roles___create']['responses']['200']['content']['application/json'];
export type AdminRolesDeleteRequest = operations['admin___roles___delete']['requestBody']['content']['application/json'];
export type AdminRolesListResponse = operations['admin___roles___list']['responses']['200']['content']['application/json'];
export type AdminRolesShowRequest = operations['admin___roles___show']['requestBody']['content']['application/json'];
export type AdminRolesShowResponse = operations['admin___roles___show']['responses']['200']['content']['application/json'];
-export type AdminRolesUpdateRequest = operations['admin___roles___update']['requestBody']['content']['application/json'];
-export type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json'];
export type AdminRolesUnassignRequest = operations['admin___roles___unassign']['requestBody']['content']['application/json'];
+export type AdminRolesUpdateRequest = operations['admin___roles___update']['requestBody']['content']['application/json'];
export type AdminRolesUpdateDefaultPoliciesRequest = operations['admin___roles___update-default-policies']['requestBody']['content']['application/json'];
export type AdminRolesUsersRequest = operations['admin___roles___users']['requestBody']['content']['application/json'];
export type AdminRolesUsersResponse = operations['admin___roles___users']['responses']['200']['content']['application/json'];
+export type AdminSendEmailRequest = operations['admin___send-email']['requestBody']['content']['application/json'];
+export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json'];
+export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json'];
+export type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json'];
+export type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json'];
+export type AdminShowUserResponse = operations['admin___show-user']['responses']['200']['content']['application/json'];
+export type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['content']['application/json'];
+export type AdminShowUsersResponse = operations['admin___show-users']['responses']['200']['content']['application/json'];
+export type AdminSilenceUserRequest = operations['admin___silence-user']['requestBody']['content']['application/json'];
+export type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json'];
export type AdminSystemWebhookCreateRequest = operations['admin___system-webhook___create']['requestBody']['content']['application/json'];
export type AdminSystemWebhookCreateResponse = operations['admin___system-webhook___create']['responses']['200']['content']['application/json'];
export type AdminSystemWebhookDeleteRequest = operations['admin___system-webhook___delete']['requestBody']['content']['application/json'];
@@ -127,9 +121,17 @@ export type AdminSystemWebhookListRequest = operations['admin___system-webhook__
export type AdminSystemWebhookListResponse = operations['admin___system-webhook___list']['responses']['200']['content']['application/json'];
export type AdminSystemWebhookShowRequest = operations['admin___system-webhook___show']['requestBody']['content']['application/json'];
export type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json'];
+export type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json'];
export type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json'];
export type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json'];
-export type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json'];
+export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json'];
+export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json'];
+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 AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json'];
+export type AdminUnnsfwUserRequest = operations['admin___unnsfw-user']['requestBody']['content']['application/json'];
+export type AdminUnsilenceUserRequest = operations['admin___unsilence-user']['requestBody']['content']['application/json'];
export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json'];
export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json'];
export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json'];
@@ -165,26 +167,29 @@ export type BlockingDeleteRequest = operations['blocking___delete']['requestBody
export type BlockingDeleteResponse = operations['blocking___delete']['responses']['200']['content']['application/json'];
export type BlockingListRequest = operations['blocking___list']['requestBody']['content']['application/json'];
export type BlockingListResponse = operations['blocking___list']['responses']['200']['content']['application/json'];
+export type BubbleGameRankingRequest = operations['bubble-game___ranking']['requestBody']['content']['application/json'];
+export type BubbleGameRankingResponse = operations['bubble-game___ranking']['responses']['200']['content']['application/json'];
+export type BubbleGameRegisterRequest = operations['bubble-game___register']['requestBody']['content']['application/json'];
export type ChannelsCreateRequest = operations['channels___create']['requestBody']['content']['application/json'];
export type ChannelsCreateResponse = operations['channels___create']['responses']['200']['content']['application/json'];
+export type ChannelsFavoriteRequest = operations['channels___favorite']['requestBody']['content']['application/json'];
export type ChannelsFeaturedResponse = operations['channels___featured']['responses']['200']['content']['application/json'];
export type ChannelsFollowRequest = operations['channels___follow']['requestBody']['content']['application/json'];
export type ChannelsFollowedRequest = operations['channels___followed']['requestBody']['content']['application/json'];
export type ChannelsFollowedResponse = operations['channels___followed']['responses']['200']['content']['application/json'];
+export type ChannelsMyFavoritesResponse = operations['channels___my-favorites']['responses']['200']['content']['application/json'];
export type ChannelsOwnedRequest = operations['channels___owned']['requestBody']['content']['application/json'];
export type ChannelsOwnedResponse = operations['channels___owned']['responses']['200']['content']['application/json'];
+export type ChannelsSearchRequest = operations['channels___search']['requestBody']['content']['application/json'];
+export type ChannelsSearchResponse = operations['channels___search']['responses']['200']['content']['application/json'];
export type ChannelsShowRequest = operations['channels___show']['requestBody']['content']['application/json'];
export type ChannelsShowResponse = operations['channels___show']['responses']['200']['content']['application/json'];
export type ChannelsTimelineRequest = operations['channels___timeline']['requestBody']['content']['application/json'];
export type ChannelsTimelineResponse = operations['channels___timeline']['responses']['200']['content']['application/json'];
+export type ChannelsUnfavoriteRequest = operations['channels___unfavorite']['requestBody']['content']['application/json'];
export type ChannelsUnfollowRequest = operations['channels___unfollow']['requestBody']['content']['application/json'];
export type ChannelsUpdateRequest = operations['channels___update']['requestBody']['content']['application/json'];
export type ChannelsUpdateResponse = operations['channels___update']['responses']['200']['content']['application/json'];
-export type ChannelsFavoriteRequest = operations['channels___favorite']['requestBody']['content']['application/json'];
-export type ChannelsUnfavoriteRequest = operations['channels___unfavorite']['requestBody']['content']['application/json'];
-export type ChannelsMyFavoritesResponse = operations['channels___my-favorites']['responses']['200']['content']['application/json'];
-export type ChannelsSearchRequest = operations['channels___search']['requestBody']['content']['application/json'];
-export type ChannelsSearchResponse = operations['channels___search']['responses']['200']['content']['application/json'];
export type ChartsActiveUsersRequest = operations['charts___active-users']['requestBody']['content']['application/json'];
export type ChartsActiveUsersResponse = operations['charts___active-users']['responses']['200']['content']['application/json'];
export type ChartsApRequestRequest = operations['charts___ap-request']['requestBody']['content']['application/json'];
@@ -210,20 +215,20 @@ export type ChartsUserReactionsResponse = operations['charts___user___reactions'
export type ChartsUsersRequest = operations['charts___users']['requestBody']['content']['application/json'];
export type ChartsUsersResponse = operations['charts___users']['responses']['200']['content']['application/json'];
export type ClipsAddNoteRequest = operations['clips___add-note']['requestBody']['content']['application/json'];
-export type ClipsRemoveNoteRequest = operations['clips___remove-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'];
export type ClipsDeleteRequest = operations['clips___delete']['requestBody']['content']['application/json'];
+export type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json'];
export type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json'];
+export type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json'];
export type ClipsNotesRequest = operations['clips___notes']['requestBody']['content']['application/json'];
export type ClipsNotesResponse = operations['clips___notes']['responses']['200']['content']['application/json'];
+export type ClipsRemoveNoteRequest = operations['clips___remove-note']['requestBody']['content']['application/json'];
export type ClipsShowRequest = operations['clips___show']['requestBody']['content']['application/json'];
export type ClipsShowResponse = operations['clips___show']['responses']['200']['content']['application/json'];
+export type ClipsUnfavoriteRequest = operations['clips___unfavorite']['requestBody']['content']['application/json'];
export type ClipsUpdateRequest = operations['clips___update']['requestBody']['content']['application/json'];
export type ClipsUpdateResponse = operations['clips___update']['responses']['200']['content']['application/json'];
-export type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json'];
-export type ClipsUnfavoriteRequest = operations['clips___unfavorite']['requestBody']['content']['application/json'];
-export type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json'];
export type DriveResponse = operations['drive']['responses']['200']['content']['application/json'];
export type DriveFilesRequest = operations['drive___files']['requestBody']['content']['application/json'];
export type DriveFilesResponse = operations['drive___files']['responses']['200']['content']['application/json'];
@@ -234,10 +239,10 @@ export type DriveFilesCheckExistenceResponse = operations['drive___files___check
export type DriveFilesCreateRequest = operations['drive___files___create']['requestBody']['content']['multipart/form-data'];
export type DriveFilesCreateResponse = operations['drive___files___create']['responses']['200']['content']['application/json'];
export type DriveFilesDeleteRequest = operations['drive___files___delete']['requestBody']['content']['application/json'];
-export type DriveFilesFindByHashRequest = operations['drive___files___find-by-hash']['requestBody']['content']['application/json'];
-export type DriveFilesFindByHashResponse = operations['drive___files___find-by-hash']['responses']['200']['content']['application/json'];
export type DriveFilesFindRequest = operations['drive___files___find']['requestBody']['content']['application/json'];
export type DriveFilesFindResponse = operations['drive___files___find']['responses']['200']['content']['application/json'];
+export type DriveFilesFindByHashRequest = operations['drive___files___find-by-hash']['requestBody']['content']['application/json'];
+export type DriveFilesFindByHashResponse = operations['drive___files___find-by-hash']['responses']['200']['content']['application/json'];
export type DriveFilesShowRequest = operations['drive___files___show']['requestBody']['content']['application/json'];
export type DriveFilesShowResponse = operations['drive___files___show']['responses']['200']['content']['application/json'];
export type DriveFilesUpdateRequest = operations['drive___files___update']['requestBody']['content']['application/json'];
@@ -258,6 +263,9 @@ export type DriveStreamRequest = operations['drive___stream']['requestBody']['co
export type DriveStreamResponse = operations['drive___stream']['responses']['200']['content']['application/json'];
export type EmailAddressAvailableRequest = operations['email-address___available']['requestBody']['content']['application/json'];
export type EmailAddressAvailableResponse = operations['email-address___available']['responses']['200']['content']['application/json'];
+export type EmojiRequest = operations['emoji']['requestBody']['content']['application/json'];
+export type EmojiResponse = operations['emoji']['responses']['200']['content']['application/json'];
+export type EmojisResponse = operations['emojis']['responses']['200']['content']['application/json'];
export type EndpointRequest = operations['endpoint']['requestBody']['content']['application/json'];
export type EndpointResponse = operations['endpoint']['responses']['200']['content']['application/json'];
export type EndpointsResponse = operations['endpoints']['responses']['200']['content']['application/json'];
@@ -269,18 +277,33 @@ export type FederationInstancesRequest = operations['federation___instances']['r
export type FederationInstancesResponse = operations['federation___instances']['responses']['200']['content']['application/json'];
export type FederationShowInstanceRequest = operations['federation___show-instance']['requestBody']['content']['application/json'];
export type FederationShowInstanceResponse = operations['federation___show-instance']['responses']['200']['content']['application/json'];
+export type FederationStatsRequest = operations['federation___stats']['requestBody']['content']['application/json'];
+export type FederationStatsResponse = operations['federation___stats']['responses']['200']['content']['application/json'];
export type FederationUpdateRemoteUserRequest = operations['federation___update-remote-user']['requestBody']['content']['application/json'];
export type FederationUsersRequest = operations['federation___users']['requestBody']['content']['application/json'];
export type FederationUsersResponse = operations['federation___users']['responses']['200']['content']['application/json'];
-export type FederationStatsRequest = operations['federation___stats']['requestBody']['content']['application/json'];
-export type FederationStatsResponse = operations['federation___stats']['responses']['200']['content']['application/json'];
+export type FetchExternalResourcesRequest = operations['fetch-external-resources']['requestBody']['content']['application/json'];
+export type FetchExternalResourcesResponse = operations['fetch-external-resources']['responses']['200']['content']['application/json'];
+export type FetchRssRequest = operations['fetch-rss']['requestBody']['content']['application/json'];
+export type FetchRssResponse = operations['fetch-rss']['responses']['200']['content']['application/json'];
+export type FlashCreateRequest = operations['flash___create']['requestBody']['content']['application/json'];
+export type FlashCreateResponse = operations['flash___create']['responses']['200']['content']['application/json'];
+export type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json'];
+export type FlashFeaturedRequest = operations['flash___featured']['requestBody']['content']['application/json'];
+export type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json'];
+export type FlashLikeRequest = operations['flash___like']['requestBody']['content']['application/json'];
+export type FlashMyRequest = operations['flash___my']['requestBody']['content']['application/json'];
+export type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json'];
+export type FlashMyLikesRequest = operations['flash___my-likes']['requestBody']['content']['application/json'];
+export type FlashMyLikesResponse = operations['flash___my-likes']['responses']['200']['content']['application/json'];
+export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json'];
+export type FlashShowResponse = operations['flash___show']['responses']['200']['content']['application/json'];
+export type FlashUnlikeRequest = operations['flash___unlike']['requestBody']['content']['application/json'];
+export type FlashUpdateRequest = operations['flash___update']['requestBody']['content']['application/json'];
export type FollowingCreateRequest = operations['following___create']['requestBody']['content']['application/json'];
export type FollowingCreateResponse = operations['following___create']['responses']['200']['content']['application/json'];
export type FollowingDeleteRequest = operations['following___delete']['requestBody']['content']['application/json'];
export type FollowingDeleteResponse = operations['following___delete']['responses']['200']['content']['application/json'];
-export type FollowingUpdateRequest = operations['following___update']['requestBody']['content']['application/json'];
-export type FollowingUpdateResponse = operations['following___update']['responses']['200']['content']['application/json'];
-export type FollowingUpdateAllRequest = operations['following___update-all']['requestBody']['content']['application/json'];
export type FollowingInvalidateRequest = operations['following___invalidate']['requestBody']['content']['application/json'];
export type FollowingInvalidateResponse = operations['following___invalidate']['responses']['200']['content']['application/json'];
export type FollowingRequestsAcceptRequest = operations['following___requests___accept']['requestBody']['content']['application/json'];
@@ -288,9 +311,12 @@ export type FollowingRequestsCancelRequest = operations['following___requests___
export type FollowingRequestsCancelResponse = operations['following___requests___cancel']['responses']['200']['content']['application/json'];
export type FollowingRequestsListRequest = operations['following___requests___list']['requestBody']['content']['application/json'];
export type FollowingRequestsListResponse = operations['following___requests___list']['responses']['200']['content']['application/json'];
+export type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json'];
export type FollowingRequestsSentRequest = operations['following___requests___sent']['requestBody']['content']['application/json'];
export type FollowingRequestsSentResponse = operations['following___requests___sent']['responses']['200']['content']['application/json'];
-export type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json'];
+export type FollowingUpdateRequest = operations['following___update']['requestBody']['content']['application/json'];
+export type FollowingUpdateResponse = operations['following___update']['responses']['200']['content']['application/json'];
+export type FollowingUpdateAllRequest = operations['following___update-all']['requestBody']['content']['application/json'];
export type GalleryFeaturedRequest = operations['gallery___featured']['requestBody']['content']['application/json'];
export type GalleryFeaturedResponse = operations['gallery___featured']['responses']['200']['content']['application/json'];
export type GalleryPopularResponse = operations['gallery___popular']['responses']['200']['content']['application/json'];
@@ -305,8 +331,8 @@ export type GalleryPostsShowResponse = operations['gallery___posts___show']['res
export type GalleryPostsUnlikeRequest = operations['gallery___posts___unlike']['requestBody']['content']['application/json'];
export type GalleryPostsUpdateRequest = operations['gallery___posts___update']['requestBody']['content']['application/json'];
export type GalleryPostsUpdateResponse = operations['gallery___posts___update']['responses']['200']['content']['application/json'];
-export type GetOnlineUsersCountResponse = operations['get-online-users-count']['responses']['200']['content']['application/json'];
export type GetAvatarDecorationsResponse = operations['get-avatar-decorations']['responses']['200']['content']['application/json'];
+export type GetOnlineUsersCountResponse = operations['get-online-users-count']['responses']['200']['content']['application/json'];
export type HashtagsListRequest = operations['hashtags___list']['requestBody']['content']['application/json'];
export type HashtagsListResponse = operations['hashtags___list']['responses']['200']['content']['application/json'];
export type HashtagsSearchRequest = operations['hashtags___search']['requestBody']['content']['application/json'];
@@ -322,19 +348,19 @@ export type I2faDoneResponse = operations['i___2fa___done']['responses']['200'][
export type I2faKeyDoneRequest = operations['i___2fa___key-done']['requestBody']['content']['application/json'];
export type I2faKeyDoneResponse = operations['i___2fa___key-done']['responses']['200']['content']['application/json'];
export type I2faPasswordLessRequest = operations['i___2fa___password-less']['requestBody']['content']['application/json'];
-export type I2faRegisterKeyRequest = operations['i___2fa___register-key']['requestBody']['content']['application/json'];
-export type I2faRegisterKeyResponse = operations['i___2fa___register-key']['responses']['200']['content']['application/json'];
export type I2faRegisterRequest = operations['i___2fa___register']['requestBody']['content']['application/json'];
export type I2faRegisterResponse = operations['i___2fa___register']['responses']['200']['content']['application/json'];
-export type I2faUpdateKeyRequest = operations['i___2fa___update-key']['requestBody']['content']['application/json'];
+export type I2faRegisterKeyRequest = operations['i___2fa___register-key']['requestBody']['content']['application/json'];
+export type I2faRegisterKeyResponse = operations['i___2fa___register-key']['responses']['200']['content']['application/json'];
export type I2faRemoveKeyRequest = operations['i___2fa___remove-key']['requestBody']['content']['application/json'];
export type I2faUnregisterRequest = operations['i___2fa___unregister']['requestBody']['content']['application/json'];
+export type I2faUpdateKeyRequest = operations['i___2fa___update-key']['requestBody']['content']['application/json'];
export type IAppsRequest = operations['i___apps']['requestBody']['content']['application/json'];
export type IAppsResponse = operations['i___apps']['responses']['200']['content']['application/json'];
export type IAuthorizedAppsRequest = operations['i___authorized-apps']['requestBody']['content']['application/json'];
export type IAuthorizedAppsResponse = operations['i___authorized-apps']['responses']['200']['content']['application/json'];
-export type IClaimAchievementRequest = operations['i___claim-achievement']['requestBody']['content']['application/json'];
export type IChangePasswordRequest = operations['i___change-password']['requestBody']['content']['application/json'];
+export type IClaimAchievementRequest = operations['i___claim-achievement']['requestBody']['content']['application/json'];
export type IDeleteAccountRequest = operations['i___delete-account']['requestBody']['content']['application/json'];
export type IExportFollowingRequest = operations['i___export-following']['requestBody']['content']['application/json'];
export type IFavoritesRequest = operations['i___favorites']['requestBody']['content']['application/json'];
@@ -343,12 +369,14 @@ export type IGalleryLikesRequest = operations['i___gallery___likes']['requestBod
export type IGalleryLikesResponse = operations['i___gallery___likes']['responses']['200']['content']['application/json'];
export type IGalleryPostsRequest = operations['i___gallery___posts']['requestBody']['content']['application/json'];
export type IGalleryPostsResponse = operations['i___gallery___posts']['responses']['200']['content']['application/json'];
+export type IImportAntennasRequest = operations['i___import-antennas']['requestBody']['content']['application/json'];
export type IImportBlockingRequest = operations['i___import-blocking']['requestBody']['content']['application/json'];
export type IImportFollowingRequest = operations['i___import-following']['requestBody']['content']['application/json'];
export type IImportNotesRequest = operations['i___import-notes']['requestBody']['content']['application/json'];
export type IImportMutingRequest = operations['i___import-muting']['requestBody']['content']['application/json'];
export type IImportUserListsRequest = operations['i___import-user-lists']['requestBody']['content']['application/json'];
-export type IImportAntennasRequest = operations['i___import-antennas']['requestBody']['content']['application/json'];
+export type IMoveRequest = operations['i___move']['requestBody']['content']['application/json'];
+export type IMoveResponse = operations['i___move']['responses']['200']['content']['application/json'];
export type INotificationsRequest = operations['i___notifications']['requestBody']['content']['application/json'];
export type INotificationsResponse = operations['i___notifications']['responses']['200']['content']['application/json'];
export type INotificationsGroupedRequest = operations['i___notifications-grouped']['requestBody']['content']['application/json'];
@@ -361,17 +389,17 @@ export type IPinRequest = operations['i___pin']['requestBody']['content']['appli
export type IPinResponse = operations['i___pin']['responses']['200']['content']['application/json'];
export type IReadAnnouncementRequest = operations['i___read-announcement']['requestBody']['content']['application/json'];
export type IRegenerateTokenRequest = operations['i___regenerate-token']['requestBody']['content']['application/json'];
+export type IRegistryGetRequest = operations['i___registry___get']['requestBody']['content']['application/json'];
+export type IRegistryGetResponse = operations['i___registry___get']['responses']['200']['content']['application/json'];
export type IRegistryGetAllRequest = operations['i___registry___get-all']['requestBody']['content']['application/json'];
export type IRegistryGetAllResponse = operations['i___registry___get-all']['responses']['200']['content']['application/json'];
export type IRegistryGetUnsecureRequest = operations['i___registry___get-unsecure']['requestBody']['content']['application/json'];
export type IRegistryGetDetailRequest = operations['i___registry___get-detail']['requestBody']['content']['application/json'];
export type IRegistryGetDetailResponse = operations['i___registry___get-detail']['responses']['200']['content']['application/json'];
-export type IRegistryGetRequest = operations['i___registry___get']['requestBody']['content']['application/json'];
-export type IRegistryGetResponse = operations['i___registry___get']['responses']['200']['content']['application/json'];
-export type IRegistryKeysWithTypeRequest = operations['i___registry___keys-with-type']['requestBody']['content']['application/json'];
-export type IRegistryKeysWithTypeResponse = operations['i___registry___keys-with-type']['responses']['200']['content']['application/json'];
export type IRegistryKeysRequest = operations['i___registry___keys']['requestBody']['content']['application/json'];
export type IRegistryKeysResponse = operations['i___registry___keys']['responses']['200']['content']['application/json'];
+export type IRegistryKeysWithTypeRequest = operations['i___registry___keys-with-type']['requestBody']['content']['application/json'];
+export type IRegistryKeysWithTypeResponse = operations['i___registry___keys-with-type']['responses']['200']['content']['application/json'];
export type IRegistryRemoveRequest = operations['i___registry___remove']['requestBody']['content']['application/json'];
export type IRegistryScopesWithDomainResponse = operations['i___registry___scopes-with-domain']['responses']['200']['content']['application/json'];
export type IRegistrySetRequest = operations['i___registry___set']['requestBody']['content']['application/json'];
@@ -380,40 +408,31 @@ export type ISigninHistoryRequest = operations['i___signin-history']['requestBod
export type ISigninHistoryResponse = operations['i___signin-history']['responses']['200']['content']['application/json'];
export type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json'];
export type IUnpinResponse = operations['i___unpin']['responses']['200']['content']['application/json'];
-export type IUpdateEmailRequest = operations['i___update-email']['requestBody']['content']['application/json'];
-export type IUpdateEmailResponse = operations['i___update-email']['responses']['200']['content']['application/json'];
export type IUpdateRequest = operations['i___update']['requestBody']['content']['application/json'];
export type IUpdateResponse = operations['i___update']['responses']['200']['content']['application/json'];
-export type IMoveRequest = operations['i___move']['requestBody']['content']['application/json'];
-export type IMoveResponse = operations['i___move']['responses']['200']['content']['application/json'];
+export type IUpdateEmailRequest = operations['i___update-email']['requestBody']['content']['application/json'];
+export type IUpdateEmailResponse = operations['i___update-email']['responses']['200']['content']['application/json'];
export type IWebhooksCreateRequest = operations['i___webhooks___create']['requestBody']['content']['application/json'];
export type IWebhooksCreateResponse = operations['i___webhooks___create']['responses']['200']['content']['application/json'];
+export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json'];
export type IWebhooksListResponse = operations['i___webhooks___list']['responses']['200']['content']['application/json'];
export type IWebhooksShowRequest = operations['i___webhooks___show']['requestBody']['content']['application/json'];
export type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json'];
-export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
-export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json'];
export type IWebhooksTestRequest = operations['i___webhooks___test']['requestBody']['content']['application/json'];
+export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
export type InviteCreateResponse = operations['invite___create']['responses']['200']['content']['application/json'];
export type InviteDeleteRequest = operations['invite___delete']['requestBody']['content']['application/json'];
+export type InviteLimitResponse = operations['invite___limit']['responses']['200']['content']['application/json'];
export type InviteListRequest = operations['invite___list']['requestBody']['content']['application/json'];
export type InviteListResponse = operations['invite___list']['responses']['200']['content']['application/json'];
-export type InviteLimitResponse = operations['invite___limit']['responses']['200']['content']['application/json'];
export type MetaRequest = operations['meta']['requestBody']['content']['application/json'];
export type MetaResponse = operations['meta']['responses']['200']['content']['application/json'];
-export type EmojisResponse = operations['emojis']['responses']['200']['content']['application/json'];
-export type EmojiRequest = operations['emoji']['requestBody']['content']['application/json'];
-export type EmojiResponse = operations['emoji']['responses']['200']['content']['application/json'];
export type MiauthGenTokenRequest = operations['miauth___gen-token']['requestBody']['content']['application/json'];
export type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json'];
export type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
export type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json'];
export type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json'];
export type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json'];
-export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
-export type RenoteMuteDeleteRequest = operations['renote-mute___delete']['requestBody']['content']['application/json'];
-export type RenoteMuteListRequest = operations['renote-mute___list']['requestBody']['content']['application/json'];
-export type RenoteMuteListResponse = operations['renote-mute___list']['responses']['200']['content']['application/json'];
export type MyAppsRequest = operations['my___apps']['requestBody']['content']['application/json'];
export type MyAppsResponse = operations['my___apps']['responses']['200']['content']['application/json'];
export type NotesRequest = operations['notes']['requestBody']['content']['application/json'];
@@ -492,49 +511,58 @@ export type PagesShowRequest = operations['pages___show']['requestBody']['conten
export type PagesShowResponse = operations['pages___show']['responses']['200']['content']['application/json'];
export type PagesUnlikeRequest = operations['pages___unlike']['requestBody']['content']['application/json'];
export type PagesUpdateRequest = operations['pages___update']['requestBody']['content']['application/json'];
-export type FlashCreateRequest = operations['flash___create']['requestBody']['content']['application/json'];
-export type FlashCreateResponse = operations['flash___create']['responses']['200']['content']['application/json'];
-export type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json'];
-export type FlashFeaturedRequest = operations['flash___featured']['requestBody']['content']['application/json'];
-export type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json'];
-export type FlashLikeRequest = operations['flash___like']['requestBody']['content']['application/json'];
-export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json'];
-export type FlashShowResponse = operations['flash___show']['responses']['200']['content']['application/json'];
-export type FlashUnlikeRequest = operations['flash___unlike']['requestBody']['content']['application/json'];
-export type FlashUpdateRequest = operations['flash___update']['requestBody']['content']['application/json'];
-export type FlashMyRequest = operations['flash___my']['requestBody']['content']['application/json'];
-export type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json'];
-export type FlashMyLikesRequest = operations['flash___my-likes']['requestBody']['content']['application/json'];
-export type FlashMyLikesResponse = operations['flash___my-likes']['responses']['200']['content']['application/json'];
export type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
export type PinnedUsersResponse = operations['pinned-users']['responses']['200']['content']['application/json'];
export type PromoReadRequest = operations['promo___read']['requestBody']['content']['application/json'];
+export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
+export type RenoteMuteDeleteRequest = operations['renote-mute___delete']['requestBody']['content']['application/json'];
+export type RenoteMuteListRequest = operations['renote-mute___list']['requestBody']['content']['application/json'];
+export type RenoteMuteListResponse = operations['renote-mute___list']['responses']['200']['content']['application/json'];
+export type RequestResetPasswordRequest = operations['request-reset-password']['requestBody']['content']['application/json'];
+export type ResetPasswordRequest = operations['reset-password']['requestBody']['content']['application/json'];
+export type RetentionResponse = operations['retention']['responses']['200']['content']['application/json'];
+export type ReversiCancelMatchRequest = operations['reversi___cancel-match']['requestBody']['content']['application/json'];
+export type ReversiGamesRequest = operations['reversi___games']['requestBody']['content']['application/json'];
+export type ReversiGamesResponse = operations['reversi___games']['responses']['200']['content']['application/json'];
+export type ReversiInvitationsResponse = operations['reversi___invitations']['responses']['200']['content']['application/json'];
+export type ReversiMatchRequest = operations['reversi___match']['requestBody']['content']['application/json'];
+export type ReversiMatchResponse = operations['reversi___match']['responses']['200']['content']['application/json'];
+export type ReversiShowGameRequest = operations['reversi___show-game']['requestBody']['content']['application/json'];
+export type ReversiShowGameResponse = operations['reversi___show-game']['responses']['200']['content']['application/json'];
+export type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json'];
+export type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json'];
+export type ReversiVerifyResponse = operations['reversi___verify']['responses']['200']['content']['application/json'];
export type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json'];
+export type RolesNotesRequest = operations['roles___notes']['requestBody']['content']['application/json'];
+export type RolesNotesResponse = operations['roles___notes']['responses']['200']['content']['application/json'];
export type RolesShowRequest = operations['roles___show']['requestBody']['content']['application/json'];
export type RolesShowResponse = operations['roles___show']['responses']['200']['content']['application/json'];
export type RolesUsersRequest = operations['roles___users']['requestBody']['content']['application/json'];
export type RolesUsersResponse = operations['roles___users']['responses']['200']['content']['application/json'];
-export type RolesNotesRequest = operations['roles___notes']['requestBody']['content']['application/json'];
-export type RolesNotesResponse = operations['roles___notes']['responses']['200']['content']['application/json'];
-export type RequestResetPasswordRequest = operations['request-reset-password']['requestBody']['content']['application/json'];
-export type ResetPasswordRequest = operations['reset-password']['requestBody']['content']['application/json'];
export type ServerInfoResponse = operations['server-info']['responses']['200']['content']['application/json'];
+export type SponsorsRequest = operations['sponsors']['requestBody']['content']['application/json'];
export type StatsResponse = operations['stats']['responses']['200']['content']['application/json'];
+export type SwRegisterRequest = operations['sw___register']['requestBody']['content']['application/json'];
+export type SwRegisterResponse = operations['sw___register']['responses']['200']['content']['application/json'];
export type SwShowRegistrationRequest = operations['sw___show-registration']['requestBody']['content']['application/json'];
export type SwShowRegistrationResponse = operations['sw___show-registration']['responses']['200']['content']['application/json'];
+export type SwUnregisterRequest = operations['sw___unregister']['requestBody']['content']['application/json'];
export type SwUpdateRegistrationRequest = operations['sw___update-registration']['requestBody']['content']['application/json'];
export type SwUpdateRegistrationResponse = operations['sw___update-registration']['responses']['200']['content']['application/json'];
-export type SwRegisterRequest = operations['sw___register']['requestBody']['content']['application/json'];
-export type SwRegisterResponse = operations['sw___register']['responses']['200']['content']['application/json'];
-export type SwUnregisterRequest = operations['sw___unregister']['requestBody']['content']['application/json'];
export type TestRequest = operations['test']['requestBody']['content']['application/json'];
export type TestResponse = operations['test']['responses']['200']['content']['application/json'];
export type UsernameAvailableRequest = operations['username___available']['requestBody']['content']['application/json'];
export type UsernameAvailableResponse = operations['username___available']['responses']['200']['content']['application/json'];
export type UsersRequest = operations['users']['requestBody']['content']['application/json'];
export type UsersResponse = operations['users']['responses']['200']['content']['application/json'];
+export type UsersAchievementsRequest = operations['users___achievements']['requestBody']['content']['application/json'];
+export type UsersAchievementsResponse = operations['users___achievements']['responses']['200']['content']['application/json'];
export type UsersClipsRequest = operations['users___clips']['requestBody']['content']['application/json'];
export type UsersClipsResponse = operations['users___clips']['responses']['200']['content']['application/json'];
+export type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json'];
+export type UsersFeaturedNotesResponse = operations['users___featured-notes']['responses']['200']['content']['application/json'];
+export type UsersFlashsRequest = operations['users___flashs']['requestBody']['content']['application/json'];
+export type UsersFlashsResponse = operations['users___flashs']['responses']['200']['content']['application/json'];
export type UsersFollowersRequest = operations['users___followers']['requestBody']['content']['application/json'];
export type UsersFollowersResponse = operations['users___followers']['responses']['200']['content']['application/json'];
export type UsersFollowingRequest = operations['users___following']['requestBody']['content']['application/json'];
@@ -543,32 +571,28 @@ export type UsersGalleryPostsRequest = operations['users___gallery___posts']['re
export type UsersGalleryPostsResponse = operations['users___gallery___posts']['responses']['200']['content']['application/json'];
export type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json'];
export type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json'];
-export type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json'];
-export type UsersFeaturedNotesResponse = operations['users___featured-notes']['responses']['200']['content']['application/json'];
export type UsersListsCreateRequest = operations['users___lists___create']['requestBody']['content']['application/json'];
export type UsersListsCreateResponse = operations['users___lists___create']['responses']['200']['content']['application/json'];
+export type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json'];
+export type UsersListsCreateFromPublicResponse = operations['users___lists___create-from-public']['responses']['200']['content']['application/json'];
export type UsersListsDeleteRequest = operations['users___lists___delete']['requestBody']['content']['application/json'];
+export type UsersListsFavoriteRequest = operations['users___lists___favorite']['requestBody']['content']['application/json'];
+export type UsersListsGetMembershipsRequest = operations['users___lists___get-memberships']['requestBody']['content']['application/json'];
+export type UsersListsGetMembershipsResponse = operations['users___lists___get-memberships']['responses']['200']['content']['application/json'];
export type UsersListsListRequest = operations['users___lists___list']['requestBody']['content']['application/json'];
export type UsersListsListResponse = operations['users___lists___list']['responses']['200']['content']['application/json'];
export type UsersListsPullRequest = operations['users___lists___pull']['requestBody']['content']['application/json'];
export type UsersListsPushRequest = operations['users___lists___push']['requestBody']['content']['application/json'];
export type UsersListsShowRequest = operations['users___lists___show']['requestBody']['content']['application/json'];
export type UsersListsShowResponse = operations['users___lists___show']['responses']['200']['content']['application/json'];
-export type UsersListsFavoriteRequest = operations['users___lists___favorite']['requestBody']['content']['application/json'];
export type UsersListsUnfavoriteRequest = operations['users___lists___unfavorite']['requestBody']['content']['application/json'];
export type UsersListsUpdateRequest = operations['users___lists___update']['requestBody']['content']['application/json'];
export type UsersListsUpdateResponse = operations['users___lists___update']['responses']['200']['content']['application/json'];
-export type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json'];
-export type UsersListsCreateFromPublicResponse = operations['users___lists___create-from-public']['responses']['200']['content']['application/json'];
export type UsersListsUpdateMembershipRequest = operations['users___lists___update-membership']['requestBody']['content']['application/json'];
-export type UsersListsGetMembershipsRequest = operations['users___lists___get-memberships']['requestBody']['content']['application/json'];
-export type UsersListsGetMembershipsResponse = operations['users___lists___get-memberships']['responses']['200']['content']['application/json'];
export type UsersNotesRequest = operations['users___notes']['requestBody']['content']['application/json'];
export type UsersNotesResponse = operations['users___notes']['responses']['200']['content']['application/json'];
export type UsersPagesRequest = operations['users___pages']['requestBody']['content']['application/json'];
export type UsersPagesResponse = operations['users___pages']['responses']['200']['content']['application/json'];
-export type UsersFlashsRequest = operations['users___flashs']['requestBody']['content']['application/json'];
-export type UsersFlashsResponse = operations['users___flashs']['responses']['200']['content']['application/json'];
export type UsersReactionsRequest = operations['users___reactions']['requestBody']['content']['application/json'];
export type UsersReactionsResponse = operations['users___reactions']['responses']['200']['content']['application/json'];
export type UsersRecommendationRequest = operations['users___recommendation']['requestBody']['content']['application/json'];
@@ -576,32 +600,12 @@ export type UsersRecommendationResponse = operations['users___recommendation']['
export type UsersRelationRequest = operations['users___relation']['requestBody']['content']['application/json'];
export type UsersRelationResponse = operations['users___relation']['responses']['200']['content']['application/json'];
export type UsersReportAbuseRequest = operations['users___report-abuse']['requestBody']['content']['application/json'];
-export type UsersSearchByUsernameAndHostRequest = operations['users___search-by-username-and-host']['requestBody']['content']['application/json'];
-export type UsersSearchByUsernameAndHostResponse = operations['users___search-by-username-and-host']['responses']['200']['content']['application/json'];
export type UsersSearchRequest = operations['users___search']['requestBody']['content']['application/json'];
export type UsersSearchResponse = operations['users___search']['responses']['200']['content']['application/json'];
+export type UsersSearchByUsernameAndHostRequest = operations['users___search-by-username-and-host']['requestBody']['content']['application/json'];
+export type UsersSearchByUsernameAndHostResponse = operations['users___search-by-username-and-host']['responses']['200']['content']['application/json'];
export type UsersShowRequest = operations['users___show']['requestBody']['content']['application/json'];
export type UsersShowResponse = operations['users___show']['responses']['200']['content']['application/json'];
-export type UsersAchievementsRequest = operations['users___achievements']['requestBody']['content']['application/json'];
-export type UsersAchievementsResponse = operations['users___achievements']['responses']['200']['content']['application/json'];
export type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['content']['application/json'];
-export type FetchRssRequest = operations['fetch-rss']['requestBody']['content']['application/json'];
-export type FetchRssResponse = operations['fetch-rss']['responses']['200']['content']['application/json'];
-export type FetchExternalResourcesRequest = operations['fetch-external-resources']['requestBody']['content']['application/json'];
-export type FetchExternalResourcesResponse = operations['fetch-external-resources']['responses']['200']['content']['application/json'];
-export type RetentionResponse = operations['retention']['responses']['200']['content']['application/json'];
-export type SponsorsRequest = operations['sponsors']['requestBody']['content']['application/json'];
-export type BubbleGameRegisterRequest = operations['bubble-game___register']['requestBody']['content']['application/json'];
-export type BubbleGameRankingRequest = operations['bubble-game___ranking']['requestBody']['content']['application/json'];
-export type BubbleGameRankingResponse = operations['bubble-game___ranking']['responses']['200']['content']['application/json'];
-export type ReversiCancelMatchRequest = operations['reversi___cancel-match']['requestBody']['content']['application/json'];
-export type ReversiGamesRequest = operations['reversi___games']['requestBody']['content']['application/json'];
-export type ReversiGamesResponse = operations['reversi___games']['responses']['200']['content']['application/json'];
-export type ReversiMatchRequest = operations['reversi___match']['requestBody']['content']['application/json'];
-export type ReversiMatchResponse = operations['reversi___match']['responses']['200']['content']['application/json'];
-export type ReversiInvitationsResponse = operations['reversi___invitations']['responses']['200']['content']['application/json'];
-export type ReversiShowGameRequest = operations['reversi___show-game']['requestBody']['content']['application/json'];
-export type ReversiShowGameResponse = operations['reversi___show-game']['responses']['200']['content']['application/json'];
-export type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json'];
-export type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json'];
-export type ReversiVerifyResponse = operations['reversi___verify']['responses']['200']['content']['application/json'];
+export type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestBody']['content']['application/json'];
+export type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index 04574849d4..1a30da4437 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -33,6 +33,7 @@ export type FederationInstance = components['schemas']['FederationInstance'];
export type GalleryPost = components['schemas']['GalleryPost'];
export type EmojiSimple = components['schemas']['EmojiSimple'];
export type EmojiDetailed = components['schemas']['EmojiDetailed'];
+export type EmojiDetailedAdmin = components['schemas']['EmojiDetailedAdmin'];
export type Flash = components['schemas']['Flash'];
export type Signin = components['schemas']['Signin'];
export type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics'];
diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts
index 6e34ec1508..0ef2d1e7a1 100644
--- a/packages/misskey-js/src/streaming.ts
+++ b/packages/misskey-js/src/streaming.ts
@@ -1,8 +1,10 @@
import { EventEmitter } from 'eventemitter3';
-import _ReconnectingWebsocket from 'reconnecting-websocket';
+import _ReconnectingWebSocket, { Options } from 'reconnecting-websocket';
import type { BroadcastEvents, Channels } from './streaming.types.js';
-const ReconnectingWebsocket = _ReconnectingWebsocket as unknown as typeof _ReconnectingWebsocket['default'];
+// コンストラクタã¨ã‚¯ãƒ©ã‚¹ãã®ã‚‚ã®ã®å®šç¾©ãŒä¸Šæ‰‹ã解決出æ¥ãªã„ãŸã‚å†å®šç¾©
+const ReconnectingWebSocketConstructor = _ReconnectingWebSocket as unknown as typeof _ReconnectingWebSocket.default;
+type ReconnectingWebSocket = _ReconnectingWebSocket.default;
export function urlQuery(obj: Record<string, string | number | boolean | undefined>): string {
const params = Object.entries(obj)
@@ -43,7 +45,7 @@ export interface IStream extends EventEmitter<StreamEvents> {
*/
// eslint-disable-next-line import/no-default-export
export default class Stream extends EventEmitter<StreamEvents> implements IStream {
- private stream: _ReconnectingWebsocket.default;
+ private stream: ReconnectingWebSocket;
public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
private sharedConnectionPools: Pool[] = [];
private sharedConnections: SharedConnection[] = [];
@@ -51,7 +53,8 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
private idCounter = 0;
constructor(origin: string, user: { token: string; } | null, options?: {
- WebSocket?: _ReconnectingWebsocket.Options['WebSocket'];
+ WebSocket?: Options['WebSocket'];
+ binaryType?: ReconnectingWebSocket['binaryType'];
}) {
super();
@@ -80,10 +83,13 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
const wsOrigin = origin.replace('http://', 'ws://').replace('https://', 'wss://');
- this.stream = new ReconnectingWebsocket(`${wsOrigin}/streaming?${query}`, '', {
+ this.stream = new ReconnectingWebSocketConstructor(`${wsOrigin}/streaming?${query}`, '', {
minReconnectionDelay: 1, // https://github.com/pladaria/reconnecting-websocket/issues/91
WebSocket: options.WebSocket,
});
+ if (options.binaryType) {
+ this.stream.binaryType = options.binaryType;
+ }
this.stream.addEventListener('open', this.onOpen);
this.stream.addEventListener('close', this.onClose);
this.stream.addEventListener('message', this.onMessage);
diff --git a/packages/misskey-reversi/build.js b/packages/misskey-reversi/build.js
index a80b71646f..5d534cc6fd 100644
--- a/packages/misskey-reversi/build.js
+++ b/packages/misskey-reversi/build.js
@@ -23,10 +23,14 @@ const options = {
sourcemap: 'linked',
};
+const args = process.argv.slice(2).map(arg => arg.toLowerCase());
+
// builté…下をã™ã¹ã¦å‰Šé™¤ã™ã‚‹
-fs.rmSync('./built', { recursive: true, force: true });
+if (!args.includes('--no-clean')) {
+ fs.rmSync('./built', { recursive: true, force: true });
+}
-if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) {
+if (args.includes('--watch')) {
await watchSrc();
} else {
await buildSrc();
diff --git a/packages/shared/eslint.config.js b/packages/shared/eslint.config.js
index 0368d008c0..860eb4a8e8 100644
--- a/packages/shared/eslint.config.js
+++ b/packages/shared/eslint.config.js
@@ -32,4 +32,11 @@ export default [
'@typescript-eslint/no-var-requires': 'off',
},
},
+ {
+ rules: {
+ 'no-restricted-imports': ['error', {
+ paths: [{ name: 'punycode' }],
+ }],
+ },
+ },
];
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a5580f1a7e..303ea5a26a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -184,7 +184,7 @@ importers:
version: 7.0.1
argon2:
specifier: ^0.40.1
- version: 0.40.1
+ version: 0.40.3
async-mutex:
specifier: 0.5.0
version: 0.5.0
@@ -373,13 +373,10 @@ importers:
version: 2.0.7
psl:
specifier: ^1.13.0
- version: 1.13.0
+ version: 1.15.0
pug:
specifier: 3.0.3
version: 3.0.3
- punycode:
- specifier: 2.3.1
- version: 2.3.1
qrcode:
specifier: 1.5.4
version: 1.5.4
@@ -631,9 +628,6 @@ importers:
'@types/pug':
specifier: 2.0.10
version: 2.0.10
- '@types/punycode':
- specifier: 2.1.4
- version: 2.1.4
'@types/qrcode':
specifier: 1.5.5
version: 1.5.5
@@ -758,8 +752,8 @@ importers:
specifier: 3.5.12
version: 3.5.12
aiscript-vscode:
- specifier: github:aiscript-dev/aiscript-vscode#v0.1.11
- version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9
+ specifier: github:aiscript-dev/aiscript-vscode#v0.1.15
+ version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7
astring:
specifier: 1.9.0
version: 1.9.0
@@ -841,7 +835,7 @@ importers:
photoswipe:
specifier: 5.4.4
version: 5.4.4
- punycode:
+ punycode.js:
specifier: 2.3.1
version: 2.3.1
rollup:
@@ -978,9 +972,9 @@ importers:
'@types/node':
specifier: 22.9.0
version: 22.9.0
- '@types/punycode':
- specifier: 2.1.4
- version: 2.1.4
+ '@types/punycode.js':
+ specifier: npm:@types/punycode@2.1.4
+ version: '@types/punycode@2.1.4'
'@types/sanitize-html':
specifier: 2.13.0
version: 2.13.0
@@ -1131,7 +1125,7 @@ importers:
misskey-js:
specifier: workspace:*
version: link:../misskey-js
- punycode:
+ punycode.js:
specifier: 2.3.1
version: 2.3.1
rollup:
@@ -1180,9 +1174,9 @@ importers:
'@types/node':
specifier: 22.9.0
version: 22.9.0
- '@types/punycode':
- specifier: 2.1.4
- version: 2.1.4
+ '@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
@@ -1277,6 +1271,9 @@ importers:
eslint-plugin-vue:
specifier: 9.31.0
version: 9.31.0(eslint@9.14.0)
+ nodemon:
+ specifier: 3.1.7
+ version: 3.1.7
typescript:
specifier: 5.6.3
version: 5.6.3
@@ -1291,13 +1288,13 @@ importers:
version: 2.5.8
'@types/form-data':
specifier: ^2.5.0
- version: 2.5.0
+ version: 2.5.2
'@types/jest':
specifier: ^29.5.10
- version: 29.5.12
+ version: 29.5.14
'@types/oauth':
specifier: ^0.9.4
- version: 0.9.5
+ version: 0.9.6
'@types/object-assign-deep':
specifier: ^0.4.3
version: 0.4.3
@@ -1309,7 +1306,7 @@ importers:
version: 9.0.8
'@types/ws':
specifier: ^8.5.10
- version: 8.5.11
+ version: 8.5.13
axios:
specifier: 1.7.4
version: 1.7.4
@@ -1318,10 +1315,10 @@ importers:
version: 1.11.10
form-data:
specifier: ^4.0.0
- version: 4.0.0
+ version: 4.0.1
https-proxy-agent:
specifier: ^7.0.2
- version: 7.0.2
+ version: 7.0.5
oauth:
specifier: ^0.10.0
version: 0.10.0
@@ -1346,16 +1343,16 @@ importers:
devDependencies:
'@typescript-eslint/eslint-plugin':
specifier: ^6.12.0
- version: 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)
+ version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1)(typescript@5.1.6)
'@typescript-eslint/parser':
specifier: ^6.12.0
- version: 6.21.0(eslint@8.57.0)(typescript@5.1.6)
+ version: 6.21.0(eslint@8.57.1)(typescript@5.1.6)
eslint:
specifier: ^8.54.0
- version: 8.57.0
+ version: 8.57.1
eslint-config-prettier:
specifier: ^9.0.0
- version: 9.1.0(eslint@8.57.0)
+ version: 9.1.0(eslint@8.57.1)
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@22.9.0)
@@ -1370,7 +1367,7 @@ importers:
version: 3.3.3
ts-jest:
specifier: ^29.1.1
- version: 29.1.2(@babel/core@7.23.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.5))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6)
+ version: 29.2.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6)
typedoc:
specifier: ^0.25.3
version: 0.25.13(typescript@5.1.6)
@@ -1788,6 +1785,10 @@ packages:
resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==}
engines: {node: '>=6.9.0'}
+ '@babel/generator@7.23.5':
+ resolution: {integrity: sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==}
+ engines: {node: '>=6.9.0'}
+
'@babel/generator@7.24.7':
resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==}
engines: {node: '>=6.9.0'}
@@ -1800,14 +1801,26 @@ packages:
resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-environment-visitor@7.22.20':
+ resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-environment-visitor@7.24.7':
resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-function-name@7.23.0':
+ resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-function-name@7.24.7':
resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-hoist-variables@7.22.5':
+ resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-hoist-variables@7.24.7':
resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==}
engines: {node: '>=6.9.0'}
@@ -1844,6 +1857,10 @@ packages:
resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-split-export-declaration@7.22.6':
+ resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-split-export-declaration@7.24.7':
resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==}
engines: {node: '>=6.9.0'}
@@ -1852,18 +1869,14 @@ packages:
resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
engines: {node: '>=6.9.0'}
- '@babel/helper-string-parser@7.25.7':
- resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==}
+ '@babel/helper-string-parser@7.24.8':
+ resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.24.7':
resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
engines: {node: '>=6.9.0'}
- '@babel/helper-validator-identifier@7.25.7':
- resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==}
- engines: {node: '>=6.9.0'}
-
'@babel/helper-validator-option@7.23.5':
resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
engines: {node: '>=6.9.0'}
@@ -1880,6 +1893,10 @@ packages:
resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==}
engines: {node: '>=6.9.0'}
+ '@babel/highlight@7.23.4':
+ resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
+ engines: {node: '>=6.9.0'}
+
'@babel/highlight@7.24.7':
resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
engines: {node: '>=6.9.0'}
@@ -1889,8 +1906,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/parser@7.25.7':
- resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==}
+ '@babel/parser@7.25.6':
+ resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -1995,8 +2012,8 @@ packages:
resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.25.7':
- resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==}
+ '@babel/types@7.25.6':
+ resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==}
engines: {node: '>=6.9.0'}
'@bcoe/v8-coverage@0.2.3':
@@ -2076,8 +2093,8 @@ packages:
'@discordapp/twemoji@15.1.0':
resolution: {integrity: sha512-QdpV4ifTONAXvDjRrMohausZeGrQ1ac/Ox6togUh6Xl3XKJ/KAaMMuAEi0qsb0wDwoVTSZBll5Y6+N3hB2ktBw==}
- '@emnapi/runtime@1.3.0':
- resolution: {integrity: sha512-XMBySMuNZs3DM96xcJmLW4EfGnf+uGmFNjzpehMjuX5PLB5j87ar2Zc4e3PVeZ3I5g3tYtAqskB28manlF69Zw==}
+ '@emnapi/runtime@1.2.0':
+ resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==}
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
@@ -2531,8 +2548,8 @@ packages:
resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@eslint/js@8.57.0':
- resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
+ '@eslint/js@8.57.1':
+ resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
'@eslint/js@9.14.0':
@@ -2543,8 +2560,8 @@ packages:
resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@eslint/plugin-kit@0.2.3':
- resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
+ '@eslint/plugin-kit@0.2.0':
+ resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@fastify/accept-negotiator@2.0.0':
@@ -2553,8 +2570,8 @@ packages:
'@fastify/accepts@5.0.1':
resolution: {integrity: sha512-8ji2MGTbceSnAXKYx/U9iWt6Fmf0zJovh0meO5rpwYS/vy0Z3QIR2J/hKmbcTpYfMu5NUliNpsAtMavmzBQhmA==}
- '@fastify/ajv-compiler@4.0.1':
- resolution: {integrity: sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==}
+ '@fastify/ajv-compiler@4.0.0':
+ resolution: {integrity: sha512-dt0jyLAlay14LpIn4Fg1SY7V5NJ9KH0YFDpYVQY5cgIVBvdI8908AMx5zQ0bBYPGT6Wh+bM3f2caMmOXLP3QsQ==}
'@fastify/busboy@1.2.1':
resolution: {integrity: sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==}
@@ -2582,8 +2599,8 @@ packages:
'@fastify/express@4.0.1':
resolution: {integrity: sha512-mEQ6pawaENeZ3swqVtkxdLi8NQC5eKBkclE+7ma1qQMuB+yI6WxDyEp55pdbqPIqBQTN/cGgHv84qxVS7NKC2Q==}
- '@fastify/fast-json-stringify-compiler@5.0.1':
- resolution: {integrity: sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==}
+ '@fastify/fast-json-stringify-compiler@5.0.0':
+ resolution: {integrity: sha512-tywfuZfXsyxLC5kEqrMubbFa9vpAxNtuPE7j9w5si1r+6p5b981pDfZ5Y8HBqmjDQl+PABT7cV5jZgXI2j+I5g==}
'@fastify/http-proxy@10.0.1':
resolution: {integrity: sha512-wCMwI9RXK5ISe9G1FGPDCCD2KlSAuLtDDU8XBEfiBxYV0nt+aYm4vPhU/0+IhUM6t+r7UWiV+9OYaJxcTem9+g==}
@@ -2594,8 +2611,8 @@ packages:
'@fastify/multipart@9.0.1':
resolution: {integrity: sha512-vt2gOCw/O4EwpN4KlLVJxth4iQlDf7T5ggw2Db2C+UbO2WJBG7y0jEBvu/HT6JIW/lBYaqrrUy9MmTpCKgXEpw==}
- '@fastify/reply-from@11.0.1':
- resolution: {integrity: sha512-F2Qk88gcqIIiug9V+4I6WeeV1faj1Wu798JyOnwbJcjQhm4LYrHdkpFSVwJE0g1cVjYCFFmH3OVh1HHaninttQ==}
+ '@fastify/reply-from@11.0.0':
+ resolution: {integrity: sha512-dv3o8hyy4sxhg1RN9l6ueM+PMMaIPKLjtL2T99H5M7h1Xt8d1RX3r+xC+sL5AqJqLvReX4N+7mTq9QDeB8i6Lg==}
'@fastify/send@3.1.1':
resolution: {integrity: sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==}
@@ -2639,9 +2656,10 @@ packages:
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
engines: {node: '>=18.18.0'}
- '@humanwhocodes/config-array@0.11.14':
- resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
+ '@humanwhocodes/config-array@0.13.0':
+ resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'}
+ deprecated: Use @eslint/config-array instead
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@@ -2653,6 +2671,7 @@ packages:
'@humanwhocodes/object-schema@2.0.3':
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
+ deprecated: Use @eslint/object-schema instead
'@humanwhocodes/retry@0.3.0':
resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
@@ -2881,6 +2900,10 @@ packages:
typescript:
optional: true
+ '@jridgewell/gen-mapping@0.3.2':
+ resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
+ engines: {node: '>=6.0.0'}
+
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@@ -2889,6 +2912,10 @@ packages:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
+ '@jridgewell/set-array@1.1.2':
+ resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
+ engines: {node: '>=6.0.0'}
+
'@jridgewell/set-array@1.2.1':
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'}
@@ -3192,8 +3219,8 @@ packages:
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
- '@opentelemetry/core@1.28.0':
- resolution: {integrity: sha512-ZLwRMV+fNDpVmF2WYUdBHlq0eOWtEaUJSusrzjGnBt7iSRvfjFE3RXYUZJrqou/wIDWV0DwQ5KIfYe9WXg9Xqw==}
+ '@opentelemetry/core@1.27.0':
+ resolution: {integrity: sha512-yQPKnK5e+76XuiqUH/gKyS8wv/7qITd5ln56QkBTf3uggr0VkXOXfcaAuG330UfdYu83wsyoBwqwxigpIG+Jkg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
@@ -3364,14 +3391,14 @@ packages:
resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==}
engines: {node: '>=14'}
- '@opentelemetry/resources@1.28.0':
- resolution: {integrity: sha512-cIyXSVJjGeTICENN40YSvLDAq4Y2502hGK3iN7tfdynQLKWb3XWZQEkPc+eSx47kiy11YeFAlYkEfXwR1w8kfw==}
+ '@opentelemetry/resources@1.27.0':
+ resolution: {integrity: sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==}
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.27.0':
+ resolution: {integrity: sha512-btz6XTQzwsyJjombpeqCX6LhiMQYpzt2pIYNPnw0IPO/3AhT6yjnf8Mnv3ZC2A4eRYOjqrg+bfaXg9XHDRJDWQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
@@ -3384,10 +3411,6 @@ packages:
resolution: {integrity: sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==}
engines: {node: '>=14'}
- '@opentelemetry/semantic-conventions@1.28.0':
- resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==}
- engines: {node: '>=14'}
-
'@opentelemetry/sql-common@0.40.1':
resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==}
engines: {node: '>=14'}
@@ -4294,8 +4317,8 @@ packages:
peerDependencies:
'@swc/core': '*'
- '@swc/types@0.1.17':
- resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==}
+ '@swc/types@0.1.15':
+ resolution: {integrity: sha512-XKaZ+dzDIQ9Ot9o89oJQ/aluI17+VvUnIpYJTcZtvv1iYX6MzHh3Ik2CSR7MdPKpPwfZXHBeCingb2b4PoDVdw==}
'@swc/wasm@1.2.130':
resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==}
@@ -4446,8 +4469,9 @@ packages:
'@types/fluent-ffmpeg@2.1.27':
resolution: {integrity: sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==}
- '@types/form-data@2.5.0':
- resolution: {integrity: sha512-23/wYiuckYYtFpL+4RPWiWmRQH2BjFuqCUi2+N3amB1a1Drv+i/byTrGvlLwRVLFNAZbwpbQ7JvTK+VCAPMbcg==}
+ '@types/form-data@2.5.2':
+ resolution: {integrity: sha512-tfmcyHn1Pp9YHAO5r40+UuZUPAZbUEgqTel3EuEKpmF9hPkXgR4l41853raliXnb4gwyPNoQOfvgGGlHN5WSog==}
+ 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==}
@@ -4476,9 +4500,6 @@ packages:
'@types/istanbul-reports@3.0.1':
resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==}
- '@types/jest@29.5.12':
- resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==}
-
'@types/jest@29.5.14':
resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==}
@@ -4554,9 +4575,6 @@ packages:
'@types/oauth2orize@1.11.5':
resolution: {integrity: sha512-C6hrRoh9hCnqis39OpeUZSwgw+TIzcV0CsxwJMGfQjTx4I1r+CLmuEPzoDJr5NRTfc7OMwHNLkQwrGFLKrJjMQ==}
- '@types/oauth@0.9.5':
- resolution: {integrity: sha512-+oQ3C2Zx6ambINOcdIARF5Z3Tu3x//HipE889/fqo3sgpQZbe9c6ExdQFtN6qlhpR7p83lTZfPJt0tCAW29dog==}
-
'@types/oauth@0.9.6':
resolution: {integrity: sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==}
@@ -4698,9 +4716,6 @@ packages:
'@types/web-push@3.6.4':
resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==}
- '@types/ws@8.5.11':
- resolution: {integrity: sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==}
-
'@types/ws@8.5.13':
resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==}
@@ -4911,8 +4926,8 @@ packages:
'@vitest/pretty-format@2.0.5':
resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==}
- '@vitest/pretty-format@2.1.2':
- resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==}
+ '@vitest/pretty-format@2.1.1':
+ resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==}
'@vitest/runner@1.6.0':
resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==}
@@ -4932,8 +4947,8 @@ packages:
'@vitest/utils@2.0.5':
resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==}
- '@vitest/utils@2.1.2':
- resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==}
+ '@vitest/utils@2.1.1':
+ resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==}
'@volar/language-core@2.2.0':
resolution: {integrity: sha512-a8WG9+4OdeNDW4ywABZIM6S6UN7em8uIlM/BZ2pWQUYrVmX+m8sj/X+QadvO+Li/t/LjAqbWJQtVgxdpEWLALQ==}
@@ -5073,9 +5088,9 @@ packages:
resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==}
engines: {node: '>=18'}
- aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9:
- resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9}
- version: 0.1.11
+ aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7:
+ resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7}
+ version: 0.1.15
engines: {vscode: ^1.83.0}
ajv-draft-04@1.0.0:
@@ -5106,8 +5121,8 @@ 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@0.2.1:
+ resolution: {integrity: sha512-FlEQrDJe9r2RI4cDlnK2zYqJezvx1uJaWEuwxsnlFqnPwvJbgitNBRumWrLDv8lA+7cCikpMxfJD2TTHiaTklA==}
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
@@ -5172,8 +5187,8 @@ packages:
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
- argon2@0.40.1:
- resolution: {integrity: sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==}
+ argon2@0.40.3:
+ resolution: {integrity: sha512-FrSmz4VeM91jwFvvjsQv9GYp6o/kARWoYKjbjDB2U5io1H3e5X67PYGclFDeQff6UXIhUd4aHR3mxCdBbMMuQw==}
engines: {node: '>=16.17.0'}
argparse@1.0.10:
@@ -5870,8 +5885,8 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
- cookie@1.0.2:
- resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
+ cookie@1.0.1:
+ resolution: {integrity: sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==}
engines: {node: '>=18'}
core-util-is@1.0.2:
@@ -6431,6 +6446,27 @@ packages:
eslint-import-resolver-node@0.3.9:
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+ eslint-module-utils@2.11.0:
+ resolution: {integrity: sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+
eslint-module-utils@2.12.0:
resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==}
engines: {node: '>=4'}
@@ -6497,9 +6533,10 @@ packages:
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- eslint@8.57.0:
- resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
+ eslint@8.57.1:
+ resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
eslint@9.14.0:
@@ -6525,10 +6562,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'}
@@ -6697,8 +6730,8 @@ packages:
fastify-plugin@4.5.1:
resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==}
- fastify-plugin@5.0.1:
- resolution: {integrity: sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==}
+ fastify-plugin@5.0.0:
+ resolution: {integrity: sha512-0725fmH/yYi8ugsjszLci+lLnGBK6cG+WSxM7edY2OXJEU7gr2JiGBoieL2h9mhTych1vFsEfXsAsGGDJ/Rd5w==}
fastify-raw-body@5.0.0:
resolution: {integrity: sha512-2qfoaQ3BQDhZ1gtbkKZd6n0kKxJISJGM6u/skD9ljdWItAscjXrtZ1lnjr7PavmXX9j4EyCPmBDiIsLn07d5vA==}
@@ -6767,8 +6800,8 @@ packages:
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
engines: {node: '>= 0.8'}
- find-my-way@9.1.0:
- resolution: {integrity: sha512-Y5jIsuYR4BwWDYYQ2A/RWWE6gD8a0FMgtU+HOq1WKku+Cwdz8M1v8wcAmRXXM1/iqtoqg06v+LjAxMYbCjViMw==}
+ find-my-way@9.0.1:
+ resolution: {integrity: sha512-/5NN/R0pFWuff16TMajeKt2JyiW+/OE8nOO8vo1DwZTxLaIURb7lcBYPIgRPh61yCNh9l8voeKwcrkUzmB00vw==}
engines: {node: '>=14'}
find-package-json@1.2.0:
@@ -6841,10 +6874,6 @@ packages:
resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
engines: {node: '>= 18'}
- form-data@4.0.0:
- resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
- engines: {node: '>= 6'}
-
form-data@4.0.1:
resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
engines: {node: '>= 6'}
@@ -6991,10 +7020,12 @@ packages:
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Glob versions prior to v9 are no longer supported
glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
+ deprecated: Glob versions prior to v9 are no longer supported
global-dirs@3.0.1:
resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
@@ -7252,6 +7283,10 @@ packages:
resolution: {integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ ignore@5.2.4:
+ resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
+ engines: {node: '>= 4'}
+
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
@@ -7289,6 +7324,7 @@ packages:
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -7940,8 +7976,8 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
- light-my-request@6.1.0:
- resolution: {integrity: sha512-+NFuhlOGoEwxeQfJ/pobkVFxcnKyDtiX847hLjuB/IzBxIl3q4VJeFI8uRCgb3AlTWL1lgOr+u5+8QdUcr33ng==}
+ light-my-request@6.0.0:
+ resolution: {integrity: sha512-kFkFXrmKCL0EEeOmJybMH5amWFd+AFvlvMlvFTRxCUwbhfapZqDmeLMPoWihntnYY6JpoQDE9k+vOzObF1fDqg==}
lilconfig@3.1.1:
resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
@@ -8013,8 +8049,8 @@ packages:
loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
- loupe@3.1.2:
- resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==}
+ loupe@3.1.1:
+ resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
lowercase-keys@2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
@@ -8024,6 +8060,10 @@ packages:
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ lru-cache@10.0.2:
+ resolution: {integrity: sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==}
+ engines: {node: 14 || >=16.14}
+
lru-cache@10.2.2:
resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
engines: {node: 14 || >=16.14}
@@ -8391,10 +8431,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'}
@@ -8539,9 +8575,9 @@ packages:
node-addon-api@3.2.1:
resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
- node-addon-api@7.1.0:
- resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==}
- engines: {node: ^16 || ^18 || >= 20}
+ node-addon-api@8.3.0:
+ resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==}
+ engines: {node: ^18 || ^20 || >= 21}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
@@ -8568,8 +8604,8 @@ packages:
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
hasBin: true
- node-gyp-build@4.8.1:
- resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
+ node-gyp-build@4.8.4:
+ resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
node-gyp@10.2.0:
@@ -8637,10 +8673,6 @@ packages:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
- npm-run-path@5.1.0:
- resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
npm-run-path@5.3.0:
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -9208,10 +9240,6 @@ packages:
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'}
@@ -9367,8 +9395,8 @@ packages:
pseudomap@1.0.2:
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
- psl@1.13.0:
- resolution: {integrity: sha512-BFwmFXiJoFqlUpZ5Qssolv15DMyc84gTBds1BjsV1BfXEo1UyyD7GsmN67n7J77uRhoSNW1AXtXKPLcBFQn9Aw==}
+ psl@1.15.0:
+ resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
@@ -9415,6 +9443,10 @@ packages:
pump@3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
+ punycode.js@2.3.1:
+ resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+ engines: {node: '>=6'}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -9577,15 +9609,15 @@ packages:
regenerator-runtime@0.14.0:
resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
- regex@4.4.0:
- resolution: {integrity: sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==}
+ regex@4.3.3:
+ resolution: {integrity: sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==}
regexp.prototype.flags@1.5.0:
resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==}
engines: {node: '>= 0.4'}
- regexp.prototype.flags@1.5.3:
- resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==}
+ regexp.prototype.flags@1.5.2:
+ resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
engines: {node: '>= 0.4'}
remark-gfm@4.0.0:
@@ -9675,6 +9707,7 @@ packages:
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rollup@4.26.0:
@@ -10375,11 +10408,11 @@ packages:
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
- tldts-core@6.1.63:
- resolution: {integrity: sha512-H1XCt54xY+QPbwhTgmxLkepX0MVHu3USfMmejiCOdkMbRcP22Pn2FVF127r/GWXVDmXTRezyF3Ckvhn4Fs6j7Q==}
+ tldts-core@6.1.61:
+ resolution: {integrity: sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==}
- tldts@6.1.63:
- resolution: {integrity: sha512-YWwhsjyn9sB/1rOkSRYxvkN/wl5LFM1QDv6F2pVR+pb/jFne4EOBxHfkKVWvDIBEAw9iGOwwubHtQTm0WRT5sQ==}
+ tldts@6.1.61:
+ resolution: {integrity: sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==}
hasBin: true
tmp@0.2.3:
@@ -10459,6 +10492,12 @@ packages:
trough@2.2.0:
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+ ts-api-utils@1.0.1:
+ resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==}
+ engines: {node: '>=16.13.0'}
+ peerDependencies:
+ typescript: '>=4.2.0'
+
ts-api-utils@1.3.0:
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
engines: {node: '>=16'}
@@ -10472,12 +10511,13 @@ packages:
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
engines: {node: '>=6.10'}
- ts-jest@29.1.2:
- resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==}
- engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0}
+ ts-jest@29.2.5:
+ resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@babel/core': '>=7.0.0-beta.0 <8'
+ '@jest/transform': ^29.0.0
'@jest/types': ^29.0.0
babel-jest: ^29.0.0
esbuild: '*'
@@ -10486,6 +10526,8 @@ packages:
peerDependenciesMeta:
'@babel/core':
optional: true
+ '@jest/transform':
+ optional: true
'@jest/types':
optional: true
babel-jest:
@@ -10564,8 +10606,8 @@ packages:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
- type-fest@4.27.0:
- resolution: {integrity: sha512-3IMSWgP7C5KSQqmo1wjhKrwsvXAtF33jO3QY+Uy++ia7hqvgSK6iXbbg5PbDBc1P2ZbNEDgejOrN4YooXvhwCw==}
+ type-fest@4.26.1:
+ resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==}
engines: {node: '>=16'}
type-is@1.6.18:
@@ -10717,8 +10759,8 @@ packages:
resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==}
engines: {node: '>=14.0'}
- undici@6.20.0:
- resolution: {integrity: sha512-AITZfPuxubm31Sx0vr8bteSalEbs9wQb/BOBi9FPlD9Qpd6HxZ4Q0+hI742jBhkPb4RT2v5MQzaW5VhRVyj+9A==}
+ undici@6.19.8:
+ resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==}
engines: {node: '>=18.17'}
unified@11.0.4:
@@ -11795,7 +11837,7 @@ snapshots:
'@babel/code-frame@7.23.5':
dependencies:
- '@babel/highlight': 7.24.7
+ '@babel/highlight': 7.23.4
chalk: 2.4.2
'@babel/code-frame@7.24.7':
@@ -11811,7 +11853,7 @@ snapshots:
dependencies:
'@ampproject/remapping': 2.2.1
'@babel/code-frame': 7.23.5
- '@babel/generator': 7.24.7
+ '@babel/generator': 7.23.5
'@babel/helper-compilation-targets': 7.22.15
'@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.5)
'@babel/helpers': 7.23.5
@@ -11835,10 +11877,10 @@ snapshots:
'@babel/helper-compilation-targets': 7.24.7
'@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7)
'@babel/helpers': 7.24.7
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.24.7
'@babel/template': 7.24.7
'@babel/traverse': 7.24.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.24.7
convert-source-map: 2.0.0
debug: 4.3.7(supports-color@8.1.1)
gensync: 1.0.0-beta.2
@@ -11847,9 +11889,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/generator@7.24.7':
+ '@babel/generator@7.23.5':
dependencies:
'@babel/types': 7.24.7
+ '@jridgewell/gen-mapping': 0.3.2
+ '@jridgewell/trace-mapping': 0.3.18
+ jsesc: 2.5.2
+
+ '@babel/generator@7.24.7':
+ dependencies:
+ '@babel/types': 7.25.6
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
jsesc: 2.5.2
@@ -11870,37 +11919,48 @@ snapshots:
lru-cache: 5.1.1
semver: 6.3.1
+ '@babel/helper-environment-visitor@7.22.20': {}
+
'@babel/helper-environment-visitor@7.24.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
+
+ '@babel/helper-function-name@7.23.0':
+ dependencies:
+ '@babel/template': 7.24.7
+ '@babel/types': 7.25.6
'@babel/helper-function-name@7.24.7':
dependencies:
'@babel/template': 7.24.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
+
+ '@babel/helper-hoist-variables@7.22.5':
+ dependencies:
+ '@babel/types': 7.25.6
'@babel/helper-hoist-variables@7.24.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
'@babel/helper-module-imports@7.22.15':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.24.7
'@babel/helper-module-imports@7.24.7':
dependencies:
'@babel/traverse': 7.24.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
transitivePeerDependencies:
- supports-color
'@babel/helper-module-transforms@7.23.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
- '@babel/helper-environment-visitor': 7.24.7
+ '@babel/helper-environment-visitor': 7.22.20
'@babel/helper-module-imports': 7.22.15
'@babel/helper-simple-access': 7.22.5
- '@babel/helper-split-export-declaration': 7.24.7
+ '@babel/helper-split-export-declaration': 7.22.6
'@babel/helper-validator-identifier': 7.24.7
'@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)':
@@ -11910,7 +11970,7 @@ snapshots:
'@babel/helper-module-imports': 7.24.7
'@babel/helper-simple-access': 7.24.7
'@babel/helper-split-export-declaration': 7.24.7
- '@babel/helper-validator-identifier': 7.25.7
+ '@babel/helper-validator-identifier': 7.24.7
transitivePeerDependencies:
- supports-color
@@ -11918,27 +11978,29 @@ snapshots:
'@babel/helper-simple-access@7.22.5':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.24.7
'@babel/helper-simple-access@7.24.7':
dependencies:
'@babel/traverse': 7.24.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
transitivePeerDependencies:
- supports-color
+ '@babel/helper-split-export-declaration@7.22.6':
+ dependencies:
+ '@babel/types': 7.24.7
+
'@babel/helper-split-export-declaration@7.24.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
'@babel/helper-string-parser@7.24.7': {}
- '@babel/helper-string-parser@7.25.7': {}
+ '@babel/helper-string-parser@7.24.8': {}
'@babel/helper-validator-identifier@7.24.7': {}
- '@babel/helper-validator-identifier@7.25.7': {}
-
'@babel/helper-validator-option@7.23.5': {}
'@babel/helper-validator-option@7.24.7': {}
@@ -11946,15 +12008,21 @@ snapshots:
'@babel/helpers@7.23.5':
dependencies:
'@babel/template': 7.22.15
- '@babel/traverse': 7.24.7
- '@babel/types': 7.25.7
+ '@babel/traverse': 7.23.5
+ '@babel/types': 7.24.7
transitivePeerDependencies:
- supports-color
'@babel/helpers@7.24.7':
dependencies:
'@babel/template': 7.24.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
+
+ '@babel/highlight@7.23.4':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.24.7
+ chalk: 2.4.2
+ js-tokens: 4.0.0
'@babel/highlight@7.24.7':
dependencies:
@@ -11967,35 +12035,65 @@ snapshots:
dependencies:
'@babel/types': 7.24.7
- '@babel/parser@7.25.7':
+ '@babel/parser@7.25.6':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
@@ -12006,36 +12104,78 @@ snapshots:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
'@babel/helper-plugin-utils': 7.22.5
+ '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)':
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/helper-plugin-utils': 7.22.5
+ optional: true
+
'@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5)':
dependencies:
'@babel/core': 7.23.5
@@ -12048,31 +12188,31 @@ snapshots:
'@babel/template@7.22.15':
dependencies:
'@babel/code-frame': 7.24.7
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.6
+ '@babel/types': 7.24.7
'@babel/template@7.24.0':
dependencies:
'@babel/code-frame': 7.24.7
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.6
+ '@babel/types': 7.25.6
'@babel/template@7.24.7':
dependencies:
'@babel/code-frame': 7.24.7
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.6
+ '@babel/types': 7.25.6
'@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
+ '@babel/generator': 7.23.5
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-function-name': 7.23.0
+ '@babel/helper-hoist-variables': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/parser': 7.25.6
+ '@babel/types': 7.24.7
debug: 4.3.7(supports-color@8.1.1)
globals: 11.12.0
transitivePeerDependencies:
@@ -12086,8 +12226,8 @@ snapshots:
'@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
+ '@babel/parser': 7.25.6
+ '@babel/types': 7.25.6
debug: 4.3.7(supports-color@8.1.1)
globals: 11.12.0
transitivePeerDependencies:
@@ -12099,10 +12239,10 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
to-fast-properties: 2.0.0
- '@babel/types@7.25.7':
+ '@babel/types@7.25.6':
dependencies:
- '@babel/helper-string-parser': 7.25.7
- '@babel/helper-validator-identifier': 7.25.7
+ '@babel/helper-string-parser': 7.24.8
+ '@babel/helper-validator-identifier': 7.24.7
to-fast-properties: 2.0.0
'@bcoe/v8-coverage@0.2.3': {}
@@ -12253,7 +12393,7 @@ snapshots:
jsonfile: 5.0.0
universalify: 0.1.2
- '@emnapi/runtime@1.3.0':
+ '@emnapi/runtime@1.2.0':
dependencies:
tslib: 2.7.0
optional: true
@@ -12465,9 +12605,9 @@ snapshots:
'@esbuild/win32-x64@0.24.0':
optional: true
- '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)':
+ '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)':
dependencies:
- eslint: 8.57.0
+ eslint: 8.57.1
eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.4.0(eslint@9.14.0)':
@@ -12496,7 +12636,7 @@ snapshots:
'@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
@@ -12521,13 +12661,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@eslint/js@8.57.0': {}
+ '@eslint/js@8.57.1': {}
'@eslint/js@9.14.0': {}
'@eslint/object-schema@2.1.4': {}
- '@eslint/plugin-kit@0.2.3':
+ '@eslint/plugin-kit@0.2.0':
dependencies:
levn: 0.4.1
@@ -12536,9 +12676,9 @@ snapshots:
'@fastify/accepts@5.0.1':
dependencies:
accepts: 1.3.8
- fastify-plugin: 5.0.1
+ fastify-plugin: 5.0.0
- '@fastify/ajv-compiler@4.0.1':
+ '@fastify/ajv-compiler@4.0.0':
dependencies:
ajv: 8.17.1
ajv-formats: 3.0.1(ajv@8.17.1)
@@ -12554,12 +12694,12 @@ snapshots:
'@fastify/cookie@11.0.1':
dependencies:
- cookie: 1.0.2
- fastify-plugin: 5.0.1
+ cookie: 1.0.1
+ fastify-plugin: 5.0.0
'@fastify/cors@10.0.1':
dependencies:
- fastify-plugin: 5.0.1
+ fastify-plugin: 5.0.0
mnemonist: 0.39.8
'@fastify/deepmerge@2.0.0': {}
@@ -12569,19 +12709,19 @@ snapshots:
'@fastify/express@4.0.1':
dependencies:
express: 4.21.0
- fastify-plugin: 5.0.1
+ fastify-plugin: 5.0.0
transitivePeerDependencies:
- supports-color
- '@fastify/fast-json-stringify-compiler@5.0.1':
+ '@fastify/fast-json-stringify-compiler@5.0.0':
dependencies:
fast-json-stringify: 6.0.0
'@fastify/http-proxy@10.0.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)':
dependencies:
- '@fastify/reply-from': 11.0.1
+ '@fastify/reply-from': 11.0.0
fast-querystring: 1.1.2
- fastify-plugin: 5.0.1
+ fastify-plugin: 5.0.0
ws: 8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
transitivePeerDependencies:
- bufferutil
@@ -12596,10 +12736,10 @@ snapshots:
'@fastify/busboy': 3.0.0
'@fastify/deepmerge': 2.0.0
'@fastify/error': 4.0.0
- fastify-plugin: 5.0.1
+ fastify-plugin: 5.0.0
secure-json-parse: 3.0.0
- '@fastify/reply-from@11.0.1':
+ '@fastify/reply-from@11.0.0':
dependencies:
'@fastify/error': 4.0.0
end-of-stream: 1.4.4
@@ -12607,7 +12747,7 @@ snapshots:
fast-querystring: 1.1.2
fastify-plugin: 4.5.1
toad-cache: 3.7.0
- undici: 6.20.0
+ undici: 6.19.8
'@fastify/send@3.1.1':
dependencies:
@@ -12622,13 +12762,13 @@ snapshots:
'@fastify/accept-negotiator': 2.0.0
'@fastify/send': 3.1.1
content-disposition: 0.5.4
- fastify-plugin: 5.0.1
+ fastify-plugin: 5.0.0
fastq: 1.17.1
glob: 11.0.0
'@fastify/view@10.0.1':
dependencies:
- fastify-plugin: 5.0.1
+ fastify-plugin: 5.0.0
toad-cache: 3.7.0
'@github/webauthn-json@2.1.1': {}
@@ -12662,10 +12802,10 @@ snapshots:
'@humanfs/core': 0.19.1
'@humanwhocodes/retry': 0.3.0
- '@humanwhocodes/config-array@0.11.14':
+ '@humanwhocodes/config-array@0.13.0':
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
@@ -12746,7 +12886,7 @@ snapshots:
'@img/sharp-wasm32@0.33.5':
dependencies:
- '@emnapi/runtime': 1.3.0
+ '@emnapi/runtime': 1.2.0
optional: true
'@img/sharp-win32-ia32@0.33.5':
@@ -12893,7 +13033,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
- '@jridgewell/trace-mapping': 0.3.18
+ '@jridgewell/trace-mapping': 0.3.25
'@types/node': 22.9.0
chalk: 4.1.2
collect-v8-coverage: 1.0.1
@@ -12943,7 +13083,7 @@ snapshots:
dependencies:
'@babel/core': 7.24.7
'@jest/types': 29.6.3
- '@jridgewell/trace-mapping': 0.3.18
+ '@jridgewell/trace-mapping': 0.3.25
babel-plugin-istanbul: 6.1.1
chalk: 4.1.2
convert-source-map: 2.0.0
@@ -12978,6 +13118,12 @@ snapshots:
optionalDependencies:
typescript: 5.6.3
+ '@jridgewell/gen-mapping@0.3.2':
+ dependencies:
+ '@jridgewell/set-array': 1.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/trace-mapping': 0.3.18
+
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1
@@ -12986,6 +13132,8 @@ snapshots:
'@jridgewell/resolve-uri@3.1.0': {}
+ '@jridgewell/set-array@1.1.2': {}
+
'@jridgewell/set-array@1.2.1': {}
'@jridgewell/source-map@0.3.6':
@@ -13290,7 +13438,7 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.27.0
- '@opentelemetry/core@1.28.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.27.0
@@ -13300,7 +13448,7 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13309,7 +13457,7 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
'@types/connect': 3.4.36
transitivePeerDependencies:
- supports-color
@@ -13326,7 +13474,7 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13335,7 +13483,7 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13366,7 +13514,7 @@ snapshots:
'@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/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13385,7 +13533,7 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
'@opentelemetry/redis-common': 0.36.2
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13393,7 +13541,7 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13401,7 +13549,7 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13410,7 +13558,7 @@ snapshots:
'@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/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13425,7 +13573,7 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13434,7 +13582,7 @@ snapshots:
'@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/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13442,7 +13590,7 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
'@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
@@ -13451,7 +13599,7 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
'@types/mysql': 2.15.26
transitivePeerDependencies:
- supports-color
@@ -13460,7 +13608,7 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13468,7 +13616,7 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.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
@@ -13480,7 +13628,7 @@ snapshots:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
'@opentelemetry/redis-common': 0.36.2
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
transitivePeerDependencies:
- supports-color
@@ -13488,7 +13636,7 @@ snapshots:
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/semantic-conventions': 1.27.0
'@types/tedious': 4.0.14
transitivePeerDependencies:
- supports-color
@@ -13539,25 +13687,23 @@ snapshots:
'@opentelemetry/redis-common@0.36.2': {}
- '@opentelemetry/resources@1.28.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/resources@1.27.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
- '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.27.0
- '@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0)':
+ '@opentelemetry/sdk-trace-base@1.27.0(@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/core': 1.27.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.27.0
'@opentelemetry/semantic-conventions@1.25.1': {}
'@opentelemetry/semantic-conventions@1.27.0': {}
- '@opentelemetry/semantic-conventions@1.28.0': {}
-
'@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
@@ -13614,7 +13760,7 @@ snapshots:
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/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0)
transitivePeerDependencies:
- supports-color
@@ -13800,25 +13946,25 @@ snapshots:
'@opentelemetry/instrumentation-redis-4': 0.42.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation-tedious': 0.15.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation-undici': 0.6.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.28.0
+ '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.27.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/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.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.0)
'@sentry/types': 8.38.0
'@sentry/utils': 8.38.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.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.27.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.27.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/semantic-conventions': 1.28.0
+ '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/semantic-conventions': 1.27.0
'@sentry/core': 8.38.0
'@sentry/types': 8.38.0
'@sentry/utils': 8.38.0
@@ -14468,7 +14614,7 @@ snapshots:
'@storybook/instrumenter@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))':
dependencies:
'@storybook/global': 5.0.0
- '@vitest/utils': 2.1.2
+ '@vitest/utils': 2.1.1
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))':
@@ -14570,7 +14716,7 @@ snapshots:
'@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
+ '@vue/compiler-core': 3.5.12
storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)
ts-dedent: 2.2.0
type-fest: 2.19.0
@@ -14665,7 +14811,7 @@ snapshots:
'@swc/core@1.9.2':
dependencies:
'@swc/counter': 0.1.3
- '@swc/types': 0.1.17
+ '@swc/types': 0.1.15
optionalDependencies:
'@swc/core-darwin-arm64': 1.9.2
'@swc/core-darwin-x64': 1.9.2
@@ -14687,7 +14833,7 @@ snapshots:
'@swc/counter': 0.1.3
jsonc-parser: 3.2.0
- '@swc/types@0.1.17':
+ '@swc/types@0.1.15':
dependencies:
'@swc/counter': 0.1.3
@@ -14785,24 +14931,24 @@ snapshots:
'@types/babel__core@7.20.0':
dependencies:
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.6
+ '@babel/types': 7.25.6
'@types/babel__generator': 7.6.4
'@types/babel__template': 7.4.1
'@types/babel__traverse': 7.20.0
'@types/babel__generator@7.6.4':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
'@types/babel__template@7.4.1':
dependencies:
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.6
+ '@babel/types': 7.25.6
'@types/babel__traverse@7.20.0':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
'@types/bcryptjs@2.4.6': {}
@@ -14874,7 +15020,7 @@ snapshots:
dependencies:
'@types/node': 22.9.0
- '@types/form-data@2.5.0':
+ '@types/form-data@2.5.2':
dependencies:
form-data: 4.0.1
@@ -14909,11 +15055,6 @@ snapshots:
dependencies:
'@types/istanbul-lib-report': 3.0.0
- '@types/jest@29.5.12':
- dependencies:
- expect: 29.7.0
- pretty-format: 29.7.0
-
'@types/jest@29.5.14':
dependencies:
expect: 29.7.0
@@ -14988,10 +15129,6 @@ snapshots:
'@types/express': 4.17.17
'@types/node': 22.9.0
- '@types/oauth@0.9.5':
- dependencies:
- '@types/node': 22.9.0
-
'@types/oauth@0.9.6':
dependencies:
'@types/node': 22.9.0
@@ -15125,10 +15262,6 @@ snapshots:
dependencies:
'@types/node': 22.9.0
- '@types/ws@8.5.11':
- dependencies:
- '@types/node': 22.9.0
-
'@types/ws@8.5.13':
dependencies:
'@types/node': 22.9.0
@@ -15144,20 +15277,20 @@ snapshots:
'@types/node': 22.9.0
optional: true
- '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6)':
+ '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.1.6))(eslint@8.57.1)(typescript@5.1.6)':
dependencies:
- '@eslint-community/regexpp': 4.11.0
- '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.1.6)
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.1.6)
'@typescript-eslint/scope-manager': 6.21.0
- '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6)
- '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6)
+ '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.1.6)
+ '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.1.6)
'@typescript-eslint/visitor-keys': 6.21.0
- debug: 4.3.5(supports-color@5.5.0)
- eslint: 8.57.0
+ debug: 4.3.7(supports-color@8.1.1)
+ eslint: 8.57.1
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
- semver: 7.6.0
+ semver: 7.6.3
ts-api-utils: 1.3.0(typescript@5.1.6)
optionalDependencies:
typescript: 5.1.6
@@ -15175,10 +15308,10 @@ snapshots:
debug: 4.3.4
eslint: 9.14.0
graphemer: 1.4.0
- ignore: 5.3.1
+ ignore: 5.2.4
natural-compare: 1.4.0
semver: 7.6.0
- ts-api-utils: 1.3.0(typescript@5.6.3)
+ ts-api-utils: 1.0.1(typescript@5.6.3)
optionalDependencies:
typescript: 5.6.3
transitivePeerDependencies:
@@ -15202,14 +15335,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6)':
+ '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.1.6)':
dependencies:
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6)
'@typescript-eslint/visitor-keys': 6.21.0
- debug: 4.3.5(supports-color@5.5.0)
- eslint: 8.57.0
+ debug: 4.3.7(supports-color@8.1.1)
+ eslint: 8.57.1
optionalDependencies:
typescript: 5.1.6
transitivePeerDependencies:
@@ -15234,7 +15367,7 @@ snapshots:
'@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)
+ debug: 4.3.5
eslint: 9.14.0
optionalDependencies:
typescript: 5.6.3
@@ -15256,12 +15389,12 @@ snapshots:
'@typescript-eslint/types': 7.17.0
'@typescript-eslint/visitor-keys': 7.17.0
- '@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.1.6)':
+ '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.1.6)':
dependencies:
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6)
- '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6)
- debug: 4.3.5(supports-color@5.5.0)
- eslint: 8.57.0
+ '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.1.6)
+ debug: 4.3.7(supports-color@8.1.1)
+ eslint: 8.57.1
ts-api-utils: 1.3.0(typescript@5.1.6)
optionalDependencies:
typescript: 5.1.6
@@ -15272,9 +15405,9 @@ snapshots:
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
+ debug: 4.3.5
eslint: 9.14.0
- ts-api-utils: 1.3.0(typescript@5.6.3)
+ ts-api-utils: 1.0.1(typescript@5.6.3)
optionalDependencies:
typescript: 5.6.3
transitivePeerDependencies:
@@ -15284,7 +15417,7 @@ snapshots:
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)
+ debug: 4.3.5
eslint: 9.14.0
ts-api-utils: 1.3.0(typescript@5.6.3)
optionalDependencies:
@@ -15302,11 +15435,11 @@ snapshots:
dependencies:
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.7(supports-color@8.1.1)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
- semver: 7.6.0
+ semver: 7.6.3
ts-api-utils: 1.3.0(typescript@5.1.6)
optionalDependencies:
typescript: 5.1.6
@@ -15317,12 +15450,12 @@ snapshots:
dependencies:
'@typescript-eslint/types': 7.1.0
'@typescript-eslint/visitor-keys': 7.1.0
- debug: 4.3.4
+ debug: 4.3.5
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
semver: 7.6.0
- ts-api-utils: 1.3.0(typescript@5.6.3)
+ ts-api-utils: 1.0.1(typescript@5.6.3)
optionalDependencies:
typescript: 5.6.3
transitivePeerDependencies:
@@ -15332,7 +15465,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 7.17.0
'@typescript-eslint/visitor-keys': 7.17.0
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.5
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.4
@@ -15343,16 +15476,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@6.21.0(eslint@8.57.0)(typescript@5.1.6)':
+ '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.1.6)':
dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6)
- eslint: 8.57.0
- semver: 7.6.0
+ eslint: 8.57.1
+ semver: 7.6.3
transitivePeerDependencies:
- supports-color
- typescript
@@ -15413,7 +15546,7 @@ snapshots:
dependencies:
'@ampproject/remapping': 2.2.1
'@bcoe/v8-coverage': 0.2.3
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.5
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.4
@@ -15432,7 +15565,7 @@ snapshots:
dependencies:
'@ampproject/remapping': 2.2.1
'@bcoe/v8-coverage': 0.2.3
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.5
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.4
@@ -15464,7 +15597,7 @@ snapshots:
dependencies:
tinyrainbow: 1.2.0
- '@vitest/pretty-format@2.1.2':
+ '@vitest/pretty-format@2.1.1':
dependencies:
tinyrainbow: 1.2.0
@@ -15499,13 +15632,13 @@ snapshots:
dependencies:
'@vitest/pretty-format': 2.0.5
estree-walker: 3.0.3
- loupe: 3.1.2
+ loupe: 3.1.1
tinyrainbow: 1.2.0
- '@vitest/utils@2.1.2':
+ '@vitest/utils@2.1.1':
dependencies:
- '@vitest/pretty-format': 2.1.2
- loupe: 3.1.2
+ '@vitest/pretty-format': 2.1.1
+ loupe: 3.1.1
tinyrainbow: 1.2.0
'@volar/language-core@2.2.0':
@@ -15535,7 +15668,7 @@ snapshots:
'@vue/compiler-core@3.5.11':
dependencies:
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.6
'@vue/shared': 3.5.11
entities: 4.5.0
estree-walker: 2.0.2
@@ -15543,7 +15676,7 @@ snapshots:
'@vue/compiler-core@3.5.12':
dependencies:
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.6
'@vue/shared': 3.5.12
entities: 4.5.0
estree-walker: 2.0.2
@@ -15561,7 +15694,7 @@ snapshots:
'@vue/compiler-sfc@3.5.12':
dependencies:
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.6
'@vue/compiler-core': 3.5.12
'@vue/compiler-dom': 3.5.12
'@vue/compiler-ssr': 3.5.12
@@ -15584,8 +15717,8 @@ snapshots:
'@vue/language-core@2.0.16(typescript@5.6.3)':
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
@@ -15599,7 +15732,7 @@ snapshots:
'@vue/compiler-dom': 3.5.11
'@vue/compiler-vue2': 2.7.16
'@vue/shared': 3.5.11
- alien-signals: 0.2.2
+ alien-signals: 0.2.1
minimatch: 9.0.4
muggle-string: 0.4.1
path-browserify: 1.0.1
@@ -15671,7 +15804,7 @@ snapshots:
agent-base@7.1.0:
dependencies:
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.5
transitivePeerDependencies:
- supports-color
@@ -15685,7 +15818,7 @@ snapshots:
clean-stack: 5.2.0
indent-string: 5.0.0
- aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9:
+ aiscript-vscode@https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/c3cde89e79a41d93540cf8a48cd619c3f2dcb1b7:
dependencies:
'@aiscript-dev/aiscript-languageserver': https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz
vscode-languageclient: 9.0.1
@@ -15734,7 +15867,7 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
- alien-signals@0.2.2: {}
+ alien-signals@0.2.1: {}
ansi-colors@4.1.3: {}
@@ -15795,11 +15928,11 @@ snapshots:
arg@5.0.2: {}
- argon2@0.40.1:
+ argon2@0.40.3:
dependencies:
'@phc/format': 1.0.0
- node-addon-api: 7.1.0
- node-gyp-build: 4.8.1
+ node-addon-api: 8.3.0
+ node-gyp-build: 4.8.4
argparse@1.0.10:
dependencies:
@@ -15817,7 +15950,7 @@ snapshots:
array-buffer-byte-length@1.0.0:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
is-array-buffer: 3.0.2
array-buffer-byte-length@1.0.1:
@@ -15849,14 +15982,14 @@ snapshots:
array.prototype.flat@1.3.2:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
define-properties: 1.2.0
es-abstract: 1.22.1
es-shim-unscopables: 1.0.0
array.prototype.flatmap@1.3.2:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
define-properties: 1.2.0
es-abstract: 1.22.1
es-shim-unscopables: 1.0.0
@@ -15864,9 +15997,9 @@ snapshots:
arraybuffer.prototype.slice@1.0.1:
dependencies:
array-buffer-byte-length: 1.0.0
- call-bind: 1.0.2
+ call-bind: 1.0.7
define-properties: 1.2.0
- get-intrinsic: 1.2.1
+ get-intrinsic: 1.2.4
is-array-buffer: 3.0.2
is-shared-array-buffer: 1.0.2
@@ -15994,6 +16127,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ babel-jest@29.7.0(@babel/core@7.24.7):
+ dependencies:
+ '@babel/core': 7.24.7
+ '@jest/transform': 29.7.0
+ '@types/babel__core': 7.20.0
+ babel-plugin-istanbul: 6.1.1
+ babel-preset-jest: 29.6.3(@babel/core@7.24.7)
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
babel-plugin-istanbul@6.1.1:
dependencies:
'@babel/helper-plugin-utils': 7.22.5
@@ -16007,7 +16154,7 @@ snapshots:
babel-plugin-jest-hoist@29.6.3:
dependencies:
'@babel/template': 7.24.0
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
'@types/babel__core': 7.20.0
'@types/babel__traverse': 7.20.0
@@ -16027,12 +16174,36 @@ snapshots:
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.5)
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.5)
+ babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.7):
+ dependencies:
+ '@babel/core': 7.24.7
+ '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7)
+ '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.7)
+ '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7)
+ '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7)
+ '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7)
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7)
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7)
+ '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7)
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7)
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7)
+ '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7)
+ '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7)
+ optional: true
+
babel-preset-jest@29.6.3(@babel/core@7.23.5):
dependencies:
'@babel/core': 7.23.5
babel-plugin-jest-hoist: 29.6.3
babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.5)
+ babel-preset-jest@29.6.3(@babel/core@7.24.7):
+ dependencies:
+ '@babel/core': 7.24.7
+ babel-plugin-jest-hoist: 29.6.3
+ babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7)
+ optional: true
+
babel-walk@3.0.0-canary-5:
dependencies:
'@babel/types': 7.24.7
@@ -16181,7 +16352,7 @@ snapshots:
bufferutil@4.0.8:
dependencies:
- node-gyp-build: 4.8.1
+ node-gyp-build: 4.6.0
optional: true
bullmq@5.26.1:
@@ -16324,7 +16495,7 @@ snapshots:
assertion-error: 2.0.1
check-error: 2.1.1
deep-eql: 5.0.2
- loupe: 3.1.2
+ loupe: 3.1.1
pathval: 2.0.0
chalk-template@1.1.0:
@@ -16411,7 +16582,7 @@ snapshots:
parse5: 7.2.1
parse5-htmlparser2-tree-adapter: 7.0.0
parse5-parser-stream: 7.1.2
- undici: 6.20.0
+ undici: 6.19.8
whatwg-mimetype: 4.0.0
cheerio@1.0.0-rc.12:
@@ -16619,7 +16790,7 @@ snapshots:
cookie@0.7.2: {}
- cookie@1.0.2: {}
+ cookie@1.0.1: {}
core-util-is@1.0.2: {}
@@ -16704,12 +16875,12 @@ snapshots:
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: {}
@@ -16869,9 +17040,13 @@ snapshots:
dependencies:
ms: 2.1.2
- debug@4.3.5(supports-color@5.5.0):
+ debug@4.3.5:
dependencies:
ms: 2.1.2
+
+ debug@4.3.7(supports-color@5.5.0):
+ dependencies:
+ ms: 2.1.3
optionalDependencies:
supports-color: 5.5.0
@@ -16968,7 +17143,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: {}
@@ -17141,16 +17316,16 @@ snapshots:
array-buffer-byte-length: 1.0.0
arraybuffer.prototype.slice: 1.0.1
available-typed-arrays: 1.0.5
- call-bind: 1.0.2
+ call-bind: 1.0.7
es-set-tostringtag: 2.0.1
es-to-primitive: 1.2.1
function.prototype.name: 1.1.5
- get-intrinsic: 1.2.1
+ get-intrinsic: 1.2.4
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-property-descriptors: 1.0.2
has-proto: 1.0.1
has-symbols: 1.0.3
internal-slot: 1.0.5
@@ -17162,7 +17337,7 @@ snapshots:
is-string: 1.0.7
is-typed-array: 1.1.10
is-weakref: 1.0.2
- object-inspect: 1.12.3
+ object-inspect: 1.13.2
object-keys: 1.1.1
object.assign: 4.1.4
regexp.prototype.flags: 1.5.0
@@ -17214,7 +17389,7 @@ snapshots:
object-inspect: 1.13.2
object-keys: 1.1.1
object.assign: 4.1.5
- regexp.prototype.flags: 1.5.3
+ regexp.prototype.flags: 1.5.2
safe-array-concat: 1.1.2
safe-regex-test: 1.0.3
string.prototype.trim: 1.2.9
@@ -17251,7 +17426,7 @@ snapshots:
es-set-tostringtag@2.0.1:
dependencies:
- get-intrinsic: 1.2.1
+ get-intrinsic: 1.2.4
has: 1.0.3
has-tostringtag: 1.0.0
@@ -17378,9 +17553,9 @@ snapshots:
escape-string-regexp@5.0.0: {}
- eslint-config-prettier@9.1.0(eslint@8.57.0):
+ eslint-config-prettier@9.1.0(eslint@8.57.1):
dependencies:
- eslint: 8.57.0
+ eslint: 8.57.1
eslint-formatter-pretty@4.1.0:
dependencies:
@@ -17401,6 +17576,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ eslint-module-utils@2.11.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@9.14.0):
+ dependencies:
+ debug: 3.2.7(supports-color@8.1.1)
+ optionalDependencies:
+ '@typescript-eslint/parser': 7.17.0(eslint@9.14.0)(typescript@5.6.3)
+ eslint: 9.14.0
+ eslint-import-resolver-node: 0.3.9
+ 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):
dependencies:
debug: 3.2.7(supports-color@8.1.1)
@@ -17422,7 +17607,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.14.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@9.14.0)
+ eslint-module-utils: 2.11.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@9.14.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -17498,20 +17683,20 @@ snapshots:
eslint-visitor-keys@4.2.0: {}
- eslint@8.57.0:
+ eslint@8.57.1:
dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
- '@eslint-community/regexpp': 4.11.0
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1)
+ '@eslint-community/regexpp': 4.12.1
'@eslint/eslintrc': 2.1.4
- '@eslint/js': 8.57.0
- '@humanwhocodes/config-array': 0.11.14
+ '@eslint/js': 8.57.1
+ '@humanwhocodes/config-array': 0.13.0
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
'@ungap/structured-clone': 1.2.0
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.7(supports-color@8.1.1)
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
@@ -17549,7 +17734,7 @@ snapshots:
'@eslint/core': 0.7.0
'@eslint/eslintrc': 3.1.0
'@eslint/js': 9.14.0
- '@eslint/plugin-kit': 0.2.3
+ '@eslint/plugin-kit': 0.2.0
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.1
@@ -17595,10 +17780,6 @@ snapshots:
esprima@4.0.1: {}
- esquery@1.4.2:
- dependencies:
- estraverse: 5.3.0
-
esquery@1.6.0:
dependencies:
estraverse: 5.3.0
@@ -17694,7 +17875,7 @@ snapshots:
human-signals: 5.0.0
is-stream: 3.0.0
merge-stream: 2.0.0
- npm-run-path: 5.1.0
+ npm-run-path: 5.3.0
onetime: 6.0.0
signal-exit: 4.1.0
strip-final-newline: 3.0.0
@@ -17874,34 +18055,34 @@ snapshots:
fastify-plugin@2.3.4:
dependencies:
- semver: 7.6.0
+ semver: 7.6.3
fastify-plugin@4.5.1: {}
- fastify-plugin@5.0.1: {}
+ fastify-plugin@5.0.0: {}
fastify-raw-body@5.0.0:
dependencies:
- fastify-plugin: 5.0.1
+ fastify-plugin: 5.0.0
raw-body: 3.0.0
secure-json-parse: 2.7.0
fastify@5.0.0:
dependencies:
- '@fastify/ajv-compiler': 4.0.1
+ '@fastify/ajv-compiler': 4.0.0
'@fastify/error': 4.0.0
- '@fastify/fast-json-stringify-compiler': 5.0.1
+ '@fastify/fast-json-stringify-compiler': 5.0.0
abstract-logging: 2.0.1
avvio: 9.0.0
fast-json-stringify: 6.0.0
- find-my-way: 9.1.0
- light-my-request: 6.1.0
+ find-my-way: 9.0.1
+ light-my-request: 6.0.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
+ semver: 7.6.3
toad-cache: 3.7.0
fastq@1.17.1:
@@ -17984,7 +18165,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- find-my-way@9.1.0:
+ find-my-way@9.0.1:
dependencies:
fast-deep-equal: 3.1.3
fast-querystring: 1.1.2
@@ -18055,12 +18236,6 @@ snapshots:
form-data-encoder@4.0.2: {}
- form-data@4.0.0:
- dependencies:
- asynckit: 0.4.0
- combined-stream: 1.0.8
- mime-types: 2.1.35
-
form-data@4.0.1:
dependencies:
asynckit: 0.4.0
@@ -18114,7 +18289,7 @@ snapshots:
function.prototype.name@1.1.5:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
define-properties: 1.2.0
es-abstract: 1.22.1
functions-have-names: 1.2.3
@@ -18147,7 +18322,7 @@ snapshots:
function-bind: 1.1.2
has-proto: 1.0.1
has-symbols: 1.0.3
- hasown: 2.0.2
+ hasown: 2.0.0
get-package-type@0.1.0: {}
@@ -18168,8 +18343,8 @@ snapshots:
get-symbol-description@1.0.0:
dependencies:
- call-bind: 1.0.2
- get-intrinsic: 1.2.1
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
get-symbol-description@1.0.2:
dependencies:
@@ -18207,8 +18382,8 @@ snapshots:
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@11.0.0:
@@ -18254,14 +18429,14 @@ snapshots:
globalthis@1.0.3:
dependencies:
- define-properties: 1.2.1
+ define-properties: 1.2.0
globby@11.1.0:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.3.2
- ignore: 5.3.1
+ ignore: 5.2.4
merge2: 1.4.1
slash: 3.0.0
@@ -18309,7 +18484,7 @@ snapshots:
lowercase-keys: 3.0.0
p-cancelable: 4.0.1
responselike: 3.0.0
- type-fest: 4.27.0
+ type-fest: 4.26.1
graceful-fs@4.2.11: {}
@@ -18346,7 +18521,7 @@ snapshots:
has-property-descriptors@1.0.0:
dependencies:
- get-intrinsic: 1.2.4
+ get-intrinsic: 1.2.1
has-property-descriptors@1.0.2:
dependencies:
@@ -18462,7 +18637,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.5
transitivePeerDependencies:
- supports-color
@@ -18488,14 +18663,14 @@ snapshots:
https-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.0
- debug: 4.3.4
+ debug: 4.3.5
transitivePeerDependencies:
- supports-color
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
@@ -18526,6 +18701,8 @@ snapshots:
dependencies:
minimatch: 9.0.4
+ ignore@5.2.4: {}
+
ignore@5.3.1: {}
immutable@4.2.2: {}
@@ -18581,7 +18758,7 @@ 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: {}
@@ -18648,7 +18825,7 @@ snapshots:
is-boolean-object@1.1.2:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
has-tostringtag: 1.0.0
is-callable@1.2.7: {}
@@ -18790,7 +18967,7 @@ snapshots:
is-weakset@2.0.2:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
get-intrinsic: 1.2.1
is-wsl@2.2.0:
@@ -18815,7 +18992,7 @@ snapshots:
istanbul-lib-instrument@5.2.1:
dependencies:
'@babel/core': 7.24.7
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.24.7
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2
semver: 6.3.1
@@ -18825,7 +19002,7 @@ snapshots:
istanbul-lib-instrument@6.0.0:
dependencies:
'@babel/core': 7.24.7
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.6
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2
semver: 7.6.3
@@ -18849,7 +19026,7 @@ snapshots:
istanbul-lib-source-maps@5.0.4:
dependencies:
'@jridgewell/trace-mapping': 0.3.25
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.5
istanbul-lib-coverage: 3.2.2
transitivePeerDependencies:
- supports-color
@@ -19125,7 +19302,7 @@ snapshots:
jest-snapshot@29.7.0:
dependencies:
'@babel/core': 7.23.5
- '@babel/generator': 7.24.7
+ '@babel/generator': 7.23.5
'@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5)
'@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.5)
'@babel/types': 7.24.7
@@ -19143,7 +19320,7 @@ snapshots:
jest-util: 29.7.0
natural-compare: 1.4.0
pretty-format: 29.7.0
- semver: 7.5.4
+ semver: 7.6.0
transitivePeerDependencies:
- supports-color
@@ -19473,9 +19650,9 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
- light-my-request@6.1.0:
+ light-my-request@6.0.0:
dependencies:
- cookie: 0.7.2
+ cookie: 0.6.0
process-warning: 4.0.0
set-cookie-parser: 2.6.0
@@ -19550,12 +19727,18 @@ snapshots:
dependencies:
get-func-name: 2.0.2
- loupe@3.1.2: {}
+ loupe@3.1.1:
+ dependencies:
+ get-func-name: 2.0.2
lowercase-keys@2.0.0: {}
lowercase-keys@3.0.0: {}
+ lru-cache@10.0.2:
+ dependencies:
+ semver: 7.6.3
+
lru-cache@10.2.2: {}
lru-cache@11.0.0: {}
@@ -19603,7 +19786,7 @@ snapshots:
make-dir@4.0.0:
dependencies:
- semver: 7.6.0
+ semver: 7.6.3
make-error@1.3.6: {}
@@ -20085,8 +20268,6 @@ snapshots:
minipass@5.0.0: {}
- minipass@7.0.4: {}
-
minipass@7.1.2: {}
minizlib@2.1.2:
@@ -20166,7 +20347,7 @@ snapshots:
outvariant: 1.4.3
path-to-regexp: 6.3.0
strict-event-emitter: 0.5.1
- type-fest: 4.27.0
+ type-fest: 4.26.1
yargs: 17.7.2
optionalDependencies:
typescript: 5.6.3
@@ -20242,7 +20423,7 @@ snapshots:
node-addon-api@3.2.1:
optional: true
- node-addon-api@7.1.0: {}
+ node-addon-api@8.3.0: {}
node-domexception@1.0.0: {}
@@ -20264,7 +20445,7 @@ snapshots:
node-gyp-build@4.6.0:
optional: true
- node-gyp-build@4.8.1: {}
+ node-gyp-build@4.8.4: {}
node-gyp@10.2.0:
dependencies:
@@ -20290,11 +20471,11 @@ snapshots:
nodemon@3.1.7:
dependencies:
chokidar: 3.5.3
- debug: 4.3.5(supports-color@5.5.0)
+ 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.0
+ semver: 7.6.3
simple-update-notifier: 2.0.0
supports-color: 5.5.0
touch: 3.1.0
@@ -20325,7 +20506,7 @@ snapshots:
dependencies:
hosted-git-info: 4.1.0
is-core-module: 2.13.1
- semver: 7.6.0
+ semver: 7.6.3
validate-npm-package-license: 3.0.4
normalize-path@3.0.0: {}
@@ -20342,10 +20523,6 @@ snapshots:
dependencies:
path-key: 3.1.1
- npm-run-path@5.1.0:
- dependencies:
- path-key: 4.0.0
-
npm-run-path@5.3.0:
dependencies:
path-key: 4.0.0
@@ -20442,7 +20619,7 @@ snapshots:
oniguruma-to-js@0.4.3:
dependencies:
- regex: 4.4.0
+ regex: 4.3.3
open@8.4.2:
dependencies:
@@ -20586,8 +20763,8 @@ snapshots:
path-scurry@1.10.1:
dependencies:
- lru-cache: 10.2.2
- minipass: 7.0.4
+ lru-cache: 10.0.2
+ minipass: 7.1.2
path-scurry@2.0.0:
dependencies:
@@ -20753,7 +20930,7 @@ snapshots:
postcss-calc@9.0.1(postcss@8.4.49):
dependencies:
postcss: 8.4.49
- postcss-selector-parser: 6.0.15
+ postcss-selector-parser: 6.0.16
postcss-value-parser: 4.2.0
postcss-colormin@6.1.0(postcss@8.4.49):
@@ -20886,11 +21063,6 @@ snapshots:
postcss: 8.4.49
postcss-value-parser: 4.2.0
- postcss-selector-parser@6.0.15:
- dependencies:
- cssesc: 3.0.0
- util-deprecate: 1.0.2
-
postcss-selector-parser@6.0.16:
dependencies:
cssesc: 3.0.0
@@ -21031,7 +21203,7 @@ snapshots:
pseudomap@1.0.2: {}
- psl@1.13.0:
+ psl@1.15.0:
dependencies:
punycode: 2.3.1
@@ -21111,6 +21283,8 @@ snapshots:
end-of-stream: 1.4.4
once: 1.4.0
+ punycode.js@2.3.1: {}
+
punycode@2.3.1: {}
pure-rand@6.0.0: {}
@@ -21187,7 +21361,7 @@ snapshots:
dependencies:
'@babel/core': 7.24.7
'@babel/traverse': 7.24.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.6
'@types/babel__core': 7.20.0
'@types/babel__traverse': 7.20.0
'@types/doctrine': 0.0.9
@@ -21293,7 +21467,7 @@ snapshots:
regenerator-runtime@0.14.0: {}
- regex@4.4.0: {}
+ regex@4.3.3: {}
regexp.prototype.flags@1.5.0:
dependencies:
@@ -21301,7 +21475,7 @@ snapshots:
define-properties: 1.2.0
functions-have-names: 1.2.3
- regexp.prototype.flags@1.5.3:
+ regexp.prototype.flags@1.5.2:
dependencies:
call-bind: 1.0.7
define-properties: 1.2.1
@@ -21450,8 +21624,8 @@ snapshots:
safe-array-concat@1.0.0:
dependencies:
- call-bind: 1.0.2
- get-intrinsic: 1.2.1
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
has-symbols: 1.0.3
isarray: 2.0.5
@@ -21468,8 +21642,8 @@ snapshots:
safe-regex-test@1.0.0:
dependencies:
- call-bind: 1.0.2
- get-intrinsic: 1.2.1
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
is-regex: 1.1.4
safe-regex-test@1.0.3:
@@ -21678,7 +21852,7 @@ snapshots:
dependencies:
'@hapi/hoek': 11.0.4
'@hapi/wreck': 18.0.1
- debug: 4.3.5(supports-color@5.5.0)
+ debug: 4.3.5
joi: 17.11.0
transitivePeerDependencies:
- supports-color
@@ -21689,7 +21863,7 @@ snapshots:
simple-update-notifier@2.0.0:
dependencies:
- semver: 7.5.4
+ semver: 7.6.0
sinon@16.1.3:
dependencies:
@@ -21976,7 +22150,7 @@ snapshots:
string.prototype.trim@1.2.7:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
define-properties: 1.2.0
es-abstract: 1.22.1
@@ -21989,7 +22163,7 @@ snapshots:
string.prototype.trimend@1.0.6:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
define-properties: 1.2.0
es-abstract: 1.22.1
@@ -22001,7 +22175,7 @@ snapshots:
string.prototype.trimstart@1.0.6:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
define-properties: 1.2.0
es-abstract: 1.22.1
@@ -22109,7 +22283,7 @@ snapshots:
css-tree: 2.3.1
css-what: 6.1.0
csso: 5.0.5
- picocolors: 1.0.0
+ picocolors: 1.0.1
symbol-tree@3.2.4: {}
@@ -22188,12 +22362,12 @@ snapshots:
tinyspy@3.0.2: {}
- tldts-core@6.1.63:
+ tldts-core@6.1.61:
optional: true
- tldts@6.1.63:
+ tldts@6.1.61:
dependencies:
- tldts-core: 6.1.63
+ tldts-core: 6.1.61
optional: true
tmp@0.2.3: {}
@@ -22230,14 +22404,14 @@ snapshots:
tough-cookie@4.1.4:
dependencies:
- psl: 1.13.0
+ psl: 1.15.0
punycode: 2.3.1
universalify: 0.2.0
url-parse: 1.5.10
tough-cookie@5.0.0:
dependencies:
- tldts: 6.1.63
+ tldts: 6.1.61
optional: true
tr46@0.0.3: {}
@@ -22261,6 +22435,10 @@ snapshots:
trough@2.2.0: {}
+ ts-api-utils@1.0.1(typescript@5.6.3):
+ dependencies:
+ typescript: 5.6.3
+
ts-api-utils@1.3.0(typescript@5.1.6):
dependencies:
typescript: 5.1.6
@@ -22273,22 +22451,24 @@ snapshots:
ts-dedent@2.2.0: {}
- ts-jest@29.1.2(@babel/core@7.23.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.5))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6):
+ ts-jest@29.2.5(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6):
dependencies:
bs-logger: 0.2.6
+ ejs: 3.1.10
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@22.9.0)
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
make-error: 1.3.6
- semver: 7.6.0
+ semver: 7.6.3
typescript: 5.1.6
yargs-parser: 21.1.1
optionalDependencies:
- '@babel/core': 7.23.5
+ '@babel/core': 7.24.7
+ '@jest/transform': 29.7.0
'@jest/types': 29.6.3
- babel-jest: 29.7.0(@babel/core@7.23.5)
+ babel-jest: 29.7.0(@babel/core@7.24.7)
esbuild: 0.24.0
ts-map@1.0.3: {}
@@ -22363,7 +22543,7 @@ snapshots:
type-fest@2.19.0: {}
- type-fest@4.27.0: {}
+ type-fest@4.26.1: {}
type-is@1.6.18:
dependencies:
@@ -22372,8 +22552,8 @@ snapshots:
typed-array-buffer@1.0.0:
dependencies:
- call-bind: 1.0.2
- get-intrinsic: 1.2.1
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
is-typed-array: 1.1.10
typed-array-buffer@1.0.2:
@@ -22384,7 +22564,7 @@ snapshots:
typed-array-byte-length@1.0.0:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
for-each: 0.3.3
has-proto: 1.0.1
is-typed-array: 1.1.10
@@ -22400,7 +22580,7 @@ snapshots:
typed-array-byte-offset@1.0.0:
dependencies:
available-typed-arrays: 1.0.5
- call-bind: 1.0.2
+ call-bind: 1.0.7
for-each: 0.3.3
has-proto: 1.0.1
is-typed-array: 1.1.10
@@ -22416,7 +22596,7 @@ snapshots:
typed-array-length@1.0.4:
dependencies:
- call-bind: 1.0.2
+ call-bind: 1.0.7
for-each: 0.3.3
is-typed-array: 1.1.10
@@ -22495,7 +22675,7 @@ snapshots:
dependencies:
'@fastify/busboy': 2.1.0
- undici@6.20.0: {}
+ undici@6.19.8: {}
unified@11.0.4:
dependencies:
@@ -22563,13 +22743,13 @@ snapshots:
dependencies:
browserslist: 4.22.2
escalade: 3.1.1
- picocolors: 1.0.0
+ picocolors: 1.0.1
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.0.1
uri-js@4.4.1:
dependencies:
@@ -22587,7 +22767,7 @@ snapshots:
utf-8-validate@6.0.4:
dependencies:
- node-gyp-build: 4.8.1
+ node-gyp-build: 4.6.0
optional: true
util-deprecate@1.0.2: {}
@@ -22652,7 +22832,7 @@ snapshots:
vite-node@1.6.0(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0):
dependencies:
cac: 6.7.14
- debug: 4.3.7(supports-color@8.1.1)
+ debug: 4.3.5
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)
@@ -22670,7 +22850,7 @@ snapshots:
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)
+ debug: 4.3.5
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)
@@ -22840,9 +23020,9 @@ snapshots:
vue-docgen-api@4.75.1(vue@3.5.12(typescript@5.6.3)):
dependencies:
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
- '@vue/compiler-dom': 3.5.11
+ '@babel/parser': 7.25.6
+ '@babel/types': 7.25.6
+ '@vue/compiler-dom': 3.5.12
'@vue/compiler-sfc': 3.5.12
ast-types: 0.16.1
hash-sum: 2.0.0
@@ -22855,12 +23035,12 @@ snapshots:
vue-eslint-parser@9.4.3(eslint@9.14.0):
dependencies:
- debug: 4.3.4
+ debug: 4.3.5
eslint: 9.14.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
- esquery: 1.4.2
+ esquery: 1.6.0
lodash: 4.17.21
semver: 7.6.0
transitivePeerDependencies:
diff --git a/scripts/dev.mjs b/scripts/dev.mjs
index ea3b017ba5..604d6567b9 100644
--- a/scripts/dev.mjs
+++ b/scripts/dev.mjs
@@ -27,7 +27,7 @@ await Promise.all([
stdout: process.stdout,
stderr: process.stderr,
}),
- execa('pnpm', ['--filter', 'misskey-js', 'build'], {
+ execa('pnpm', ['--filter', 'backend...', 'build'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,
@@ -39,19 +39,6 @@ await Promise.all([
}),
]);
-await Promise.all([
- execa('pnpm', ['--filter', 'misskey-reversi', 'build'], {
- cwd: _dirname + '/../',
- stdout: process.stdout,
- stderr: process.stderr,
- }),
- execa('pnpm', ['--filter', 'misskey-bubble-game', 'build'], {
- cwd: _dirname + '/../',
- stdout: process.stdout,
- stderr: process.stderr,
- }),
-]);
-
execa('pnpm', ['build-pre', '--watch'], {
cwd: _dirname + '/../',
stdout: process.stdout,
@@ -70,19 +57,19 @@ execa('pnpm', ['--filter', 'backend', 'dev'], {
stderr: process.stderr,
});
-execa('pnpm', ['--filter', 'frontend-shared', 'watch'], {
+execa('pnpm', ['--filter', 'frontend-shared', 'watch', '--no-clean'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,
});
-execa('pnpm', ['--filter', 'frontend', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], {
+execa('pnpm', ['--filter', 'frontend', 'watch'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,
});
-execa('pnpm', ['--filter', 'frontend-embed', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], {
+execa('pnpm', ['--filter', 'frontend-embed', 'watch'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,
@@ -94,19 +81,19 @@ execa('pnpm', ['--filter', 'sw', 'watch'], {
stderr: process.stderr,
});
-execa('pnpm', ['--filter', 'misskey-js', 'watch'], {
+execa('pnpm', ['--filter', 'misskey-js', 'watch', '--no-clean'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,
});
-execa('pnpm', ['--filter', 'misskey-reversi', 'watch'], {
+execa('pnpm', ['--filter', 'misskey-reversi', 'watch', '--no-clean'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,
});
-execa('pnpm', ['--filter', 'misskey-bubble-game', 'watch'], {
+execa('pnpm', ['--filter', 'misskey-bubble-game', 'watch', '--no-clean'], {
cwd: _dirname + '/../',
stdout: process.stdout,
stderr: process.stderr,