summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authormisskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com>2025-02-27 08:58:43 +0000
committerGitHub <noreply@github.com>2025-02-27 08:58:43 +0000
commita5f28c21e47030a9202de9ccf87556c5bebd7129 (patch)
treebd161b9620622d5bdc0d0a6a48dad7eda95c931d /packages/frontend/src
parentMerge pull request #15378 from misskey-dev/develop (diff)
parentRelease: 2025.2.1 (diff)
downloadmisskey-a5f28c21e47030a9202de9ccf87556c5bebd7129.tar.gz
misskey-a5f28c21e47030a9202de9ccf87556c5bebd7129.tar.bz2
misskey-a5f28c21e47030a9202de9ccf87556c5bebd7129.zip
Merge pull request #15507 from misskey-dev/develop
Release: 2025.2.1
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/account.ts5
-rw-r--r--packages/frontend/src/analytics.ts107
-rw-r--r--packages/frontend/src/boot/common.ts17
-rw-r--r--packages/frontend/src/boot/main-boot.ts5
-rw-r--r--packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAccountMoved.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAchievements.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAnalogClock.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAntennaEditor.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAsUi.vue5
-rw-r--r--packages/frontend/src/components/MkAutocomplete.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkAutocomplete.vue13
-rw-r--r--packages/frontend/src/components/MkAvatars.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkButton.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCaptcha.vue2
-rw-r--r--packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkChannelList.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkChannelList.vue3
-rw-r--r--packages/frontend/src/components/MkChannelPreview.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkChart.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkChartLegend.vue3
-rw-r--r--packages/frontend/src/components/MkClickerGame.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkClipPreview.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCode.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCodeEditor.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCodeInline.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkColorInput.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkContextMenu.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCropperDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue4
-rw-r--r--packages/frontend/src/components/MkCwButton.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDateSeparatedList.vue5
-rw-r--r--packages/frontend/src/components/MkDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDialog.vue1
-rw-r--r--packages/frontend/src/components/MkDigitalClock.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDonation.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDrive.file.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDrive.folder.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDrive.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.section.vue6
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.vue12
-rw-r--r--packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkFlashPreview.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts40
-rw-r--r--packages/frontend/src/components/MkImgPreviewDialog.vue58
-rw-r--r--packages/frontend/src/components/MkInput.vue6
-rw-r--r--packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkInstanceStats.vue3
-rw-r--r--packages/frontend/src/components/MkInstanceTicker.vue3
-rw-r--r--packages/frontend/src/components/MkInviteCode.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkLink.vue2
-rw-r--r--packages/frontend/src/components/MkMediaAudio.vue31
-rw-r--r--packages/frontend/src/components/MkMediaImage.vue29
-rw-r--r--packages/frontend/src/components/MkMediaRange.vue5
-rw-r--r--packages/frontend/src/components/MkMediaVideo.vue31
-rw-r--r--packages/frontend/src/components/MkMention.vue2
-rw-r--r--packages/frontend/src/components/MkMenu.vue4
-rw-r--r--packages/frontend/src/components/MkModal.vue2
-rw-r--r--packages/frontend/src/components/MkNote.vue19
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue21
-rw-r--r--packages/frontend/src/components/MkNoteMediaGrid.vue70
-rw-r--r--packages/frontend/src/components/MkNotes.vue3
-rw-r--r--packages/frontend/src/components/MkNotification.vue14
-rw-r--r--packages/frontend/src/components/MkNotificationSelectWindow.vue5
-rw-r--r--packages/frontend/src/components/MkNotifications.vue2
-rw-r--r--packages/frontend/src/components/MkPageWindow.vue17
-rw-r--r--packages/frontend/src/components/MkPagination.vue5
-rw-r--r--packages/frontend/src/components/MkPostForm.vue66
-rw-r--r--packages/frontend/src/components/MkPostFormAttaches.vue32
-rw-r--r--packages/frontend/src/components/MkRadios.vue3
-rw-r--r--packages/frontend/src/components/MkReactionsViewer.reaction.vue9
-rw-r--r--packages/frontend/src/components/MkRemoteCaution.vue4
-rw-r--r--packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkSelect.vue131
-rw-r--r--packages/frontend/src/components/MkSignin.vue3
-rw-r--r--packages/frontend/src/components/MkSignupDialog.form.vue3
-rw-r--r--packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkSortOrderEditor.define.ts4
-rw-r--r--packages/frontend/src/components/MkSortOrderEditor.vue4
-rw-r--r--packages/frontend/src/components/MkSuperMenu.vue2
-rw-r--r--packages/frontend/src/components/MkSwitch.button.vue3
-rw-r--r--packages/frontend/src/components/MkSwitch.vue3
-rw-r--r--packages/frontend/src/components/MkSystemWebhookEditor.vue4
-rw-r--r--packages/frontend/src/components/MkTagItem.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkTextarea.vue3
-rw-r--r--packages/frontend/src/components/MkTimeline.vue20
-rw-r--r--packages/frontend/src/components/MkUrlPreview.vue2
-rw-r--r--packages/frontend/src/components/MkUserAnnouncementEditDialog.vue2
-rw-r--r--packages/frontend/src/components/MkUserList.vue3
-rw-r--r--packages/frontend/src/components/MkUserSelectDialog.vue4
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Follow.vue3
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkA.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkA.vue2
-rw-r--r--packages/frontend/src/components/global/MkAcct.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkAd.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkAvatar.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkEllipsis.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkEmoji.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkError.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkError.stories.meta.ts2
-rw-r--r--packages/frontend/src/components/global/MkLoading.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkMfm.stories.impl.ts4
-rw-r--r--packages/frontend/src/components/global/MkMfm.ts6
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.vue3
-rw-r--r--packages/frontend/src/components/global/MkStickyContainer.vue3
-rw-r--r--packages/frontend/src/components/global/MkTime.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkUrl.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/MkUrl.vue2
-rw-r--r--packages/frontend/src/components/global/MkUserName.stories.impl.ts2
-rw-r--r--packages/frontend/src/components/global/RouterView.vue2
-rw-r--r--packages/frontend/src/components/grid/MkDataCell.vue7
-rw-r--r--packages/frontend/src/components/grid/MkDataRow.vue7
-rw-r--r--packages/frontend/src/components/grid/MkGrid.stories.impl.ts8
-rw-r--r--packages/frontend/src/components/grid/MkGrid.vue19
-rw-r--r--packages/frontend/src/components/grid/MkHeaderCell.vue5
-rw-r--r--packages/frontend/src/components/grid/MkHeaderRow.vue7
-rw-r--r--packages/frontend/src/components/grid/MkNumberCell.vue3
-rw-r--r--packages/frontend/src/components/grid/cell-validators.ts14
-rw-r--r--packages/frontend/src/components/grid/cell.ts18
-rw-r--r--packages/frontend/src/components/grid/column.ts14
-rw-r--r--packages/frontend/src/components/grid/grid-event.ts10
-rw-r--r--packages/frontend/src/components/grid/grid-utils.ts14
-rw-r--r--packages/frontend/src/components/grid/grid.ts14
-rw-r--r--packages/frontend/src/components/grid/row.ts16
-rw-r--r--packages/frontend/src/components/index.ts2
-rw-r--r--packages/frontend/src/debug.ts3
-rw-r--r--packages/frontend/src/directives/adaptive-bg.ts2
-rw-r--r--packages/frontend/src/directives/adaptive-border.ts2
-rw-r--r--packages/frontend/src/directives/anim.ts2
-rw-r--r--packages/frontend/src/directives/appear.ts2
-rw-r--r--packages/frontend/src/directives/click-anime.ts2
-rw-r--r--packages/frontend/src/directives/follow-append.ts2
-rw-r--r--packages/frontend/src/directives/get-size.ts2
-rw-r--r--packages/frontend/src/directives/hotkey.ts2
-rw-r--r--packages/frontend/src/directives/index.ts2
-rw-r--r--packages/frontend/src/directives/panel.ts2
-rw-r--r--packages/frontend/src/directives/tooltip.ts3
-rw-r--r--packages/frontend/src/directives/user-preview.ts3
-rw-r--r--packages/frontend/src/local-storage.ts3
-rw-r--r--packages/frontend/src/memory-storage.ts57
-rw-r--r--packages/frontend/src/nirax.ts7
-rw-r--r--packages/frontend/src/os.ts18
-rw-r--r--packages/frontend/src/pages/about-misskey.vue3
-rw-r--r--packages/frontend/src/pages/about.federation.vue3
-rw-r--r--packages/frontend/src/pages/admin-user.vue2
-rw-r--r--packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue3
-rw-r--r--packages/frontend/src/pages/admin/bot-protection.vue2
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue16
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue14
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue17
-rw-r--r--packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts2
-rw-r--r--packages/frontend/src/pages/admin/external-services.vue53
-rw-r--r--packages/frontend/src/pages/admin/index.vue3
-rw-r--r--packages/frontend/src/pages/admin/invites.vue3
-rw-r--r--packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts2
-rw-r--r--packages/frontend/src/pages/admin/overview.federation.vue3
-rw-r--r--packages/frontend/src/pages/admin/queue.vue3
-rw-r--r--packages/frontend/src/pages/admin/server-rules.vue2
-rw-r--r--packages/frontend/src/pages/admin/users.vue44
-rw-r--r--packages/frontend/src/pages/api-console.vue2
-rw-r--r--packages/frontend/src/pages/channel.vue2
-rw-r--r--packages/frontend/src/pages/drive.file.notes.vue2
-rw-r--r--packages/frontend/src/pages/drop-and-fusion.game.vue3
-rw-r--r--packages/frontend/src/pages/flash/flash.vue12
-rw-r--r--packages/frontend/src/pages/follow-requests.vue3
-rw-r--r--packages/frontend/src/pages/install-extensions.vue6
-rw-r--r--packages/frontend/src/pages/instance-info.vue6
-rw-r--r--packages/frontend/src/pages/invite.vue5
-rw-r--r--packages/frontend/src/pages/note.vue8
-rw-r--r--packages/frontend/src/pages/page-editor/page-editor.container.vue4
-rw-r--r--packages/frontend/src/pages/scratchpad.vue8
-rw-r--r--packages/frontend/src/pages/search.note.vue315
-rw-r--r--packages/frontend/src/pages/search.stories.impl.ts2
-rw-r--r--packages/frontend/src/pages/search.user.vue11
-rw-r--r--packages/frontend/src/pages/settings/accounts.vue4
-rw-r--r--packages/frontend/src/pages/settings/drive-cleaner.vue3
-rw-r--r--packages/frontend/src/pages/settings/emoji-picker.vue3
-rw-r--r--packages/frontend/src/pages/settings/general.vue2
-rw-r--r--packages/frontend/src/pages/settings/index.vue6
-rw-r--r--packages/frontend/src/pages/settings/notifications.vue5
-rw-r--r--packages/frontend/src/pages/settings/sounds.vue3
-rw-r--r--packages/frontend/src/pages/settings/theme.manage.vue3
-rw-r--r--packages/frontend/src/pages/settings/theme.vue83
-rw-r--r--packages/frontend/src/pages/theme-editor.vue3
-rw-r--r--packages/frontend/src/pages/user/activity.following.vue3
-rw-r--r--packages/frontend/src/pages/user/activity.notes.vue3
-rw-r--r--packages/frontend/src/pages/user/activity.pv.vue3
-rw-r--r--packages/frontend/src/pages/user/home.stories.impl.ts2
-rw-r--r--packages/frontend/src/pizzax.ts14
-rw-r--r--packages/frontend/src/plugin.ts3
-rw-r--r--packages/frontend/src/router/definition.ts3
-rw-r--r--packages/frontend/src/router/main.ts11
-rw-r--r--packages/frontend/src/router/supplier.ts3
-rw-r--r--packages/frontend/src/scripts/aiscript/api.ts2
-rw-r--r--packages/frontend/src/scripts/aiscript/common.ts3
-rw-r--r--packages/frontend/src/scripts/aiscript/ui.ts3
-rw-r--r--packages/frontend/src/scripts/autocomplete.ts15
-rw-r--r--packages/frontend/src/scripts/chart-legend.ts2
-rw-r--r--packages/frontend/src/scripts/chart-vline.ts2
-rw-r--r--packages/frontend/src/scripts/check-reaction-permissions.ts2
-rw-r--r--packages/frontend/src/scripts/emoji-picker.ts3
-rw-r--r--packages/frontend/src/scripts/file-drop.ts2
-rw-r--r--packages/frontend/src/scripts/format-time-string.ts2
-rw-r--r--packages/frontend/src/scripts/gen-search-query.ts35
-rw-r--r--packages/frontend/src/scripts/get-note-menu.ts3
-rw-r--r--packages/frontend/src/scripts/get-user-menu.ts2
-rw-r--r--packages/frontend/src/scripts/install-theme.ts3
-rw-r--r--packages/frontend/src/scripts/key-event.ts14
-rw-r--r--packages/frontend/src/scripts/lookup.ts4
-rw-r--r--packages/frontend/src/scripts/mfm-function-picker.ts3
-rw-r--r--packages/frontend/src/scripts/page-metadata.ts3
-rw-r--r--packages/frontend/src/scripts/reaction-picker.ts3
-rw-r--r--packages/frontend/src/scripts/stream-mock.ts6
-rw-r--r--packages/frontend/src/scripts/theme-editor.ts3
-rw-r--r--packages/frontend/src/scripts/use-form.ts3
-rw-r--r--packages/frontend/src/scripts/use-leave-guard.ts2
-rw-r--r--packages/frontend/src/scripts/use-note-capture.ts3
-rw-r--r--packages/frontend/src/scripts/use-tooltip.ts3
-rw-r--r--packages/frontend/src/store.ts9
-rw-r--r--packages/frontend/src/theme-store.ts3
-rw-r--r--packages/frontend/src/types/menu.ts4
-rw-r--r--packages/frontend/src/ui/classic.vue3
-rw-r--r--packages/frontend/src/ui/deck.vue2
-rw-r--r--packages/frontend/src/ui/deck/antenna-column.vue15
-rw-r--r--packages/frontend/src/ui/deck/channel-column.vue24
-rw-r--r--packages/frontend/src/ui/deck/column.vue8
-rw-r--r--packages/frontend/src/ui/deck/deck-store.ts16
-rw-r--r--packages/frontend/src/ui/deck/direct-column.vue5
-rw-r--r--packages/frontend/src/ui/deck/list-column.vue25
-rw-r--r--packages/frontend/src/ui/deck/main-column.vue6
-rw-r--r--packages/frontend/src/ui/deck/mentions-column.vue5
-rw-r--r--packages/frontend/src/ui/deck/notifications-column.vue5
-rw-r--r--packages/frontend/src/ui/deck/role-timeline-column.vue17
-rw-r--r--packages/frontend/src/ui/deck/tl-column.vue7
-rw-r--r--packages/frontend/src/ui/deck/tl-note-notification.ts7
-rw-r--r--packages/frontend/src/ui/deck/widgets-column.vue5
-rw-r--r--packages/frontend/src/ui/minimum.vue3
-rw-r--r--packages/frontend/src/ui/universal.vue6
-rw-r--r--packages/frontend/src/ui/visitor.vue3
-rw-r--r--packages/frontend/src/ui/zen.vue3
-rw-r--r--packages/frontend/src/widgets/WidgetActivity.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetAichan.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetAiscript.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetAiscriptApp.vue11
-rw-r--r--packages/frontend/src/widgets/WidgetBirthdayFollowings.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetButton.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetCalendar.vue7
-rw-r--r--packages/frontend/src/widgets/WidgetClicker.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetClock.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetDigitalClock.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetFederation.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetInstanceCloud.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetInstanceInfo.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetJobQueue.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetMemo.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetNotifications.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetOnlineUsers.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetPhotos.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetPostForm.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetProfile.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetRss.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetRssTicker.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetSlideshow.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetTimeline.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetTrends.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetUnixClock.vue5
-rw-r--r--packages/frontend/src/widgets/WidgetUserList.vue5
-rw-r--r--packages/frontend/src/widgets/index.ts3
-rw-r--r--packages/frontend/src/widgets/server-metric/index.vue5
-rw-r--r--packages/frontend/src/widgets/widget.ts2
284 files changed, 1709 insertions, 757 deletions
diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index 9006150bc8..17d690cd3a 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -7,6 +7,7 @@ import { defineAsyncComponent, reactive, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { apiUrl } from '@@/js/config.js';
import type { MenuItem, MenuButton } from '@/types/menu.js';
+import { defaultMemoryStorage } from '@/memory-storage';
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
@@ -40,6 +41,8 @@ export function incNotesCount() {
export async function signout() {
if (!$i) return;
+ defaultMemoryStorage.clear();
+
waiting();
document.cookie.split(';').forEach((cookie) => {
const cookieName = cookie.split('=')[0].trim();
@@ -107,7 +110,7 @@ export async function removeAccount(idOrToken: Account['id']) {
}
function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Promise<Account> {
- document.cookie = "token=; path=/; max-age=0";
+ document.cookie = 'token=; path=/; max-age=0';
document.cookie = `token=${token}; path=/queue; max-age=86400; SameSite=Strict; Secure`; // bull dashboardの認証とかで使う
return new Promise((done, fail) => {
diff --git a/packages/frontend/src/analytics.ts b/packages/frontend/src/analytics.ts
new file mode 100644
index 0000000000..e07a4e9258
--- /dev/null
+++ b/packages/frontend/src/analytics.ts
@@ -0,0 +1,107 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import type { AnalyticsInstance, AnalyticsPlugin } from 'analytics';
+
+/**
+ * analytics moduleを読み込まなくても動作するようにするためのラッパー
+ */
+class AnalyticsProxy implements AnalyticsInstance {
+ private analytics?: AnalyticsInstance;
+
+ constructor(analytics?: AnalyticsInstance) {
+ if (analytics) {
+ this.analytics = analytics;
+ }
+ }
+
+ public setAnalytics(analytics: AnalyticsInstance) {
+ if (this.analytics) {
+ throw new Error('Analytics instance already exists.');
+ }
+ this.analytics = analytics;
+ }
+
+ public identify(...args: Parameters<AnalyticsInstance['identify']>) {
+ return this.analytics?.identify(...args) ?? Promise.resolve();
+ }
+
+ public track(...args: Parameters<AnalyticsInstance['track']>) {
+ return this.analytics?.track(...args) ?? Promise.resolve();
+ }
+
+ public page(...args: Parameters<AnalyticsInstance['page']>) {
+ return this.analytics?.page(...args) ?? Promise.resolve();
+ }
+
+ public user(...args: Parameters<AnalyticsInstance['user']>) {
+ return this.analytics?.user(...args) ?? Promise.resolve();
+ }
+
+ public reset(...args: Parameters<AnalyticsInstance['reset']>) {
+ return this.analytics?.reset(...args) ?? Promise.resolve();
+ }
+
+ public ready(...args: Parameters<AnalyticsInstance['ready']>) {
+ return this.analytics?.ready(...args) ?? function () { void 0; };
+ }
+
+ public on(...args: Parameters<AnalyticsInstance['on']>) {
+ return this.analytics?.on(...args) ?? function () { void 0; };
+ }
+
+ public once(...args: Parameters<AnalyticsInstance['once']>) {
+ return this.analytics?.once(...args) ?? function () { void 0; };
+ }
+
+ public getState(...args: Parameters<AnalyticsInstance['getState']>) {
+ return this.analytics?.getState(...args) ?? Promise.resolve();
+ }
+
+ public get storage() {
+ return this.analytics?.storage ?? {
+ getItem: () => null,
+ setItem: () => void 0,
+ removeItem: () => void 0,
+ };
+ }
+
+ public get plugins() {
+ return this.analytics?.plugins ?? {
+ enable: (p, c) => Promise.resolve(c ? c() : void 0),
+ disable: (p, c) => Promise.resolve(c ? c() : void 0),
+ };
+ }
+}
+
+export const analytics = new AnalyticsProxy();
+
+export async function initAnalytics(instance: Misskey.entities.MetaDetailed) {
+ // アナリティクスプロバイダに関する設定がひとつもない場合は、アナリティクスモジュールを読み込まない
+ if (!instance.googleAnalyticsMeasurementId) {
+ return;
+ }
+
+ const { default: Analytics } = await import('analytics');
+ const plugins: AnalyticsPlugin[] = [];
+
+ // Google Analytics
+ if (instance.googleAnalyticsMeasurementId) {
+ const { default: googleAnalytics } = await import('@analytics/google-analytics');
+
+ plugins.push(googleAnalytics({
+ measurementIds: [instance.googleAnalyticsMeasurementId],
+ debug: _DEV_,
+ }));
+ }
+
+ analytics.setAnalytics(Analytics({
+ app: 'misskey',
+ version: _VERSION_,
+ debug: _DEV_,
+ plugins,
+ }));
+}
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index ae6b1aee26..d09b98efe0 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -3,9 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { computed, watch, version as vueVersion, App } from 'vue';
+import { computed, watch, version as vueVersion } from 'vue';
import { compareVersions } from 'compare-versions';
import { version, lang, updateLocale, locale } from '@@/js/config.js';
+import type { App } from 'vue';
import widgets from '@/widgets/index.js';
import directives from '@/directives/index.js';
import components from '@/components/index.js';
@@ -20,6 +21,7 @@ import { reloadChannel } from '@/scripts/unison-reload.js';
import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
import { getAccountFromId } from '@/scripts/get-account-from-id.js';
import { deckStore } from '@/ui/deck/deck-store.js';
+import { analytics, initAnalytics } from '@/analytics.js';
import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
import { setupRouter } from '@/router/main.js';
@@ -240,6 +242,19 @@ export async function common(createVue: () => App<Element>) {
await fetchCustomEmojis();
} catch (err) { /* empty */ }
+ // analytics
+ fetchInstanceMetaPromise.then(async () => {
+ await initAnalytics(instance);
+
+ if ($i) {
+ analytics.identify($i.id);
+ }
+
+ analytics.page({
+ path: window.location.pathname,
+ });
+ });
+
const app = createVue();
setupRouter(app, createMainRouter);
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index 874e97f3a4..3a43c6794b 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -6,7 +6,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 * as Misskey from 'misskey-js';
import type { Component } from 'vue';
import { i18n } from '@/i18n.js';
import { alert, confirm, popup, post, toast } from '@/os.js';
@@ -22,7 +22,8 @@ import { initializeSw } from '@/scripts/initialize-sw.js';
import { deckStore } from '@/ui/deck/deck-store.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mainRouter } from '@/router/main.js';
-import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
+import { makeHotkey } from '@/scripts/hotkey.js';
+import type { Keymap } from '@/scripts/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
export async function mainBoot() {
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
index 9df957f3ec..b62096bbe9 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
+++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
index cad26de6e2..b907b5b25a 100644
--- a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
+++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts
index 7614da51da..bbd3f69d7c 100644
--- a/packages/frontend/src/components/MkAchievements.stories.impl.ts
+++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
index 270ca40825..a01d91ad20 100644
--- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import MkAnalogClock from './MkAnalogClock.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
index bf3ddb935b..627cb0c4ff 100644
--- a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAnnouncementDialog from './MkAnnouncementDialog.vue';
diff --git a/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts b/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts
index 1749e07a4e..4d921a4c48 100644
--- a/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts
+++ b/packages/frontend/src/components/MkAntennaEditor.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAntennaEditor from './MkAntennaEditor.vue';
diff --git a/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts b/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts
index 1c6ca83b47..5878b52fb9 100644
--- a/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkAntennaEditorDialog.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAntennaEditorDialog from './MkAntennaEditorDialog.vue';
diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index 365b767bd6..5c4d887e0c 100644
--- a/packages/frontend/src/components/MkAsUi.vue
+++ b/packages/frontend/src/components/MkAsUi.vue
@@ -63,14 +63,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { Ref, ref, computed } from 'vue';
+import { ref, computed } from 'vue';
+import type { Ref } from 'vue';
import * as os from '@/os.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSelect from '@/components/MkSelect.vue';
-import { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/scripts/aiscript/ui.js';
+import type { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/scripts/aiscript/ui.js';
import MkFolder from '@/components/MkFolder.vue';
import MkPostForm from '@/components/MkPostForm.vue';
diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
index ec24b8c240..af5dd4784d 100644
--- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
+++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 0ea4566d4e..33495c8af6 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -47,8 +47,10 @@ SPDX-License-Identifier: AGPL-3.0-only
import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import sanitizeHtml from 'sanitize-html';
import { emojilist, getEmojiName } from '@@/js/emojilist.js';
-import contains from '@/scripts/contains.js';
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
+import { MFM_TAGS, MFM_PARAMS } from '@@/js/const.js';
+import type { EmojiDef } from '@/scripts/search-emoji.js';
+import contains from '@/scripts/contains.js';
import { acct } from '@/filters/user.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
@@ -56,8 +58,7 @@ import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
import { customEmojis } from '@/custom-emojis.js';
-import { MFM_TAGS, MFM_PARAMS } from '@@/js/const.js';
-import { searchEmoji, EmojiDef } from '@/scripts/search-emoji.js';
+import { searchEmoji } from '@/scripts/search-emoji.js';
const lib = emojilist.filter(x => x.category !== 'flags');
@@ -197,8 +198,10 @@ function exec() {
users.value = JSON.parse(cache);
fetching.value = false;
} else {
+ const [username, host] = props.q.toString().split('@');
misskeyApi('users/search-by-username-and-host', {
- username: props.q,
+ username: username,
+ host: host,
limit: 10,
detail: false,
}).then(searchedUsers => {
@@ -407,7 +410,7 @@ onBeforeUnmount(() => {
text-overflow: ellipsis;
&:hover {
- background: var(--MI_THEME-X3);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
&[data-selected='true'] {
diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts
index d2a4a9f03b..6e20294438 100644
--- a/packages/frontend/src/components/MkAvatars.stories.impl.ts
+++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts
index e8802e4f8f..0a569b3beb 100644
--- a/packages/frontend/src/components/MkButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkButton.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkButton from './MkButton.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index b1167bbac6..134f8226d4 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -50,6 +50,8 @@ type CaptchaContainer = {
};
declare global {
+ // Window を拡張してるため、空ではない
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface Window extends CaptchaContainer { }
}
diff --git a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts
index b9770670dc..a42e80c27a 100644
--- a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
diff --git a/packages/frontend/src/components/MkChannelList.stories.impl.ts b/packages/frontend/src/components/MkChannelList.stories.impl.ts
index f69b20c049..47ca864dc0 100644
--- a/packages/frontend/src/components/MkChannelList.stories.impl.ts
+++ b/packages/frontend/src/components/MkChannelList.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { channel } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue
index 2850ecca16..23705f6ff8 100644
--- a/packages/frontend/src/components/MkChannelList.vue
+++ b/packages/frontend/src/components/MkChannelList.vue
@@ -20,7 +20,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import MkChannelPreview from '@/components/MkChannelPreview.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
diff --git a/packages/frontend/src/components/MkChannelPreview.stories.impl.ts b/packages/frontend/src/components/MkChannelPreview.stories.impl.ts
index de0193c78f..dbee069771 100644
--- a/packages/frontend/src/components/MkChannelPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkChannelPreview.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { channel } from '../../.storybook/fakes.js';
import MkChannelPreview from './MkChannelPreview.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkChart.stories.impl.ts b/packages/frontend/src/components/MkChart.stories.impl.ts
index 1bcb9c30d8..3caf01d34e 100644
--- a/packages/frontend/src/components/MkChart.stories.impl.ts
+++ b/packages/frontend/src/components/MkChart.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import { getChartResolver } from '../../.storybook/charts.js';
diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue
index 574cde9da4..06607bc04c 100644
--- a/packages/frontend/src/components/MkChartLegend.vue
+++ b/packages/frontend/src/components/MkChartLegend.vue
@@ -14,7 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { shallowRef } from 'vue';
-import { Chart, LegendItem } from 'chart.js';
+import { Chart } from 'chart.js';
+import type { LegendItem } from 'chart.js';
const chart = shallowRef<Chart>();
const type = shallowRef<string>();
diff --git a/packages/frontend/src/components/MkClickerGame.stories.impl.ts b/packages/frontend/src/components/MkClickerGame.stories.impl.ts
index 36313f965d..eb7e61f294 100644
--- a/packages/frontend/src/components/MkClickerGame.stories.impl.ts
+++ b/packages/frontend/src/components/MkClickerGame.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
diff --git a/packages/frontend/src/components/MkClipPreview.stories.impl.ts b/packages/frontend/src/components/MkClipPreview.stories.impl.ts
index 62503fb98a..496dc09eed 100644
--- a/packages/frontend/src/components/MkClipPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkClipPreview.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { clip } from '../../.storybook/fakes.js';
import MkClipPreview from './MkClipPreview.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkCode.stories.impl.ts b/packages/frontend/src/components/MkCode.stories.impl.ts
index b7e53e8e35..fae9d459fb 100644
--- a/packages/frontend/src/components/MkCode.stories.impl.ts
+++ b/packages/frontend/src/components/MkCode.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkCode from './MkCode.vue';
const code = `for (let i, 100) {
<: if (i % 15 == 0) "FizzBuzz"
diff --git a/packages/frontend/src/components/MkCodeEditor.stories.impl.ts b/packages/frontend/src/components/MkCodeEditor.stories.impl.ts
index 5c410c4886..c76b6fd08e 100644
--- a/packages/frontend/src/components/MkCodeEditor.stories.impl.ts
+++ b/packages/frontend/src/components/MkCodeEditor.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { action } from '@storybook/addon-actions';
import MkCodeEditor from './MkCodeEditor.vue';
const code = `for (let i, 100) {
diff --git a/packages/frontend/src/components/MkCodeInline.stories.impl.ts b/packages/frontend/src/components/MkCodeInline.stories.impl.ts
index 51d4d106ff..c17be177cb 100644
--- a/packages/frontend/src/components/MkCodeInline.stories.impl.ts
+++ b/packages/frontend/src/components/MkCodeInline.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkCodeInline from './MkCodeInline.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/MkColorInput.stories.impl.ts b/packages/frontend/src/components/MkColorInput.stories.impl.ts
index 61383e2cae..3df92ca858 100644
--- a/packages/frontend/src/components/MkColorInput.stories.impl.ts
+++ b/packages/frontend/src/components/MkColorInput.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { action } from '@storybook/addon-actions';
import MkColorInput from './MkColorInput.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkContextMenu.stories.impl.ts b/packages/frontend/src/components/MkContextMenu.stories.impl.ts
index 1ff0f51bd4..7a5e36131b 100644
--- a/packages/frontend/src/components/MkContextMenu.stories.impl.ts
+++ b/packages/frontend/src/components/MkContextMenu.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userEvent, within } from '@storybook/test';
import MkContextMenu from './MkContextMenu.vue';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/components/MkCropperDialog.stories.impl.ts b/packages/frontend/src/components/MkCropperDialog.stories.impl.ts
index ce13093975..27ce60415b 100644
--- a/packages/frontend/src/components/MkCropperDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkCropperDialog.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { file } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts
index 8a05e06311..3da27dcedb 100644
--- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { emojiDetailed } from '../../.storybook/fakes.js';
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
index e6ab17417d..86d6269c69 100644
--- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
@@ -85,7 +85,7 @@ function cancel() {
.emojiImgWrapper {
max-width: 100%;
height: 40cqh;
- background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-X5) 8px, var(--MI_THEME-X5) 14px);
+ background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)) 8px, light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)) 14px);
border-radius: var(--MI-radius);
margin: auto;
overflow-y: hidden;
@@ -101,7 +101,7 @@ function cancel() {
display: inline-block;
word-break: break-all;
padding: 3px 10px;
- background-color: var(--MI_THEME-X5);
+ background-color: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
border: solid 1px var(--MI_THEME-divider);
border-radius: var(--MI-radius);
}
diff --git a/packages/frontend/src/components/MkCwButton.stories.impl.ts b/packages/frontend/src/components/MkCwButton.stories.impl.ts
index 5d6ea56da9..bbe5f4eddb 100644
--- a/packages/frontend/src/components/MkCwButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkCwButton.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
import { file } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index 9c75f91cb2..0d5a16126b 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -4,14 +4,15 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<script lang="ts">
-import { defineComponent, h, PropType, TransitionGroup, useCssModule } from 'vue';
+import { defineComponent, h, TransitionGroup, useCssModule } from 'vue';
+import type { PropType } from 'vue';
import MkAd from '@/components/global/MkAd.vue';
import { isDebuggerEnabled, stackTraceInstances } from '@/debug.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { instance } from '@/instance.js';
import { defaultStore } from '@/store.js';
-import { MisskeyEntity } from '@/types/date-separated-list.js';
+import type { MisskeyEntity } from '@/types/date-separated-list.js';
export default defineComponent({
props: {
diff --git a/packages/frontend/src/components/MkDialog.stories.impl.ts b/packages/frontend/src/components/MkDialog.stories.impl.ts
index 2d8d3661f2..57c7916049 100644
--- a/packages/frontend/src/components/MkDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkDialog.stories.impl.ts
@@ -5,7 +5,7 @@
import { action } from '@storybook/addon-actions';
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { i18n } from '@/i18n.js';
import MkDialog from './MkDialog.vue';
const Base = {
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index b095a1cd4a..6c9fa3167a 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -142,6 +142,7 @@ const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'character
// overload function を使いたいので lint エラーを無視する
function done(canceled: true): void;
function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
+
function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result });
modal.value?.close();
diff --git a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
index e3391bcf7e..af58f5c375 100644
--- a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import MkDigitalClock from './MkDigitalClock.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkDonation.stories.impl.ts b/packages/frontend/src/components/MkDonation.stories.impl.ts
index 27d6b7df6c..71d0c20c63 100644
--- a/packages/frontend/src/components/MkDonation.stories.impl.ts
+++ b/packages/frontend/src/components/MkDonation.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { onBeforeUnmount } from 'vue';
import MkDonation from './MkDonation.vue';
import { instance } from '@/instance.js';
diff --git a/packages/frontend/src/components/MkDrive.file.stories.impl.ts b/packages/frontend/src/components/MkDrive.file.stories.impl.ts
index 5f6e6a0667..933383775c 100644
--- a/packages/frontend/src/components/MkDrive.file.stories.impl.ts
+++ b/packages/frontend/src/components/MkDrive.file.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkDrive_file from './MkDrive.file.vue';
import { file } from '../../.storybook/fakes.js';
export const Default = {
diff --git a/packages/frontend/src/components/MkDrive.folder.stories.impl.ts b/packages/frontend/src/components/MkDrive.folder.stories.impl.ts
index 5f8ef48520..e6c7c2f645 100644
--- a/packages/frontend/src/components/MkDrive.folder.stories.impl.ts
+++ b/packages/frontend/src/components/MkDrive.folder.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http, HttpResponse } from 'msw';
import * as Misskey from 'misskey-js';
import MkDrive_folder from './MkDrive.folder.vue';
diff --git a/packages/frontend/src/components/MkDrive.stories.impl.ts b/packages/frontend/src/components/MkDrive.stories.impl.ts
index fe20e61415..4394eebfda 100644
--- a/packages/frontend/src/components/MkDrive.stories.impl.ts
+++ b/packages/frontend/src/components/MkDrive.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http, HttpResponse } from 'msw';
import * as Misskey from 'misskey-js';
import MkDrive from './MkDrive.vue';
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts b/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts
index 3fa24d7edb..d259444e94 100644
--- a/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts
+++ b/packages/frontend/src/components/MkDriveFileThumbnail.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkDriveFileThumbnail from './MkDriveFileThumbnail.vue';
import { file } from '../../.storybook/fakes.js';
export const Default = {
diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue
index b418ed3ae6..ef515e471f 100644
--- a/packages/frontend/src/components/MkEmojiPicker.section.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.section.vue
@@ -61,8 +61,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, computed, Ref } from 'vue';
-import { CustomEmojiFolderTree, getEmojiName } from '@@/js/emojilist.js';
+import { ref, computed } from 'vue';
+import type { Ref } from 'vue';
+import { getEmojiName } from '@@/js/emojilist.js';
+import type { CustomEmojiFolderTree } from '@@/js/emojilist.js';
import { i18n } from '@/i18n.js';
import { customEmojis } from '@/custom-emojis.js';
import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue';
diff --git a/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts b/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts
index d38d8de808..bf4158a2c8 100644
--- a/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts
+++ b/packages/frontend/src/components/MkEmojiPicker.stories.impl.ts
@@ -5,7 +5,7 @@
import { action } from '@storybook/addon-actions';
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { i18n } from '@/i18n.js';
import MkEmojiPicker from './MkEmojiPicker.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 8187d991e7..5da161dae8 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -120,12 +120,14 @@ import * as Misskey from 'misskey-js';
import {
emojilist,
emojiCharByCategory,
- UnicodeEmojiDef,
unicodeEmojiCategories as categories,
getEmojiName,
- CustomEmojiFolderTree,
getUnicodeEmoji,
} from '@@/js/emojilist.js';
+import type {
+ UnicodeEmojiDef,
+ CustomEmojiFolderTree,
+} from '@@/js/emojilist.js';
import XSection from '@/components/MkEmojiPicker.section.vue';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import * as os from '@/os.js';
@@ -580,7 +582,7 @@ defineExpose({
&:disabled {
cursor: not-allowed;
- background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
+ background: linear-gradient(-45deg, transparent 0% 48%, light-dark(rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.15)) 48% 52%, transparent 52% 100%);
opacity: 1;
> .emoji {
@@ -615,7 +617,7 @@ defineExpose({
&:disabled {
cursor: not-allowed;
- background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
+ background: linear-gradient(-45deg, transparent 0% 48%, light-dark(rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.15)) 48% 52%, transparent 52% 100%);
opacity: 1;
> .emoji {
@@ -736,7 +738,7 @@ defineExpose({
&:disabled {
cursor: not-allowed;
- background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
+ background: linear-gradient(-45deg, transparent 0% 48%, light-dark(rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.15)) 48% 52%, transparent 52% 100%);
opacity: 1;
> .emoji {
diff --git a/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts b/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts
index 6763f7c546..f531762710 100644
--- a/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts
+++ b/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkExtensionInstaller from './MkExtensionInstaller.vue';
import lightTheme from '@@/themes/_light.json5';
diff --git a/packages/frontend/src/components/MkFlashPreview.stories.impl.ts b/packages/frontend/src/components/MkFlashPreview.stories.impl.ts
index fa5288b73d..4a751062c9 100644
--- a/packages/frontend/src/components/MkFlashPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkFlashPreview.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkFlashPreview from './MkFlashPreview.vue';
import { flash } from './../../.storybook/fakes.js';
export const Public = {
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
index a433ad680b..616e04aabb 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { galleryPost } from '../../.storybook/fakes.js';
import MkGalleryPostPreview from './MkGalleryPostPreview.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts b/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts
new file mode 100644
index 0000000000..339e6d10f3
--- /dev/null
+++ b/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { StoryObj } from '@storybook/vue3';
+import { file } from '../../.storybook/fakes.js';
+import MkImgPreviewDialog from './MkImgPreviewDialog.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkImgPreviewDialog,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '<MkImgPreviewDialog v-bind="props" />',
+ };
+ },
+ args: {
+ file: file(),
+ },
+ parameters: {
+ chromatic: {
+ // NOTE: ロードが終わるまで待つ
+ delay: 3000,
+ },
+ layout: 'centered',
+ },
+} satisfies StoryObj<typeof MkImgPreviewDialog>;
diff --git a/packages/frontend/src/components/MkImgPreviewDialog.vue b/packages/frontend/src/components/MkImgPreviewDialog.vue
new file mode 100644
index 0000000000..3e6e4e0ec9
--- /dev/null
+++ b/packages/frontend/src/components/MkImgPreviewDialog.vue
@@ -0,0 +1,58 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkModalWindow
+ ref="modal"
+ :width="1800"
+ :height="900"
+ @close="close"
+ @esc="close"
+ @click="close"
+>
+ <template #header>{{ file.name }}</template>
+ <div :class="$style.container">
+ <img :src="file.url" :alt="file.comment ?? file.name" :class="$style.img"/>
+ </div>
+</MkModalWindow>
+</template>
+<script lang="ts" setup>
+import { defineProps, ref } from 'vue';
+import MkModalWindow from './MkModalWindow.vue';
+import type * as Misskey from 'misskey-js';
+
+defineProps<{
+ file: Misskey.entities.DriveFile;
+}>();
+
+const modal = ref<typeof MkModalWindow | null>(null);
+
+function close() {
+ modal.value?.close();
+}
+
+</script>
+<style lang="scss" module>
+ .container {
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ min-height: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+
+ background-color: var(--MI_THEME-bg);
+ background-size: auto auto;
+ background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--MI_THEME-panel) 6px, var(--MI_THEME-panel) 12px);
+ }
+
+ .img {
+ width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ }
+</style>
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index 08817fd6a8..739061bce1 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -44,12 +44,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs, InputHTMLAttributes } from 'vue';
+import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
+import type { InputHTMLAttributes } from 'vue';
import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue';
import { useInterval } from '@@/js/use-interval.js';
import { i18n } from '@/i18n.js';
-import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js';
+import { Autocomplete } from '@/scripts/autocomplete.js';
+import type { SuggestionType } from '@/scripts/autocomplete.js';
const props = defineProps<{
modelValue: string | number | null;
diff --git a/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts b/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
index 9e8de9d878..b9d203ee80 100644
--- a/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
+++ b/packages/frontend/src/components/MkInstanceCardMini.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { federationInstance } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index d8066857fe..c2860ed89b 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -94,7 +94,8 @@ 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 MkHeatmap from '@/components/MkHeatmap.vue';
+import type { HeatmapSource } from '@/components/MkHeatmap.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue
index 70c33a692d..1da3f14ad4 100644
--- a/packages/frontend/src/components/MkInstanceTicker.vue
+++ b/packages/frontend/src/components/MkInstanceTicker.vue
@@ -11,7 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, type CSSProperties } from 'vue';
+import { computed } from 'vue';
+import type { CSSProperties } from 'vue';
import { instanceName as localInstanceName } from '@@/js/config.js';
import { instance as localInstance } from '@/instance.js';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
diff --git a/packages/frontend/src/components/MkInviteCode.stories.impl.ts b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
index 456d215288..ccdebf0a4d 100644
--- a/packages/frontend/src/components/MkInviteCode.stories.impl.ts
+++ b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue
index bda2161eb8..a276bf68b4 100644
--- a/packages/frontend/src/components/MkLink.vue
+++ b/packages/frontend/src/components/MkLink.vue
@@ -20,7 +20,7 @@ import { url as local } from '@@/js/config.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import * as os from '@/os.js';
import { isEnabledUrlPreview } from '@/instance.js';
-import { MkABehavior } from '@/components/global/MkA.vue';
+import type { MkABehavior } from '@/components/global/MkA.vue';
const props = withDefaults(defineProps<{
url: string;
diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue
index 8b713b2734..8cf88abaaf 100644
--- a/packages/frontend/src/components/MkMediaAudio.vue
+++ b/packages/frontend/src/components/MkMediaAudio.vue
@@ -91,10 +91,11 @@ SPDX-License-Identifier: AGPL-3.0-only
import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
+import type { Keymap } from '@/scripts/hotkey.js';
+import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
-import { type Keymap } from '@/scripts/hotkey.js';
import bytes from '@/filters/bytes.js';
import { hms } from '@/filters/hms.js';
import MkMediaRange from '@/components/MkMediaRange.vue';
@@ -216,10 +217,9 @@ function showMenu(ev: MouseEvent) {
});
}
+ const details: MenuItem[] = [];
if ($i?.id === props.audio.userId) {
- menu.push({
- type: 'divider',
- }, {
+ details.push({
type: 'link',
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
@@ -227,6 +227,29 @@ function showMenu(ev: MouseEvent) {
});
}
+ if (iAmModerator) {
+ details.push({
+ type: 'link',
+ text: i18n.ts.moderation,
+ icon: 'ti ti-photo-exclamation',
+ to: `/admin/file/${props.audio.id}`,
+ });
+ }
+
+ if (details.length > 0) {
+ menu.push({ type: 'divider' }, ...details);
+ }
+
+ if (defaultStore.state.devMode) {
+ menu.push({ type: 'divider' }, {
+ icon: 'ti ti-id',
+ text: i18n.ts.copyFileId,
+ action: () => {
+ copyToClipboard(props.audio.id);
+ },
+ });
+ }
+
menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right',
diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue
index ec85569df5..3645a4a66e 100644
--- a/packages/frontend/src/components/MkMediaImage.vue
+++ b/packages/frontend/src/components/MkMediaImage.vue
@@ -54,6 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { watch, ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
+import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import bytes from '@/filters/bytes.js';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
@@ -132,10 +133,9 @@ function showMenu(ev: MouseEvent) {
});
}
+ const details: MenuItem[] = [];
if ($i?.id === props.image.userId) {
- menuItems.push({
- type: 'divider',
- }, {
+ details.push({
type: 'link',
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
@@ -143,6 +143,29 @@ function showMenu(ev: MouseEvent) {
});
}
+ if (iAmModerator) {
+ details.push({
+ type: 'link',
+ text: i18n.ts.moderation,
+ icon: 'ti ti-photo-exclamation',
+ to: `/admin/file/${props.image.id}`,
+ });
+ }
+
+ if (details.length > 0) {
+ menuItems.push({ type: 'divider' }, ...details);
+ }
+
+ if (defaultStore.state.devMode) {
+ menuItems.push({ type: 'divider' }, {
+ icon: 'ti ti-id',
+ text: i18n.ts.copyFileId,
+ action: () => {
+ copyToClipboard(props.image.id);
+ },
+ });
+ }
+
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}
diff --git a/packages/frontend/src/components/MkMediaRange.vue b/packages/frontend/src/components/MkMediaRange.vue
index df7505b0c3..9689dc5cfa 100644
--- a/packages/frontend/src/components/MkMediaRange.vue
+++ b/packages/frontend/src/components/MkMediaRange.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { computed, ModelRef } from 'vue';
+import { computed } from 'vue';
withDefaults(defineProps<{
buffer?: number;
@@ -28,8 +28,7 @@ const emit = defineEmits<{
(ev: 'dragEnded', value: number): void;
}>();
-// eslint-disable-next-line no-undef
-const model = defineModel({ required: true }) as ModelRef<string | number>;
+const model = defineModel<string | number>({ required: true });
const modelValue = computed({
get: () => typeof model.value === 'number' ? model.value : parseFloat(model.value),
set: v => { model.value = v; },
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index 65e4a1eb12..26372e1a52 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -112,7 +112,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
-import { type Keymap } from '@/scripts/hotkey.js';
+import type { Keymap } from '@/scripts/hotkey.js';
+import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import bytes from '@/filters/bytes.js';
import { hms } from '@/filters/hms.js';
import { defaultStore } from '@/store.js';
@@ -241,10 +242,9 @@ function showMenu(ev: MouseEvent) {
});
}
+ const details: MenuItem[] = [];
if ($i?.id === props.video.userId) {
- menu.push({
- type: 'divider',
- }, {
+ details.push({
type: 'link',
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
@@ -252,6 +252,29 @@ function showMenu(ev: MouseEvent) {
});
}
+ if (iAmModerator) {
+ details.push({
+ type: 'link',
+ text: i18n.ts.moderation,
+ icon: 'ti ti-photo-exclamation',
+ to: `/admin/file/${props.video.id}`,
+ });
+ }
+
+ if (details.length > 0) {
+ menu.push({ type: 'divider' }, ...details);
+ }
+
+ if (defaultStore.state.devMode) {
+ menu.push({ type: 'divider' }, {
+ icon: 'ti ti-id',
+ text: i18n.ts.copyFileId,
+ action: () => {
+ copyToClipboard(props.video.id);
+ },
+ });
+ }
+
menuShowing.value = true;
os.popupMenu(menu, ev.currentTarget ?? ev.target, {
align: 'right',
diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue
index 5ceeeee255..8616081423 100644
--- a/packages/frontend/src/components/MkMention.vue
+++ b/packages/frontend/src/components/MkMention.vue
@@ -20,7 +20,7 @@ import { host as localHost } from '@@/js/config.js';
import { $i } from '@/account.js';
import { defaultStore } from '@/store.js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
-import { MkABehavior } from '@/components/global/MkA.vue';
+import type { MkABehavior } from '@/components/global/MkA.vue';
const props = defineProps<{
username: string;
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index 13a65e411f..d484c1b338 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -178,11 +178,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts">
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } from 'vue';
import MkSwitchButton from '@/components/MkSwitch.button.vue';
-import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
+import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { isTouchUsing } from '@/scripts/touch.js';
-import { type Keymap } from '@/scripts/hotkey.js';
+import type { Keymap } from '@/scripts/hotkey.js';
import { isFocusable } from '@/scripts/focus.js';
import { getNodeOrNull } from '@/scripts/get-dom-node-or-null.js';
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index a446dad0ab..19588003fa 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -47,7 +47,7 @@ import * as os from '@/os.js';
import { isTouchUsing } from '@/scripts/touch.js';
import { defaultStore } from '@/store.js';
import { deviceKind } from '@/scripts/device-kind.js';
-import { type Keymap } from '@/scripts/hotkey.js';
+import type { Keymap } from '@/scripts/hotkey.js';
import { focusTrap } from '@/scripts/focus-trap.js';
import { focusParent } from '@/scripts/focus.js';
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index a23ff9b48e..193dfe5b7e 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -177,7 +177,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
+import { computed, inject, onMounted, ref, shallowRef, watch, provide } from 'vue';
+import type { Ref } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
@@ -195,7 +196,8 @@ import MkPoll from '@/components/MkPoll.vue';
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
-import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js';
+import { pleaseLogin } from '@/scripts/please-login.js';
+import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
import { checkWordMute } from '@/scripts/check-word-mute.js';
import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js';
@@ -217,7 +219,7 @@ import { getNoteSummary } from '@/scripts/get-note-summary.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { isEnabledUrlPreview } from '@/instance.js';
-import { type Keymap } from '@/scripts/hotkey.js';
+import type { Keymap } from '@/scripts/hotkey.js';
import { focusPrev, focusNext } from '@/scripts/focus.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
@@ -487,7 +489,16 @@ function react(): void {
}
} else {
blur();
- reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+ reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
+ if (defaultStore.state.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
if (props.mock) {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 9d3374d433..d5bd4ad133 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -225,7 +225,8 @@ import MkPoll from '@/components/MkPoll.vue';
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
import MkUrlPreview from '@/components/MkUrlPreview.vue';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
-import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js';
+import { pleaseLogin } from '@/scripts/please-login.js';
+import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js';
import { notePage } from '@/filters/note.js';
@@ -246,16 +247,17 @@ import { claimAchievement } from '@/scripts/achievements.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkPagination, { type Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue';
import { isEnabledUrlPreview } from '@/instance.js';
import { getAppearNote } from '@/scripts/get-appear-note.js';
-import { type Keymap } from '@/scripts/hotkey.js';
+import type { Keymap } from '@/scripts/hotkey.js';
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
- initialTab: string;
+ initialTab?: string;
}>(), {
initialTab: 'replies',
});
@@ -450,7 +452,16 @@ function react(): void {
}
} else {
blur();
- reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+ reactionPicker.show(reactButton.value ?? null, note.value, async (reaction) => {
+ if (defaultStore.state.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
misskeyApi('notes/reactions/create', {
diff --git a/packages/frontend/src/components/MkNoteMediaGrid.vue b/packages/frontend/src/components/MkNoteMediaGrid.vue
index bf105c3c27..e51ea5a2de 100644
--- a/packages/frontend/src/components/MkNoteMediaGrid.vue
+++ b/packages/frontend/src/components/MkNoteMediaGrid.vue
@@ -4,43 +4,43 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
- <template v-for="file in note.files">
- <div
- v-if="(((
- (defaultStore.state.nsfw === 'force' || file.isSensitive) &&
- defaultStore.state.nsfw !== 'ignore'
- ) || (defaultStore.state.dataSaver.media && file.type.startsWith('image/'))) &&
- !showingFiles.has(file.id)
- )"
- :class="[$style.filePreview, { [$style.square]: square }]"
- @click="showingFiles.add(file.id)"
- >
- <MkDriveFileThumbnail
- :file="file"
- fit="cover"
- :highlightWhenSensitive="defaultStore.state.highlightSensitiveMedia"
- :forceBlurhash="true"
- :large="true"
- :class="$style.file"
- />
- <div :class="$style.sensitive">
- <div>
- <div v-if="file.isSensitive"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media && file.size ? ` (${bytes(file.size)})` : '' }}</div>
- <div v-else><i class="ti ti-photo"></i> {{ defaultStore.state.dataSaver.media && file.size ? bytes(file.size) : i18n.ts.image }}</div>
- <div>{{ i18n.ts.clickToShow }}</div>
- </div>
+<template v-for="file in note.files">
+ <div
+ v-if="(((
+ (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>
- <MkA v-else :class="[$style.filePreview, { [$style.square]: square }]" :to="notePage(note)">
- <MkDriveFileThumbnail
- :file="file"
- fit="cover"
- :highlightWhenSensitive="defaultStore.state.highlightSensitiveMedia"
- :large="true"
- :class="$style.file"
- />
- </MkA>
- </template>
+ </div>
+ <MkA v-else :class="[$style.filePreview, { [$style.square]: square }]" :to="notePage(note)">
+ <MkDriveFileThumbnail
+ :file="file"
+ fit="cover"
+ :highlightWhenSensitive="defaultStore.state.highlightSensitiveMedia"
+ :large="true"
+ :class="$style.file"
+ />
+ </MkA>
+</template>
</template>
<script lang="ts" setup>
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index 1c17c6b691..344b7c4dd2 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -35,7 +35,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { shallowRef } from 'vue';
import MkNote from '@/components/MkNote.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index 093bdb8b4c..80cb9a45bb 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root">
<div :class="$style.head">
<MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/>
- <MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
+ <MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'createToken'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
@@ -27,6 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
[$style.t_login]: notification.type === 'login',
+ [$style.t_createToken]: notification.type === 'createToken',
[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
}]"
>
@@ -41,6 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
<i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i>
<i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i>
+ <i v-else-if="notification.type === 'createToken'" class="ti ti-key"></i>
<template v-else-if="notification.type === 'roleAssigned'">
<img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
<i v-else class="ti ti-badges"></i>
@@ -61,6 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span>
+ <span v-else-if="notification.type === 'createToken'">{{ i18n.ts._notification.createToken }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
@@ -107,6 +110,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
{{ i18n.ts.showFile }}
</MkA>
+ <MkA v-else-if="notification.type === 'createToken'" :class="$style.text" to="/settings/apps">
+ <Mfm :text="i18n.tsx._notification.createTokenDescription({ text: i18n.ts.manageAccessTokens })"/>
+ </MkA>
<template v-else-if="notification.type === 'follow'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
</template>
@@ -357,6 +363,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
pointer-events: none;
}
+.t_createToken {
+ padding: 3px;
+ background: var(--eventOther);
+ pointer-events: none;
+}
+
.tail {
flex: 1;
min-width: 0;
diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue
index d07827d11a..a0fb7fea83 100644
--- a/packages/frontend/src/components/MkNotificationSelectWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue
@@ -30,7 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, Ref, shallowRef } from 'vue';
+import { ref, shallowRef } from 'vue';
+import type { Ref } from 'vue';
import MkSwitch from './MkSwitch.vue';
import MkInfo from './MkInfo.vue';
import MkButton from './MkButton.vue';
@@ -38,7 +39,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
import { notificationTypes } from '@@/js/const.js';
import { i18n } from '@/i18n.js';
-type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>
+type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>;
const emit = defineEmits<{
(ev: 'done', v: { excludeTypes: string[] }): void,
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 5a6ada474a..470837ace5 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -31,7 +31,7 @@ import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import MkNote from '@/components/MkNote.vue';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
-import { notificationTypes } from '@@/js/const.js';
+import type { notificationTypes } from '@@/js/const.js';
import { infoImageUrl } from '@/instance.js';
import { defaultStore } from '@/store.js';
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 9547423227..e725d2a15d 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -32,17 +32,19 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
import { url } from '@@/js/config.js';
import { getScrollContainer } from '@@/js/scroll.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import RouterView from '@/components/global/RouterView.vue';
import MkWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/scripts/popout.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { useScrollPositionManager } from '@/nirax.js';
import { i18n } from '@/i18n.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { useRouterFactory } from '@/router/supplier.js';
import { mainRouter } from '@/router/main.js';
+import { analytics } from '@/analytics.js';
const props = defineProps<{
initialPath: string;
@@ -98,6 +100,14 @@ windowRouter.addListener('replace', ctx => {
history.value.push({ path: ctx.path, key: ctx.key });
});
+windowRouter.addListener('change', ctx => {
+ console.log('windowRouter: change', ctx.path);
+ analytics.page({
+ path: ctx.path,
+ title: ctx.path,
+ });
+});
+
windowRouter.init();
provide('router', windowRouter);
@@ -159,6 +169,11 @@ function popout() {
useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter);
onMounted(() => {
+ analytics.page({
+ path: props.initialPath,
+ title: props.initialPath,
+ });
+
openingWindowsCount.value++;
if (openingWindowsCount.value >= 3) {
claimAchievement('open3windows');
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index ea299c319e..d9135ab517 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -43,14 +43,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
-import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
+import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
+import type { ComputedRef } from 'vue';
import * as Misskey from 'misskey-js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
-import { MisskeyEntity } from '@/types/date-separated-list.js';
+import type { MisskeyEntity } from '@/types/date-separated-list.js';
import { i18n } from '@/i18n.js';
const SECOND_FETCH_LIMIT = 30;
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 5d0716ef37..ad0a332f99 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -65,7 +65,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
- <input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
+ <div v-show="useCw" :class="$style.cwOuter">
+ <input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
+ <div v-if="maxCwTextLength - cwTextLength < 20" :class="['_acrylic', $style.cwTextCount, { [$style.cwTextOver]: cwTextLength > maxCwTextLength }]">{{ maxCwTextLength - cwTextLength }}</div>
+ </div>
<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
@@ -100,7 +103,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, type ShallowRef } from 'vue';
+import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
+import type { ShallowRef } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
@@ -110,7 +114,8 @@ 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';
-import MkPollEditor, { type PollEditorModelValue } from '@/components/MkPollEditor.vue';
+import MkPollEditor from '@/components/MkPollEditor.vue';
+import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
import { erase, unique } from '@/scripts/array.js';
import { extractMentions } from '@/scripts/extract-mentions.js';
import { formatTimeString } from '@/scripts/format-time-string.js';
@@ -242,6 +247,12 @@ const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 1000;
});
+const cwTextLength = computed((): number => {
+ return cw.value?.length ?? 0;
+});
+
+const maxCwTextLength = 100;
+
const canPost = computed((): boolean => {
return !props.mock && !posting.value && !posted.value &&
(
@@ -252,6 +263,7 @@ const canPost = computed((): boolean => {
quoteId.value != null
) &&
(textLength.value <= maxTextLength.value) &&
+ (cwTextLength.value <= maxCwTextLength) &&
(files.value.length <= 16) &&
(!poll.value || poll.value.choices.length >= 2);
});
@@ -719,6 +731,14 @@ function deleteDraft() {
miLocalStorage.setItem('drafts', JSON.stringify(draftData));
}
+function isAnnoying(text: string): boolean {
+ return text.includes('$[x2') ||
+ text.includes('$[x3') ||
+ text.includes('$[x4') ||
+ text.includes('$[scale') ||
+ text.includes('$[position');
+}
+
async function post(ev?: MouseEvent) {
if (useCw.value && (cw.value == null || cw.value.trim() === '')) {
os.alert({
@@ -743,14 +763,10 @@ async function post(ev?: MouseEvent) {
if (props.mock) return;
- const annoying =
- text.value.includes('$[x2') ||
- text.value.includes('$[x3') ||
- text.value.includes('$[x4') ||
- text.value.includes('$[scale') ||
- text.value.includes('$[position');
-
- if (annoying && visibility.value === 'public') {
+ if (visibility.value === 'public' && (
+ (useCw.value && cw.value != null && cw.value.trim() !== '' && isAnnoying(cw.value)) || // CWが迷惑になる場合
+ ((!useCw.value || cw.value == null || cw.value.trim() === '') && text.value != null && text.value.trim() !== '' && isAnnoying(text.value)) // CWが無い かつ 本文が迷惑になる場合
+ )) {
const { canceled, result } = await os.actions({
type: 'warning',
text: i18n.ts.thisPostMayBeAnnoying,
@@ -1164,7 +1180,7 @@ defineExpose({
border-radius: 6px;
&:hover {
- background: var(--MI_THEME-X5);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
&:disabled {
@@ -1236,7 +1252,7 @@ html[data-color-scheme=light] .preview {
margin-right: 14px;
padding: 8px 0 8px 8px;
border-radius: 8px;
- background: var(--MI_THEME-X4);
+ background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
}
.hasNotSpecifiedMentions {
@@ -1267,12 +1283,34 @@ html[data-color-scheme=light] .preview {
}
}
+.cwOuter {
+ width: 100%;
+ position: relative;
+}
+
.cw {
z-index: 1;
padding-bottom: 8px;
border-bottom: solid 0.5px var(--MI_THEME-divider);
}
+.cwTextCount {
+ position: absolute;
+ top: 0;
+ right: 2px;
+ padding: 2px 6px;
+ font-size: .9em;
+ color: var(--MI_THEME-warn);
+ border-radius: 6px;
+ max-width: 100%;
+ min-width: 1.6em;
+ text-align: center;
+
+ &.cwTextOver {
+ color: #ff2a2a;
+ }
+}
+
.hashtags {
z-index: 1;
padding-top: 8px;
@@ -1347,7 +1385,7 @@ html[data-color-scheme=light] .preview {
border-radius: 6px;
&:hover {
- background: var(--MI_THEME-X5);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
&.footerButtonActive {
diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue
index 1336b61356..f2d6c7e2cd 100644
--- a/packages/frontend/src/components/MkPostFormAttaches.vue
+++ b/packages/frontend/src/components/MkPostFormAttaches.vue
@@ -22,20 +22,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
</Sortable>
- <p :class="[$style.remain, {
- [$style.exceeded]: props.modelValue.length > 16,
- }]">{{ 16 - props.modelValue.length }}/16</p>
+ <p
+ :class="[$style.remain, {
+ [$style.exceeded]: props.modelValue.length > 16,
+ }]"
+ >
+ {{ 16 - props.modelValue.length }}/16
+ </p>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, inject } from 'vue';
import * as Misskey from 'misskey-js';
+import type { MenuItem } from '@/types/menu';
+import { defaultStore } from '@/store';
+import { copyToClipboard } from '@/scripts/copy-to-clipboard';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
-import type { MenuItem } from '@/types/menu.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@@ -168,6 +174,14 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
text: i18n.ts.cropImage,
icon: 'ti ti-crop',
action: () : void => { crop(file); },
+ }, {
+ text: i18n.ts.preview,
+ icon: 'ti ti-photo-search',
+ action: () => {
+ os.popup(defineAsyncComponent(() => import('@/components/MkImgPreviewDialog.vue')), {
+ file: file,
+ });
+ },
});
}
@@ -184,6 +198,16 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
action: () => { detachAndDeleteMedia(file); },
});
+ if (defaultStore.state.devMode) {
+ menuItems.push({ type: 'divider' }, {
+ icon: 'ti ti-id',
+ text: i18n.ts.copyFileId,
+ action: () => {
+ copyToClipboard(file.id);
+ },
+ });
+ }
+
os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => menuShowing = false);
menuShowing = true;
}
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index af81eb814d..559399d1d4 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -4,7 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<script lang="ts">
-import { VNode, defineComponent, h, ref, watch } from 'vue';
+import { defineComponent, h, ref, watch } from 'vue';
+import type { VNode } from 'vue';
import MkRadio from './MkRadio.vue';
export default defineComponent({
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index b65038aadc..41e475eade 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -90,6 +90,15 @@ async function toggleReaction() {
}
});
} else {
+ if (defaultStore.state.confirmOnReact) {
+ const confirm = await os.confirm({
+ type: 'question',
+ text: i18n.tsx.reactAreYouSure({ emoji: props.reaction.replace('@.', '') }),
+ });
+
+ if (confirm.canceled) return;
+ }
+
sound.playMisskeySfx('reaction');
if (mock) {
diff --git a/packages/frontend/src/components/MkRemoteCaution.vue b/packages/frontend/src/components/MkRemoteCaution.vue
index a56a4b1671..2d3b7d107d 100644
--- a/packages/frontend/src/components/MkRemoteCaution.vue
+++ b/packages/frontend/src/components/MkRemoteCaution.vue
@@ -4,14 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div :class="$style.root"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i>{{ i18n.ts.remoteUserCaution }}<a :class="$style.link" :href="href" rel="nofollow noopener" target="_blank">{{ i18n.ts.showOnRemote }}</a></div>
+<div :class="$style.root"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i>{{ i18n.ts.remoteUserCaution }}<a v-if="href" :class="$style.link" :href="href" rel="nofollow noopener" target="_blank">{{ i18n.ts.showOnRemote }}</a></div>
</template>
<script lang="ts" setup>
import { i18n } from '@/i18n.js';
defineProps<{
- href: string;
+ href?: string;
}>();
</script>
diff --git a/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts
index 411d62edf9..b090f0a0fa 100644
--- a/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http, HttpResponse } from 'msw';
import { role } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index eeadd49936..15717802ec 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -40,11 +40,29 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue';
+import { onMounted, nextTick, ref, watch, computed, toRefs, useSlots } from 'vue';
import { useInterval } from '@@/js/use-interval.js';
+import type { VNode, VNodeChild } from 'vue';
import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
+type ItemOption = {
+ type?: 'option';
+ value: string | number | null;
+ label: string;
+};
+
+type ItemGroup = {
+ type: 'group';
+ label: string;
+ items: ItemOption[];
+};
+
+export type MkSelectItem = ItemOption | ItemGroup;
+
+// TODO: itemsをslot内のoptionで指定する用法は廃止する(props.itemsを必須化する)
+// see: https://github.com/misskey-dev/misskey/issues/15558
+
const props = defineProps<{
modelValue: string | number | null;
required?: boolean;
@@ -55,6 +73,7 @@ const props = defineProps<{
inline?: boolean;
small?: boolean;
large?: boolean;
+ items?: MkSelectItem[];
}>();
const emit = defineEmits<{
@@ -106,7 +125,30 @@ onMounted(() => {
});
});
-watch(modelValue, () => {
+watch([modelValue, () => props.items], () => {
+ if (props.items) {
+ let found: ItemOption | null = null;
+ for (const item of props.items) {
+ if (item.type === 'group') {
+ for (const option of item.items) {
+ if (option.value === modelValue.value) {
+ found = option;
+ break;
+ }
+ }
+ } else {
+ if (item.value === modelValue.value) {
+ found = item;
+ break;
+ }
+ }
+ }
+ if (found) {
+ currentValueText.value = found.label;
+ }
+ return;
+ }
+
const scanOptions = (options: VNodeChild[]) => {
for (const vnode of options) {
if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
@@ -129,7 +171,7 @@ watch(modelValue, () => {
};
scanOptions(slots.default!());
-}, { immediate: true });
+}, { immediate: true, deep: true });
function show() {
if (opening.value) return;
@@ -138,41 +180,70 @@ function show() {
opening.value = true;
const menu: MenuItem[] = [];
- let options = slots.default!();
- const pushOption = (option: VNode) => {
- menu.push({
- text: option.children as string,
- active: computed(() => modelValue.value === option.props?.value),
- action: () => {
- emit('update:modelValue', option.props?.value);
- },
- });
- };
-
- const scanOptions = (options: VNodeChild[]) => {
- for (const vnode of options) {
- if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
- if (vnode.type === 'optgroup') {
- const optgroup = vnode;
+ if (props.items) {
+ for (const item of props.items) {
+ if (item.type === 'group') {
menu.push({
type: 'label',
- text: optgroup.props?.label,
+ text: item.label,
});
- if (Array.isArray(optgroup.children)) scanOptions(optgroup.children);
- } else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある
- const fragment = vnode;
- if (Array.isArray(fragment.children)) scanOptions(fragment.children);
- } else if (vnode.props == null) { // v-if で条件が false のときにこうなる
- // nop?
+ for (const option of item.items) {
+ menu.push({
+ text: option.label,
+ active: computed(() => modelValue.value === option.value),
+ action: () => {
+ emit('update:modelValue', option.value);
+ },
+ });
+ }
} else {
- const option = vnode;
- pushOption(option);
+ menu.push({
+ text: item.label,
+ active: computed(() => modelValue.value === item.value),
+ action: () => {
+ emit('update:modelValue', item.value);
+ },
+ });
}
}
- };
+ } else {
+ let options = slots.default!();
+
+ const pushOption = (option: VNode) => {
+ menu.push({
+ text: option.children as string,
+ active: computed(() => modelValue.value === option.props?.value),
+ action: () => {
+ emit('update:modelValue', option.props?.value);
+ },
+ });
+ };
- scanOptions(options);
+ const scanOptions = (options: VNodeChild[]) => {
+ for (const vnode of options) {
+ if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
+ if (vnode.type === 'optgroup') {
+ const optgroup = vnode;
+ menu.push({
+ type: 'label',
+ text: optgroup.props?.label,
+ });
+ if (Array.isArray(optgroup.children)) scanOptions(optgroup.children);
+ } else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある
+ const fragment = vnode;
+ if (Array.isArray(fragment.children)) scanOptions(fragment.children);
+ } else if (vnode.props == null) { // v-if で条件が false のときにこうなる
+ // nop?
+ } else {
+ const option = vnode;
+ pushOption(option);
+ }
+ }
+ };
+
+ scanOptions(options);
+ }
os.popupMenu(menu, container.value, {
width: container.value?.offsetWidth,
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index 5a27cd6de7..2fb97e8e46 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -77,7 +77,8 @@ import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import XInput from '@/components/MkSignin.input.vue';
-import XPassword, { type PwResponse } from '@/components/MkSignin.password.vue';
+import XPassword from '@/components/MkSignin.password.vue';
+import type { PwResponse } from '@/components/MkSignin.password.vue';
import XTotp from '@/components/MkSignin.totp.vue';
import XPasskey from '@/components/MkSignin.passkey.vue';
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index 3a7d896bff..4227e1317a 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -85,7 +85,8 @@ import * as Misskey from 'misskey-js';
import * as config from '@@/js/config.js';
import MkButton from './MkButton.vue';
import MkInput from './MkInput.vue';
-import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
+import MkCaptcha from '@/components/MkCaptcha.vue';
+import type { Captcha } from '@/components/MkCaptcha.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { login } from '@/account.js';
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
index 9df3ec0c30..8d99bc44b7 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
+++ b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { onBeforeUnmount } from 'vue';
import MkSignupServerRules from './MkSignupDialog.rules.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/components/MkSortOrderEditor.define.ts b/packages/frontend/src/components/MkSortOrderEditor.define.ts
index f023b5d72b..e56b93f98a 100644
--- a/packages/frontend/src/components/MkSortOrderEditor.define.ts
+++ b/packages/frontend/src/components/MkSortOrderEditor.define.ts
@@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-export type SortOrderDirection = '+' | '-'
+export type SortOrderDirection = '+' | '-';
export type SortOrder<T extends string> = {
key: T;
direction: SortOrderDirection;
-}
+};
diff --git a/packages/frontend/src/components/MkSortOrderEditor.vue b/packages/frontend/src/components/MkSortOrderEditor.vue
index 9decacc5f5..27ffc724ae 100644
--- a/packages/frontend/src/components/MkSortOrderEditor.vue
+++ b/packages/frontend/src/components/MkSortOrderEditor.vue
@@ -27,9 +27,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { toRefs } from 'vue';
import MkTagItem from '@/components/MkTagItem.vue';
import MkButton from '@/components/MkButton.vue';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
-import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
+import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
const emit = defineEmits<{
(ev: 'update', sortOrders: SortOrder<T>[]): void;
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index fa0e40d8f9..397aa68ed6 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -45,7 +45,7 @@ export type SuperMenuDef = {
text: string;
danger?: boolean;
active?: boolean;
- action: (ev: MouseEvent) => void;
+ action: (ev: MouseEvent) => void | Promise<void>;
} | {
type?: 'link';
to: string;
diff --git a/packages/frontend/src/components/MkSwitch.button.vue b/packages/frontend/src/components/MkSwitch.button.vue
index fd8ed0992e..263b883567 100644
--- a/packages/frontend/src/components/MkSwitch.button.vue
+++ b/packages/frontend/src/components/MkSwitch.button.vue
@@ -19,7 +19,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { toRefs, Ref } from 'vue';
+import { toRefs } from 'vue';
+import type { Ref } from 'vue';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue
index 5e6029ee40..797e577fa4 100644
--- a/packages/frontend/src/components/MkSwitch.vue
+++ b/packages/frontend/src/components/MkSwitch.vue
@@ -27,7 +27,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { toRefs, Ref } from 'vue';
+import { toRefs } from 'vue';
+import type { Ref } from 'vue';
import XButton from '@/components/MkSwitch.button.vue';
const props = defineProps<{
diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue
index 485d003f93..7e92726dcb 100644
--- a/packages/frontend/src/components/MkSystemWebhookEditor.vue
+++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue
@@ -96,7 +96,7 @@ import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import {
+import type {
MkSystemWebhookEditorProps,
MkSystemWebhookResult,
SystemWebhookEventType,
@@ -114,7 +114,7 @@ type EventType = {
userCreated: boolean;
inactiveModeratorsWarning: boolean;
inactiveModeratorsInvitationOnlyChanged: boolean;
-}
+};
const emit = defineEmits<{
(ev: 'submitted', result: MkSystemWebhookResult): void;
diff --git a/packages/frontend/src/components/MkTagItem.stories.impl.ts b/packages/frontend/src/components/MkTagItem.stories.impl.ts
index 3f243ff651..ac932c8342 100644
--- a/packages/frontend/src/components/MkTagItem.stories.impl.ts
+++ b/packages/frontend/src/components/MkTagItem.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkTagItem from './MkTagItem.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index d1a6e1ebbf..3e8588018c 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -40,7 +40,8 @@ import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallow
import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
-import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js';
+import { Autocomplete } from '@/scripts/autocomplete.js';
+import type { SuggestionType } from '@/scripts/autocomplete.js';
const props = defineProps<{
modelValue: string | null;
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index fb8eb4ae37..7bae240ddd 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -27,7 +27,7 @@ import * as sound from '@/scripts/sound.js';
import { $i } from '@/account.js';
import { instance } from '@/instance.js';
import { defaultStore } from '@/store.js';
-import { Paging } from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
const props = withDefaults(defineProps<{
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
@@ -57,15 +57,15 @@ provide('tl_withSensitive', computed(() => props.withSensitive));
provide('inChannel', computed(() => props.src === 'channel'));
type TimelineQueryType = {
- antennaId?: string,
- withRenotes?: boolean,
- withReplies?: boolean,
- withFiles?: boolean,
- visibility?: string,
- listId?: string,
- channelId?: string,
- roleId?: string
-}
+ antennaId?: string,
+ withRenotes?: boolean,
+ withReplies?: boolean,
+ withFiles?: boolean,
+ visibility?: string,
+ listId?: string,
+ channelId?: string,
+ roleId?: string
+};
const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index f0da8fd3f2..063e5dcad2 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<iframe
v-if="player.url.startsWith('http://') || player.url.startsWith('https://')"
- sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin"
+ sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-storage-access-by-user-activation allow-same-origin"
scrolling="no"
:allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')"
:class="$style.playerIframe"
diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
index fe499fabbf..34991fa0dd 100644
--- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
+++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
@@ -62,7 +62,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
-type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; }
+type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; };
const props = defineProps<{
user: Misskey.entities.User,
diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue
index 8b4afd7994..d1881ec3fc 100644
--- a/packages/frontend/src/components/MkUserList.vue
+++ b/packages/frontend/src/components/MkUserList.vue
@@ -22,7 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import MkUserInfo from '@/components/MkUserInfo.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index 63af652cbc..1e93d9dbea 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -63,6 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref, computed, shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
+import { host as currentHost, hostname } from '@@/js/config.js';
import MkInput from '@/components/MkInput.vue';
import FormSplit from '@/components/form/split.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -71,7 +72,6 @@ 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<{
(ev: 'ok', selected: Misskey.entities.UserDetailed): void;
@@ -198,7 +198,7 @@ onMounted(() => {
font-size: 14px;
&:hover {
- background: var(--MI_THEME-X7);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
&.selected {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
index 638bfb4372..52467893a0 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
index 5153c06139..67a06c70db 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
@@ -38,7 +38,8 @@ import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
import MkFolder from '@/components/MkFolder.vue';
import XUser from '@/components/MkUserSetupDialog.User.vue';
-import MkPagination, { type Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
const pinnedUsers: Paging = {
endpoint: 'pinned-users',
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
index 2a7947c6f8..0ada259d3f 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkUserSetupDialog_Privacy from './MkUserSetupDialog.Privacy.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
index c6088a5ae3..cefd48cb01 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkUserSetupDialog_Profile from './MkUserSetupDialog.Profile.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
index f0206e0cb4..b424632bdc 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../.storybook/fakes.js';
import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue';
export const Default = {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
index 3f5ae734bd..751391c2d8 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes.js';
diff --git a/packages/frontend/src/components/global/MkA.stories.impl.ts b/packages/frontend/src/components/global/MkA.stories.impl.ts
index 02e5a7f98c..1ccf105dbb 100644
--- a/packages/frontend/src/components/global/MkA.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkA.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkA from './MkA.vue';
import { tick } from '@/scripts/test-utils.js';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 87fa9c8252..8eacf16d6d 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -15,9 +15,9 @@ export type MkABehavior = 'window' | 'browser' | null;
<script lang="ts" setup>
import { computed, inject, shallowRef } from 'vue';
+import { url } from '@@/js/config.js';
import * as os from '@/os.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { url } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router/supplier.js';
diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
index 04960ec60c..02fc835709 100644
--- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkAcct from './MkAcct.vue';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts
index 8c0b7ef52f..c5a928b5cf 100644
--- a/packages/frontend/src/components/global/MkAd.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkAd from './MkAd.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
index 9d2de9f0be..84221842e9 100644
--- a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkAvatar from './MkAvatar.vue';
const common = {
diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
index e15dcba760..15ae489ff8 100644
--- a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkCondensedLine from './MkCondensedLine.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts b/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
index 9e6177045d..eded13b686 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkCustomEmoji from './MkCustomEmoji.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts b/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
index 6a8fcf4fe3..dafdcbd13f 100644
--- a/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import MkEllipsis from './MkEllipsis.vue';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkEmoji.stories.impl.ts b/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
index 309c015757..1a394ca6bb 100644
--- a/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkEmoji from './MkEmoji.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts
index daef04cd87..e150493a18 100644
--- a/packages/frontend/src/components/global/MkError.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkError.stories.impl.ts
@@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
import { expect, waitFor } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkError from './MkError.vue';
export const Default = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkError.stories.meta.ts b/packages/frontend/src/components/global/MkError.stories.meta.ts
index cd7fada189..940b445e90 100644
--- a/packages/frontend/src/components/global/MkError.stories.meta.ts
+++ b/packages/frontend/src/components/global/MkError.stories.meta.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Meta } from '@storybook/vue3';
+import type { Meta } from '@storybook/vue3';
import MkError from './MkError.vue';
export const argTypes = {
diff --git a/packages/frontend/src/components/global/MkLoading.stories.impl.ts b/packages/frontend/src/components/global/MkLoading.stories.impl.ts
index c781ad0479..8313f73e4b 100644
--- a/packages/frontend/src/components/global/MkLoading.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkLoading.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic/isChromatic';
import MkLoading from './MkLoading.vue';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkMfm.stories.impl.ts b/packages/frontend/src/components/global/MkMfm.stories.impl.ts
index 1daf7a29cb..98da531ed4 100644
--- a/packages/frontend/src/components/global/MkMfm.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkMfm.stories.impl.ts
@@ -2,8 +2,8 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
-
-import { StoryObj } from '@storybook/vue3';
+
+import type { StoryObj } from '@storybook/vue3';
import { expect, within } from '@storybook/test';
import MkMfm from './MkMfm.js';
export const Default = {
diff --git a/packages/frontend/src/components/global/MkMfm.ts b/packages/frontend/src/components/global/MkMfm.ts
index 0d138d1f1c..48d7e34d76 100644
--- a/packages/frontend/src/components/global/MkMfm.ts
+++ b/packages/frontend/src/components/global/MkMfm.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { VNode, h, SetupContext, provide } from 'vue';
+import { h, provide } from 'vue';
+import type { VNode, SetupContext } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
@@ -17,7 +18,8 @@ import MkCode from '@/components/MkCode.vue';
import MkCodeInline from '@/components/MkCodeInline.vue';
import MkGoogle from '@/components/MkGoogle.vue';
import MkSparkle from '@/components/MkSparkle.vue';
-import MkA, { MkABehavior } from '@/components/global/MkA.vue';
+import MkA from '@/components/global/MkA.vue';
+import type { MkABehavior } from '@/components/global/MkA.vue';
import { defaultStore } from '@/store.js';
function safeParseFloat(str: unknown): number | null {
diff --git a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
index 1d079edd2c..c9af5f4ea4 100644
--- a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { waitFor } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkPageHeader from './MkPageHeader.vue';
export const Empty = {
render(args) {
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index a2e70a5cad..1070c0c83b 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -43,7 +43,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue';
import tinycolor from 'tinycolor2';
-import XTabs, { Tab } from './MkPageHeader.tabs.vue';
+import XTabs from './MkPageHeader.tabs.vue';
+import type { Tab } from './MkPageHeader.tabs.vue';
import { scrollToTop } from '@@/js/scroll.js';
import { globalEvents } from '@/events.js';
import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index 1aebf487bb..7ee3952083 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -22,7 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, useTemplateRef } from 'vue';
+import { onMounted, onUnmounted, provide, inject, ref, watch, useTemplateRef } from 'vue';
+import type { Ref } from 'vue';
import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js';
diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts
index ccf7f200b5..5e62c3fbab 100644
--- a/packages/frontend/src/components/global/MkTime.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import MkTime from './MkTime.vue';
import { i18n } from '@/i18n.js';
import { dateTimeFormat } from '@@/js/intl-const.js';
diff --git a/packages/frontend/src/components/global/MkUrl.stories.impl.ts b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
index 34a4adfe49..ea02fdfdd0 100644
--- a/packages/frontend/src/components/global/MkUrl.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect, userEvent, waitFor, within } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { commonHandlers } from '../../../.storybook/mocks.js';
import MkUrl from './MkUrl.vue';
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 4ab530b136..3d2036e376 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -31,7 +31,7 @@ import { url as local } from '@@/js/config.js';
import * as os from '@/os.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import { isEnabledUrlPreview } from '@/instance.js';
-import { MkABehavior } from '@/components/global/MkA.vue';
+import type { MkABehavior } from '@/components/global/MkA.vue';
function safeURIDecode(str: string): string {
try {
diff --git a/packages/frontend/src/components/global/MkUserName.stories.impl.ts b/packages/frontend/src/components/global/MkUserName.stories.impl.ts
index e39061c291..b46c91c903 100644
--- a/packages/frontend/src/components/global/MkUserName.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkUserName.stories.impl.ts
@@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/test';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkUserName from './MkUserName.vue';
export const Default = {
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index 38bdfc52d4..3ab3d10a40 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
-import { IRouter, Resolved, RouteDef } from '@/nirax.js';
+import type { IRouter, Resolved, RouteDef } from '@/nirax.js';
import { defaultStore } from '@/store.js';
import { globalEvents } from '@/events.js';
import MkLoadingPage from '@/pages/_loading_.vue';
diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue
index e473b7c1af..c2dc05efe6 100644
--- a/packages/frontend/src/components/grid/MkDataCell.vue
+++ b/packages/frontend/src/components/grid/MkDataCell.vue
@@ -89,12 +89,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<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 { GridEventEmitter } 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';
+import type { Size } from '@/components/grid/grid.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRowSetting } from '@/components/grid/row.js';
const emit = defineEmits<{
(ev: 'operation:beginEdit', sender: GridCell): void;
diff --git a/packages/frontend/src/components/grid/MkDataRow.vue b/packages/frontend/src/components/grid/MkDataRow.vue
index 280a14bc4a..a35f93b435 100644
--- a/packages/frontend/src/components/grid/MkDataRow.vue
+++ b/packages/frontend/src/components/grid/MkDataRow.vue
@@ -37,11 +37,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { GridEventEmitter, Size } from '@/components/grid/grid.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
import MkDataCell from '@/components/grid/MkDataCell.vue';
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
-import { CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridRow, GridRowSetting } from '@/components/grid/row.js';
+import type { Size } from '@/components/grid/grid.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRow, GridRowSetting } from '@/components/grid/row.js';
const emit = defineEmits<{
(ev: 'operation:beginEdit', sender: GridCell): void;
diff --git a/packages/frontend/src/components/grid/MkGrid.stories.impl.ts b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts
index 5801012f15..f85bf146e8 100644
--- a/packages/frontend/src/components/grid/MkGrid.stories.impl.ts
+++ b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts
@@ -5,14 +5,14 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { action } from '@storybook/addon-actions';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { ref } from 'vue';
import { commonHandlers } from '../../../.storybook/mocks.js';
import { boolean, choose, country, date, firstName, integer, lastName, text } from '../../../.storybook/fake-utils.js';
import MkGrid from './MkGrid.vue';
-import { GridContext, GridEvent } from '@/components/grid/grid-event.js';
-import { DataSource, GridSetting } from '@/components/grid/grid.js';
-import { GridColumnSetting } from '@/components/grid/column.js';
+import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
+import type { DataSource, GridSetting } from '@/components/grid/grid.js';
+import type { GridColumnSetting } from '@/components/grid/column.js';
function d(p: {
check?: boolean,
diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue
index 4dbd4ebcae..c89e23c135 100644
--- a/packages/frontend/src/components/grid/MkGrid.vue
+++ b/packages/frontend/src/components/grid/MkGrid.vue
@@ -50,11 +50,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, onMounted, ref, toRefs, watch } from 'vue';
-import { DataSource, GridEventEmitter, GridSetting, GridState, Size } from '@/components/grid/grid.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
import MkDataRow from '@/components/grid/MkDataRow.vue';
import MkHeaderRow from '@/components/grid/MkHeaderRow.vue';
import { cellValidation } from '@/components/grid/cell-validators.js';
-import { CELL_ADDRESS_NONE, CellAddress, CellValue, createCell, GridCell, resetCell } from '@/components/grid/cell.js';
+import { CELL_ADDRESS_NONE, createCell, resetCell } from '@/components/grid/cell.js';
import {
copyGridDataToClipboard,
equalCellAddress,
@@ -63,18 +63,23 @@ import {
pasteToGridFromClipboard,
removeDataFromGrid,
} from '@/components/grid/grid-utils.js';
-import { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
-import { GridContext, GridEvent } from '@/components/grid/grid-event.js';
-import { createColumn, GridColumn } from '@/components/grid/column.js';
-import { createRow, defaultGridRowSetting, GridRow, GridRowSetting, resetRow } from '@/components/grid/row.js';
+import { createColumn } from '@/components/grid/column.js';
+import { createRow, defaultGridRowSetting, resetRow } from '@/components/grid/row.js';
import { handleKeyEvent } from '@/scripts/key-event.js';
+import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js';
+import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRow, GridRowSetting } from '@/components/grid/row.js';
+import type { MenuItem } from '@/types/menu.js';
+
type RowHolder = {
row: GridRow,
cells: GridCell[],
origin: DataSource,
-}
+};
const emit = defineEmits<{
(ev: 'event', event: GridEvent, context: GridContext): void;
diff --git a/packages/frontend/src/components/grid/MkHeaderCell.vue b/packages/frontend/src/components/grid/MkHeaderCell.vue
index aecfe7eaa3..69a68b6f2c 100644
--- a/packages/frontend/src/components/grid/MkHeaderCell.vue
+++ b/packages/frontend/src/components/grid/MkHeaderCell.vue
@@ -32,8 +32,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, toRefs, watch } from 'vue';
-import { GridEventEmitter, Size } from '@/components/grid/grid.js';
-import { GridColumn } from '@/components/grid/column.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
+import type { Size } from '@/components/grid/grid.js';
+import type { GridColumn } from '@/components/grid/column.js';
const emit = defineEmits<{
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
diff --git a/packages/frontend/src/components/grid/MkHeaderRow.vue b/packages/frontend/src/components/grid/MkHeaderRow.vue
index 8affa08fd5..225f623b84 100644
--- a/packages/frontend/src/components/grid/MkHeaderRow.vue
+++ b/packages/frontend/src/components/grid/MkHeaderRow.vue
@@ -29,11 +29,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { GridEventEmitter, Size } from '@/components/grid/grid.js';
+import { GridEventEmitter } from '@/components/grid/grid.js';
import MkHeaderCell from '@/components/grid/MkHeaderCell.vue';
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
-import { GridColumn } from '@/components/grid/column.js';
-import { GridRowSetting } from '@/components/grid/row.js';
+import type { Size } from '@/components/grid/grid.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRowSetting } from '@/components/grid/row.js';
const emit = defineEmits<{
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
diff --git a/packages/frontend/src/components/grid/MkNumberCell.vue b/packages/frontend/src/components/grid/MkNumberCell.vue
index 674bba96bc..d3b5956ddd 100644
--- a/packages/frontend/src/components/grid/MkNumberCell.vue
+++ b/packages/frontend/src/components/grid/MkNumberCell.vue
@@ -19,8 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-
-import { GridRow } from '@/components/grid/row.js';
+import type { GridRow } from '@/components/grid/row.js';
defineProps<{
content: string,
diff --git a/packages/frontend/src/components/grid/cell-validators.ts b/packages/frontend/src/components/grid/cell-validators.ts
index 949cab2ec6..7310a82c9e 100644
--- a/packages/frontend/src/components/grid/cell-validators.ts
+++ b/packages/frontend/src/components/grid/cell-validators.ts
@@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridColumn } from '@/components/grid/column.js';
-import { GridRow } from '@/components/grid/row.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRow } from '@/components/grid/row.js';
import { i18n } from '@/i18n.js';
export type ValidatorParams = {
@@ -18,25 +18,25 @@ export type ValidatorParams = {
export type ValidatorResult = {
valid: boolean;
message?: string;
-}
+};
export type GridCellValidator = {
name?: string;
ignoreViolation?: boolean;
validate: (params: ValidatorParams) => ValidatorResult;
-}
+};
export type ValidateViolation = {
valid: boolean;
params: ValidatorParams;
violations: ValidateViolationItem[];
-}
+};
export type ValidateViolationItem = {
valid: boolean;
validator: GridCellValidator;
result: ValidatorResult;
-}
+};
export function cellValidation(allCells: GridCell[], cell: GridCell, newValue: CellValue): ValidateViolation {
const { column, row } = cell;
diff --git a/packages/frontend/src/components/grid/cell.ts b/packages/frontend/src/components/grid/cell.ts
index 71b7a3e3f1..d347d05bdb 100644
--- a/packages/frontend/src/components/grid/cell.ts
+++ b/packages/frontend/src/components/grid/cell.ts
@@ -3,19 +3,19 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { ValidateViolation } from '@/components/grid/cell-validators.js';
-import { Size } from '@/components/grid/grid.js';
-import { GridColumn } from '@/components/grid/column.js';
-import { GridRow } from '@/components/grid/row.js';
-import { MenuItem } from '@/types/menu.js';
-import { GridContext } from '@/components/grid/grid-event.js';
+import type { ValidateViolation } from '@/components/grid/cell-validators.js';
+import type { Size } from '@/components/grid/grid.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRow } from '@/components/grid/row.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { GridContext } from '@/components/grid/grid-event.js';
export type CellValue = string | boolean | number | undefined | null | Array<unknown> | NonNullable<unknown>;
export type CellAddress = {
row: number;
col: number;
-}
+};
export const CELL_ADDRESS_NONE: CellAddress = {
row: -1,
@@ -32,13 +32,13 @@ export type GridCell = {
contentSize: Size;
setting: GridCellSetting;
violation: ValidateViolation;
-}
+};
export type GridCellContextMenuFactory = (col: GridColumn, row: GridRow, value: CellValue, context: GridContext) => MenuItem[];
export type GridCellSetting = {
contextMenuFactory?: GridCellContextMenuFactory;
-}
+};
export function createCell(
column: GridColumn,
diff --git a/packages/frontend/src/components/grid/column.ts b/packages/frontend/src/components/grid/column.ts
index 2f505756fe..6a694b39ec 100644
--- a/packages/frontend/src/components/grid/column.ts
+++ b/packages/frontend/src/components/grid/column.ts
@@ -3,13 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { GridCellValidator } from '@/components/grid/cell-validators.js';
-import { Size, SizeStyle } from '@/components/grid/grid.js';
import { calcCellWidth } from '@/components/grid/grid-utils.js';
-import { CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridRow } from '@/components/grid/row.js';
-import { MenuItem } from '@/types/menu.js';
-import { GridContext } from '@/components/grid/grid-event.js';
+import type { GridCellValidator } from '@/components/grid/cell-validators.js';
+import type { Size, SizeStyle } from '@/components/grid/grid.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRow } from '@/components/grid/row.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { GridContext } from '@/components/grid/grid-event.js';
export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image' | 'hidden';
@@ -40,7 +40,7 @@ export type GridColumn = {
setting: GridColumnSetting;
width: string;
contentSize: Size;
-}
+};
export function createColumn(setting: GridColumnSetting, index: number): GridColumn {
return {
diff --git a/packages/frontend/src/components/grid/grid-event.ts b/packages/frontend/src/components/grid/grid-event.ts
index 074b72b956..e2f1e44055 100644
--- a/packages/frontend/src/components/grid/grid-event.ts
+++ b/packages/frontend/src/components/grid/grid-event.ts
@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridState } from '@/components/grid/grid.js';
-import { ValidateViolation } from '@/components/grid/cell-validators.js';
-import { GridColumn } from '@/components/grid/column.js';
-import { GridRow } from '@/components/grid/row.js';
+import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridState } from '@/components/grid/grid.js';
+import type { ValidateViolation } from '@/components/grid/cell-validators.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { GridRow } from '@/components/grid/row.js';
export type GridContext = {
selectedCell?: GridCell;
diff --git a/packages/frontend/src/components/grid/grid-utils.ts b/packages/frontend/src/components/grid/grid-utils.ts
index a45bc88926..4f48af194c 100644
--- a/packages/frontend/src/components/grid/grid-utils.ts
+++ b/packages/frontend/src/components/grid/grid-utils.ts
@@ -3,13 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { isRef, Ref } from 'vue';
-import { DataSource, SizeStyle } from '@/components/grid/grid.js';
-import { CELL_ADDRESS_NONE, CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
-import { GridRow } from '@/components/grid/row.js';
-import { GridContext } from '@/components/grid/grid-event.js';
+import { isRef } from 'vue';
+import type { Ref } from 'vue';
+import type { DataSource, SizeStyle } from '@/components/grid/grid.js';
+import { CELL_ADDRESS_NONE } from '@/components/grid/cell.js';
+import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRow } from '@/components/grid/row.js';
+import type { GridContext } from '@/components/grid/grid-event.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { GridColumn, GridColumnSetting } from '@/components/grid/column.js';
+import type { GridColumn, GridColumnSetting } from '@/components/grid/column.js';
export function isCellElement(elem: HTMLElement): boolean {
return elem.hasAttribute('data-grid-cell');
diff --git a/packages/frontend/src/components/grid/grid.ts b/packages/frontend/src/components/grid/grid.ts
index b82e12b304..0428e6493c 100644
--- a/packages/frontend/src/components/grid/grid.ts
+++ b/packages/frontend/src/components/grid/grid.ts
@@ -4,9 +4,9 @@
*/
import { EventEmitter } from 'eventemitter3';
-import { CellValue, GridCellSetting } from '@/components/grid/cell.js';
-import { GridColumnSetting } from '@/components/grid/column.js';
-import { GridRowSetting } from '@/components/grid/row.js';
+import type { CellValue, GridCellSetting } from '@/components/grid/cell.js';
+import type { GridColumnSetting } from '@/components/grid/column.js';
+import type { GridRowSetting } from '@/components/grid/row.js';
export type GridSetting = {
root?: {
@@ -21,7 +21,7 @@ export type GridSetting = {
export type DataSource = Record<string, CellValue>;
-export type GridState =
+export type GridState = (
'normal' |
'cellSelecting' |
'cellEditing' |
@@ -29,19 +29,19 @@ export type GridState =
'colSelecting' |
'rowSelecting' |
'hidden'
- ;
+);
export type Size = {
width: number;
height: number;
-}
+};
export type SizeStyle = number | 'auto' | undefined;
export type AdditionalStyle = {
className?: string;
style?: Record<string, string | number>;
-}
+};
export class GridEventEmitter extends EventEmitter<{
'forceRefreshContentSize': void;
diff --git a/packages/frontend/src/components/grid/row.ts b/packages/frontend/src/components/grid/row.ts
index e0a317c9d3..42da22193f 100644
--- a/packages/frontend/src/components/grid/row.ts
+++ b/packages/frontend/src/components/grid/row.ts
@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { AdditionalStyle } from '@/components/grid/grid.js';
-import { GridCell } from '@/components/grid/cell.js';
-import { GridColumn } from '@/components/grid/column.js';
-import { MenuItem } from '@/types/menu.js';
-import { GridContext } from '@/components/grid/grid-event.js';
+import type { AdditionalStyle } from '@/components/grid/grid.js';
+import type { GridCell } from '@/components/grid/cell.js';
+import type { GridColumn } from '@/components/grid/column.js';
+import type { MenuItem } from '@/types/menu.js';
+import type { GridContext } from '@/components/grid/grid-event.js';
export const defaultGridRowSetting: Required<GridRowSetting> = {
showNumber: true,
@@ -27,7 +27,7 @@ export type GridRowStyleRuleConditionParams = {
export type GridRowStyleRule = {
condition: (params: GridRowStyleRuleConditionParams) => boolean;
applyStyle: AdditionalStyle;
-}
+};
export type GridRowContextMenuFactory = (row: GridRow, context: GridContext) => MenuItem[];
@@ -40,7 +40,7 @@ export type GridRowSetting = {
events?: {
delete?: (rows: GridRow[]) => void;
}
-}
+};
export type GridRow = {
index: number;
@@ -48,7 +48,7 @@ export type GridRow = {
using: boolean;
setting: GridRowSetting;
additionalStyles: AdditionalStyle[];
-}
+};
export function createRow(index: number, using: boolean, setting: GridRowSetting): GridRow {
return {
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index b36625ed1b..0252bf0252 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { App } from 'vue';
+import type { App } from 'vue';
import Mfm from './global/MkMfm.js';
import MkA from './global/MkA.vue';
diff --git a/packages/frontend/src/debug.ts b/packages/frontend/src/debug.ts
index 8bb8012ae3..3a8f6289d1 100644
--- a/packages/frontend/src/debug.ts
+++ b/packages/frontend/src/debug.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { type ComponentInternalInstance, getCurrentInstance } from 'vue';
+import { getCurrentInstance } from 'vue';
+import type { ComponentInternalInstance } from 'vue';
export function isDebuggerEnabled(id: number): boolean {
try {
diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts
index f88996019f..98e3d91b29 100644
--- a/packages/frontend/src/directives/adaptive-bg.ts
+++ b/packages/frontend/src/directives/adaptive-bg.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
import { getBgColor } from '@/scripts/get-bg-color.js';
export default {
diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts
index 1305f312bd..4037076cae 100644
--- a/packages/frontend/src/directives/adaptive-border.ts
+++ b/packages/frontend/src/directives/adaptive-border.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
import { getBgColor } from '@/scripts/get-bg-color.js';
export default {
diff --git a/packages/frontend/src/directives/anim.ts b/packages/frontend/src/directives/anim.ts
index d5b6ae4287..ad0cb5ed81 100644
--- a/packages/frontend/src/directives/anim.ts
+++ b/packages/frontend/src/directives/anim.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
export default {
beforeMount(src, binding, vn) {
diff --git a/packages/frontend/src/directives/appear.ts b/packages/frontend/src/directives/appear.ts
index 706d4a9ee4..802477e00b 100644
--- a/packages/frontend/src/directives/appear.ts
+++ b/packages/frontend/src/directives/appear.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
export default {
mounted(src, binding, vn) {
diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts
index 5bb48bbcdd..60242837f2 100644
--- a/packages/frontend/src/directives/click-anime.ts
+++ b/packages/frontend/src/directives/click-anime.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
import { defaultStore } from '@/store.js';
export default {
diff --git a/packages/frontend/src/directives/follow-append.ts b/packages/frontend/src/directives/follow-append.ts
index 615dd99fa8..f3eaac10e3 100644
--- a/packages/frontend/src/directives/follow-append.ts
+++ b/packages/frontend/src/directives/follow-append.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
import { getScrollContainer, getScrollPosition } from '@@/js/scroll.js';
export default {
diff --git a/packages/frontend/src/directives/get-size.ts b/packages/frontend/src/directives/get-size.ts
index 2655c76c48..488f201a0d 100644
--- a/packages/frontend/src/directives/get-size.ts
+++ b/packages/frontend/src/directives/get-size.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
const mountings = new Map<Element, {
resize: ResizeObserver;
diff --git a/packages/frontend/src/directives/hotkey.ts b/packages/frontend/src/directives/hotkey.ts
index 0e5c7ede24..ec00652381 100644
--- a/packages/frontend/src/directives/hotkey.ts
+++ b/packages/frontend/src/directives/hotkey.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
import { makeHotkey } from '@/scripts/hotkey.js';
export default {
diff --git a/packages/frontend/src/directives/index.ts b/packages/frontend/src/directives/index.ts
index bda7738ccd..9555045afe 100644
--- a/packages/frontend/src/directives/index.ts
+++ b/packages/frontend/src/directives/index.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { App } from 'vue';
+import type { App } from 'vue';
import userPreview from './user-preview.js';
import getSize from './get-size.js';
diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts
index aa26b94d0b..19fd374861 100644
--- a/packages/frontend/src/directives/panel.ts
+++ b/packages/frontend/src/directives/panel.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Directive } from 'vue';
+import type { Directive } from 'vue';
import { getBgColor } from '@/scripts/get-bg-color.js';
export default {
diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts
index 251ce5675f..6bfe6ac31d 100644
--- a/packages/frontend/src/directives/tooltip.ts
+++ b/packages/frontend/src/directives/tooltip.ts
@@ -6,7 +6,8 @@
// TODO: useTooltip関数使うようにしたい
// ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明
-import { defineAsyncComponent, Directive, ref } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
+import type { Directive } from 'vue';
import { isTouchUsing } from '@/scripts/touch.js';
import { popup, alert } from '@/os.js';
diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts
index 278d842d09..43a93a0865 100644
--- a/packages/frontend/src/directives/user-preview.ts
+++ b/packages/frontend/src/directives/user-preview.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { defineAsyncComponent, Directive, ref } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
+import type { Directive } from 'vue';
import { popup } from '@/os.js';
export class UserPreview {
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
index 5b8ba77e01..9c8863f863 100644
--- a/packages/frontend/src/local-storage.ts
+++ b/packages/frontend/src/local-storage.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-export type Keys =
+export type Keys = (
'v' |
'lastVersion' |
'instance' |
@@ -40,6 +40,7 @@ export type Keys =
'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~);
`channelLastReadedAt:${string}` |
`idbfallback::${string}`
+);
// セッション毎に廃棄されるLocalStorage代替(セーフモードなどで使用できそう)
//const safeSessionStorage = new Map<Keys, string>();
diff --git a/packages/frontend/src/memory-storage.ts b/packages/frontend/src/memory-storage.ts
new file mode 100644
index 0000000000..df0dc1308f
--- /dev/null
+++ b/packages/frontend/src/memory-storage.ts
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export type MemoryStorage = {
+ has: (key: string) => boolean;
+ getItem: <T>(key: string) => T | null;
+ setItem: (key: string, value: unknown) => void;
+ removeItem: (key: string) => void;
+ clear: () => void;
+ size: number;
+};
+
+class MemoryStorageImpl implements MemoryStorage {
+ private readonly storage: Map<string, unknown>;
+
+ constructor() {
+ this.storage = new Map();
+ }
+
+ has(key: string): boolean {
+ return this.storage.has(key);
+ }
+
+ getItem<T>(key: string): T | null {
+ return this.storage.has(key) ? this.storage.get(key) as T : null;
+ }
+
+ setItem(key: string, value: unknown): void {
+ this.storage.set(key, value);
+ }
+
+ removeItem(key: string): void {
+ this.storage.delete(key);
+ }
+
+ clear(): void {
+ this.storage.clear();
+ }
+
+ get size(): number {
+ return this.storage.size;
+ }
+}
+
+export function createMemoryStorage(): MemoryStorage {
+ return new MemoryStorageImpl();
+}
+
+/**
+ * SessionStorageよりも更に短い期間でクリアされるストレージです
+ * - ブラウザの再読み込みやタブの閉じると内容が揮発します
+ * - このストレージは他のタブと共有されません
+ * - アカウント切り替えやログアウトを行うと内容が揮発します
+ */
+export const defaultMemoryStorage: MemoryStorage = createMemoryStorage();
diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts
index 965bd6f0bc..ea3f1fb01a 100644
--- a/packages/frontend/src/nirax.ts
+++ b/packages/frontend/src/nirax.ts
@@ -5,8 +5,9 @@
// NIRAX --- A lightweight router
-import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
+import { onMounted, shallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3';
+import type { Component, ShallowRef } from 'vue';
function safeURIDecode(str: string): string {
try {
@@ -64,7 +65,7 @@ export type RouterEvent = {
key: string;
}) => void;
same: () => void;
-}
+};
export type Resolved = {
route: RouteDef;
@@ -241,8 +242,6 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
hash,
};
- if (_DEV_) console.log('Routing: ', path, queryString);
-
function check(routes: RouteDef[], _parts: string[]): Resolved | null {
forEachRouteLoop:
for (const route of routes) {
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index 18c7464d2e..6e48366092 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -5,7 +5,8 @@
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
-import { Component, markRaw, Ref, ref, defineAsyncComponent, nextTick } from 'vue';
+import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
+import type { Component, Ref } from 'vue';
import { EventEmitter } from 'eventemitter3';
import * as Misskey from 'misskey-js';
import type { ComponentProps as CP } from 'vue-component-type-helpers';
@@ -309,6 +310,21 @@ export function inputText(props: {
} | {
canceled: false; result: string;
}>;
+// min lengthが指定されてたら result は null になり得ないことを保証する overload function
+export function inputText(props: {
+ type?: 'text' | 'email' | 'password' | 'url';
+ title?: string;
+ text?: string;
+ placeholder?: string | null;
+ autocomplete?: string;
+ default?: string;
+ minLength: number;
+ maxLength?: number;
+}): Promise<{
+ canceled: true; result: undefined;
+} | {
+ canceled: false; result: string;
+}>;
export function inputText(props: {
type?: 'text' | 'email' | 'password' | 'url';
title?: string;
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 762e3d2f64..29772ae00a 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -109,6 +109,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<div>
<a style="display: inline-block;" class="pepabo" title="GMO Pepabo" href="https://pepabo.com/" target="_blank"><img style="width: 100%;" src="https://assets.misskey-hub.net/sponsors/gmo_pepabo.svg" alt="GMO Pepabo"></a>
</div>
+ <div>
+ <a style="display: inline-block;" class="purpledotdigital" title="Purple Dot Digital" href="https://purpledotdigital.com/" target="_blank"><img style="width: 100%;" src="https://assets.misskey-hub.net/sponsors/purple-dot-digital.jpg" alt="Purple Dot Digital"></a>
+ </div>
</div>
</FormSection>
<FormSection>
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index 0a7cb8a50b..97743995bf 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -54,7 +54,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, ref } from 'vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
import FormSplit from '@/components/form/split.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index 30d7e38638..fa08c213e2 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -418,7 +418,7 @@ async function deleteAccount() {
}
async function assignRole() {
- const roles = await misskeyApi('admin/roles/list');
+ const roles = await misskeyApi('admin/roles/list').then(it => it.filter(r => r.target === 'manual'));
const { canceled, result: roleId } = await os.select({
title: i18n.ts._role.chooseRoleToAssign,
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
index eef24afd32..5f683c7a1d 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
@@ -79,7 +79,8 @@ import { i18n } from '@/i18n.js';
import MkInput from '@/components/MkInput.vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
import MkSelect from '@/components/MkSelect.vue';
-import { MkSystemWebhookResult, showSystemWebhookEditorDialog } from '@/components/MkSystemWebhookEditor.impl.js';
+import { showSystemWebhookEditorDialog } from '@/components/MkSystemWebhookEditor.impl.js';
+import type { MkSystemWebhookResult } from '@/components/MkSystemWebhookEditor.impl.js';
import MkSwitch from '@/components/MkSwitch.vue';
import MkDivider from '@/components/MkDivider.vue';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 498cf13943..2d314c822d 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -167,7 +167,7 @@ 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';
+import type { ApiWithDialogCustomErrors } from '@/os.js';
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
index c4ea3b93e3..06d13cda75 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue
@@ -71,25 +71,25 @@ export type EmojiSearchQuery = {
<script setup lang="ts">
import { computed, defineAsyncComponent, onMounted, ref, nextTick, useCssModule } from 'vue';
import * as Misskey from 'misskey-js';
+import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
+import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
+import type { GridSetting } from '@/components/grid/grid.js';
import * as os from '@/os.js';
import {
emptyStrToEmptyArray,
emptyStrToNull,
emptyStrToUndefined,
- RequestLogItem,
roleIdsParser,
} from '@/pages/admin/custom-emojis-manager.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import { validators } from '@/components/grid/cell-validators.js';
-import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import 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";
+import { useLoading } from '@/components/hook/useLoading.js';
type GridItem = {
checked: boolean;
@@ -108,7 +108,7 @@ type GridItem = {
publicUrl?: string | null;
originalUrl?: string | null;
type: string | null;
-}
+};
function setupGrid(): GridSetting {
const $style = useCssModule();
@@ -464,8 +464,8 @@ async function refreshCustomEmojis() {
aliases: emptyStrToUndefined(searchQuery.value.aliases),
category: emptyStrToUndefined(searchQuery.value.category),
license: emptyStrToUndefined(searchQuery.value.license),
- isSensitive: searchQuery.value.sensitive ? Boolean(searchQuery.value.sensitive).valueOf() : undefined,
- localOnly: searchQuery.value.localOnly ? Boolean(searchQuery.value.localOnly).valueOf() : undefined,
+ isSensitive: searchQuery.value.sensitive != null ? Boolean(searchQuery.value.sensitive).valueOf() : undefined,
+ localOnly: searchQuery.value.localOnly != null ? Boolean(searchQuery.value.localOnly).valueOf() : undefined,
updatedAtFrom: emptyStrToUndefined(searchQuery.value.updatedAtFrom),
updatedAtTo: emptyStrToUndefined(searchQuery.value.updatedAtTo),
roleIds: searchQuery.value.roles.map(it => it.id),
@@ -592,7 +592,7 @@ const headerActions = computed(() => [{
dispose();
},
});
- }
+ },
}]);
</script>
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
index cc8b625cd5..d6ee8ea49c 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue
@@ -82,7 +82,6 @@ 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';
@@ -96,12 +95,15 @@ 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 { 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';
+
+import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
+import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
+import type { DroppedFile } from '@/scripts/file-drop.js';
+import type { GridSetting } from '@/components/grid/grid.js';
+import type { GridRow } from '@/components/grid/row.js';
const MAXIMUM_EMOJI_REGISTER_COUNT = 100;
@@ -122,7 +124,7 @@ type GridItem = {
localOnly: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: { id: string, name: string }[];
type: string | null;
-}
+};
function setupGrid(): GridSetting {
const $style = useCssModule();
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
index eecf8d7390..609d445d79 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue
@@ -148,30 +148,27 @@ import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkGrid from '@/components/grid/MkGrid.vue';
-import {
- emptyStrToUndefined,
- GridSortOrderKey,
- gridSortOrderKeys,
- RequestLogItem,
-} from '@/pages/admin/custom-emojis-manager.impl.js';
-import { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
+import { emptyStrToUndefined, gridSortOrderKeys } from '@/pages/admin/custom-emojis-manager.impl.js';
import MkFolder from '@/components/MkFolder.vue';
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
import * as os from '@/os.js';
-import { GridSetting } from '@/components/grid/grid.js';
import { deviceKind } from '@/scripts/device-kind.js';
import MkPagingButtons from '@/components/MkPagingButtons.vue';
import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
-import { SortOrder } from '@/components/MkSortOrderEditor.define.js';
import { useLoading } from '@/components/hook/useLoading.js';
+import type { GridSortOrderKey, RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
+import type { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
+import type { GridSetting } from '@/components/grid/grid.js';
+import type { SortOrder } from '@/components/MkSortOrderEditor.define.js';
+
type GridItem = {
checked: boolean;
id: string;
url: string;
name: string;
host: string;
-}
+};
function setupGrid(): GridSetting {
const $style = useCssModule();
diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts b/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts
index f62304277a..3384a71f0f 100644
--- a/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts
+++ b/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts
@@ -4,7 +4,7 @@
*/
import { delay, http, HttpResponse } from 'msw';
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { entities } from 'misskey-js';
import { commonHandlers } from '../../../.storybook/mocks.js';
import { emoji } from '../../../.storybook/fakes.js';
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index 91f41166e9..a312ecce12 100644
--- a/packages/frontend/src/pages/admin/external-services.vue
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -8,20 +8,34 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
<FormSuspense :p="init">
- <MkFolder>
- <template #label>DeepL Translation</template>
+ <div class="_gaps_m">
+ <MkFolder>
+ <template #label>Google Analytics<span class="_beta">{{ i18n.ts.beta }}</span></template>
- <div class="_gaps_m">
- <MkInput v-model="deeplAuthKey">
- <template #prefix><i class="ti ti-key"></i></template>
- <template #label>DeepL Auth Key</template>
- </MkInput>
- <MkSwitch v-model="deeplIsPro">
- <template #label>Pro account</template>
- </MkSwitch>
- <MkButton primary @click="save_deepl">Save</MkButton>
- </div>
- </MkFolder>
+ <div class="_gaps_m">
+ <MkInput v-model="googleAnalyticsMeasurementId">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>Measurement ID</template>
+ </MkInput>
+ <MkButton primary @click="save_googleAnalytics">Save</MkButton>
+ </div>
+ </MkFolder>
+
+ <MkFolder>
+ <template #label>DeepL Translation</template>
+
+ <div class="_gaps_m">
+ <MkInput v-model="deeplAuthKey">
+ <template #prefix><i class="ti ti-key"></i></template>
+ <template #label>DeepL Auth Key</template>
+ </MkInput>
+ <MkSwitch v-model="deeplIsPro">
+ <template #label>Pro account</template>
+ </MkSwitch>
+ <MkButton primary @click="save_deepl">Save</MkButton>
+ </div>
+ </MkFolder>
+ </div>
</FormSuspense>
</MkSpacer>
</MkStickyContainer>
@@ -44,10 +58,13 @@ import MkFolder from '@/components/MkFolder.vue';
const deeplAuthKey = ref<string>('');
const deeplIsPro = ref<boolean>(false);
+const googleAnalyticsMeasurementId = ref<string>('');
+
async function init() {
const meta = await misskeyApi('admin/meta');
- deeplAuthKey.value = meta.deeplAuthKey;
+ deeplAuthKey.value = meta.deeplAuthKey ?? '';
deeplIsPro.value = meta.deeplIsPro;
+ googleAnalyticsMeasurementId.value = meta.googleAnalyticsMeasurementId ?? '';
}
function save_deepl() {
@@ -59,6 +76,14 @@ function save_deepl() {
});
}
+function save_googleAnalytics() {
+ os.apiWithDialog('admin/update-meta', {
+ googleAnalyticsMeasurementId: googleAnalyticsMeasurementId.value,
+ }).then(() => {
+ fetchInstance(true);
+ });
+}
+
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index ea5fa457f2..672ee8da18 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -41,7 +41,8 @@ import { lookup } from '@/scripts/lookup.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { lookupUser, lookupUserByEmail, lookupFile } from '@/scripts/admin-lookup.js';
-import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import { useRouter } from '@/router/supplier.js';
const isEmpty = (x: string | null) => x == null || x === '';
diff --git a/packages/frontend/src/pages/admin/invites.vue b/packages/frontend/src/pages/admin/invites.vue
index 9cb430b0fe..5189e12899 100644
--- a/packages/frontend/src/pages/admin/invites.vue
+++ b/packages/frontend/src/pages/admin/invites.vue
@@ -65,7 +65,8 @@ import MkFolder from '@/components/MkFolder.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkInviteCode from '@/components/MkInviteCode.vue';
import { definePageMetadata } from '@/scripts/page-metadata.js';
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts b/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts
index 584cd3e4d9..88747ef4ed 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { http, HttpResponse } from 'msw';
import { action } from '@storybook/addon-actions';
import { commonHandlers } from '../../../.storybook/mocks.js';
diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue
index 0896859f3c..7d80f8c2e3 100644
--- a/packages/frontend/src/pages/admin/overview.federation.vue
+++ b/packages/frontend/src/pages/admin/overview.federation.vue
@@ -47,7 +47,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
-import XPie, { type InstanceForPie } from './overview.pie.vue';
+import XPie from './overview.pie.vue';
+import type { InstanceForPie } from './overview.pie.vue';
import * as os from '@/os.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import number from '@/filters/number.js';
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index 512039242e..1a26c00ddb 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -16,7 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, computed, type Ref } from 'vue';
+import { ref, computed } from 'vue';
+import type { Ref } from 'vue';
import XQueue from './queue.chart.vue';
import XHeader from './_header_.vue';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index 552958af6f..cd50f92143 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -122,7 +122,7 @@ definePageMetadata(() => ({
border-radius: 6px;
&:hover {
- background: var(--MI_THEME-X5);
+ background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
}
}
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index 870c3ce88b..91104b676d 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -10,6 +10,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="900">
<div class="_gaps">
<div :class="$style.inputs">
+ <MkButton style="margin-left: auto" @click="resetQuery">{{ i18n.ts.reset }}</MkButton>
+ </div>
+ <div :class="$style.inputs">
<MkSelect v-model="sort" style="flex: 1;">
<template #label>{{ i18n.ts.sort }}</template>
<option value="-createdAt">{{ i18n.ts.registeredDate }} ({{ i18n.ts.ascendingOrder }})</option>
@@ -57,8 +60,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, shallowRef, ref } from 'vue';
+import { computed, shallowRef, ref, watchEffect } from 'vue';
import XHeader from './_header_.vue';
+import { defaultMemoryStorage } from '@/memory-storage';
+import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
@@ -69,13 +74,22 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import { dateString } from '@/filters/date.js';
+type SearchQuery = {
+ sort?: string;
+ state?: string;
+ origin?: string;
+ username?: string;
+ hostname?: string;
+};
+
const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const storedQuery = JSON.parse(defaultMemoryStorage.getItem('admin-users-query') ?? '{}') as SearchQuery;
-const sort = ref('+createdAt');
-const state = ref('all');
-const origin = ref('local');
-const searchUsername = ref('');
-const searchHost = ref('');
+const sort = ref(storedQuery.sort ?? '+createdAt');
+const state = ref(storedQuery.state ?? 'all');
+const origin = ref(storedQuery.origin ?? 'local');
+const searchUsername = ref(storedQuery.username ?? '');
+const searchHost = ref(storedQuery.hostname ?? '');
const pagination = {
endpoint: 'admin/show-users' as const,
limit: 10,
@@ -119,6 +133,14 @@ function show(user) {
os.pageWindow(`/admin/user/${user.id}`);
}
+function resetQuery() {
+ sort.value = '+createdAt';
+ state.value = 'all';
+ origin.value = 'local';
+ searchUsername.value = '';
+ searchHost.value = '';
+}
+
const headerActions = computed(() => [{
icon: 'ti ti-search',
text: i18n.ts.search,
@@ -137,6 +159,16 @@ const headerActions = computed(() => [{
const headerTabs = computed(() => []);
+watchEffect(() => {
+ defaultMemoryStorage.setItem('admin-users-query', JSON.stringify({
+ sort: sort.value,
+ state: state.value,
+ origin: origin.value,
+ username: searchUsername.value,
+ hostname: searchHost.value,
+ }));
+});
+
definePageMetadata(() => ({
title: i18n.ts.users,
icon: 'ti ti-users',
diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue
index 30f12a8fb3..9bddb0a9d2 100644
--- a/packages/frontend/src/pages/api-console.vue
+++ b/packages/frontend/src/pages/api-console.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import JSON5 from 'json5';
-import { Endpoints } from 'misskey-js';
+import type { Endpoints } from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 655e69461f..4a91165d50 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -94,7 +94,7 @@ import MkNote from '@/components/MkNote.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { PageHeaderItem } from '@/types/page-header.js';
+import type { 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';
diff --git a/packages/frontend/src/pages/drive.file.notes.vue b/packages/frontend/src/pages/drive.file.notes.vue
index ca63d43747..d7519896cc 100644
--- a/packages/frontend/src/pages/drive.file.notes.vue
+++ b/packages/frontend/src/pages/drive.file.notes.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { i18n } from '@/i18n.js';
-import { Paging } from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkNotes from '@/components/MkNotes.vue';
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index fb4d599c28..10099e6291 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -194,7 +194,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
import * as Matter from 'matter-js';
import * as Misskey from 'misskey-js';
-import { DropAndFusionGame, Mono } from 'misskey-bubble-game';
+import { DropAndFusionGame } from 'misskey-bubble-game';
+import type { Mono } from 'misskey-bubble-game';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 3b982a405e..6294a3f4a2 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -62,17 +62,20 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, onDeactivated, onUnmounted, Ref, ref, watch, shallowRef, defineAsyncComponent } from 'vue';
+import { computed, onDeactivated, onUnmounted, ref, watch, shallowRef, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
import { Interpreter, Parser, values } from '@syuilo/aiscript';
+import { url } from '@@/js/config.js';
+import type { Ref } from 'vue';
+import type { AsUiComponent, AsUiRoot } from '@/scripts/aiscript/ui.js';
+import type { MenuItem } from '@/types/menu.js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkAsUi from '@/components/MkAsUi.vue';
-import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui.js';
+import { registerAsUiLib } from '@/scripts/aiscript/ui.js';
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
import MkFolder from '@/components/MkFolder.vue';
import MkCode from '@/components/MkCode.vue';
@@ -80,7 +83,6 @@ import { defaultStore } from '@/store.js';
import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import type { MenuItem } from '@/types/menu.js';
import { pleaseLogin } from '@/scripts/please-login.js';
const props = defineProps<{
@@ -196,6 +198,8 @@ async function run() {
if (aiscript.value) aiscript.value.abort();
if (!flash.value) return;
+ components.value = [];
+
aiscript.value = new Interpreter({
...createAiScriptEnv({
storageKey: 'flash:' + flash.value.id,
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index 5d819e7993..863ae018ba 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -46,7 +46,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { shallowRef, computed, ref } from 'vue';
-import MkPagination, { type Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import { userPage, acct } from '@/filters/user.js';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/pages/install-extensions.vue b/packages/frontend/src/pages/install-extensions.vue
index 6d68ed83b4..58f8b865bb 100644
--- a/packages/frontend/src/pages/install-extensions.vue
+++ b/packages/frontend/src/pages/install-extensions.vue
@@ -46,14 +46,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue';
import MkLoading from '@/components/global/MkLoading.vue';
-import MkExtensionInstaller, { type Extension } from '@/components/MkExtensionInstaller.vue';
+import MkExtensionInstaller from '@/components/MkExtensionInstaller.vue';
+import type { Extension } from '@/components/MkExtensionInstaller.vue';
import MkButton from '@/components/MkButton.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkUrl from '@/components/global/MkUrl.vue';
import FormSection from '@/components/form/section.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
-import { AiScriptPluginMeta, parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js';
+import { parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js';
+import type { AiScriptPluginMeta } from '@/scripts/install-plugin.js';
import { parseThemeCode, installTheme } from '@/scripts/install-theme.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 6cec3f9d45..35d1c7dcd1 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -135,7 +135,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
import * as Misskey from 'misskey-js';
-import MkChart, { type ChartSrc } from '@/components/MkChart.vue';
+import MkChart from '@/components/MkChart.vue';
+import type { ChartSrc } from '@/components/MkChart.vue';
import MkObjectView from '@/components/MkObjectView.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue';
@@ -151,7 +152,8 @@ import { iAmModerator, iAmAdmin } from '@/account.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkPagination, { type Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
import { dateString } from '@/filters/date.js';
diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue
index 3f6ae27b89..49c0d931c5 100644
--- a/packages/frontend/src/pages/invite.vue
+++ b/packages/frontend/src/pages/invite.vue
@@ -35,12 +35,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, shallowRef } from 'vue';
-import type * as Misskey from 'misskey-js';
+import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import MkButton from '@/components/MkButton.vue';
-import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import type { Paging } from '@/components/MkPagination.vue';
import MkInviteCode from '@/components/MkInviteCode.vue';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { serverErrorImageUrl, instance } from '@/instance.js';
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index e6b4a0b222..0791c1343b 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -63,6 +63,7 @@ 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 { getAppearNote } from '@/scripts/get-appear-note.js';
import { serverContext, assertServerContext } from '@/server-context.js';
import { $i } from '@/account.js';
@@ -132,10 +133,11 @@ function fetchNote() {
noteId: props.noteId,
}).then(res => {
note.value = res;
+ const appearNote = getAppearNote(res);
// 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く
- if (note.value.clippedCount > 0 || new Date(note.value.createdAt).getTime() < new Date('2023-10-01').getTime()) {
+ if ((appearNote.clippedCount ?? 0) > 0 || new Date(appearNote.createdAt).getTime() < new Date('2023-10-01').getTime()) {
misskeyApi('notes/clips', {
- noteId: note.value.id,
+ noteId: appearNote.id,
}).then((_clips) => {
clips.value = _clips;
});
@@ -170,7 +172,7 @@ definePageMetadata(() => ({
avatar: note.value.user,
path: `/notes/${note.value.id}`,
share: {
- title: i18n.tsx.noteOf({ user: note.value.user.name }),
+ title: i18n.tsx.noteOf({ user: note.value.user.name ?? note.value.user.username }),
text: note.value.text,
},
} : {},
diff --git a/packages/frontend/src/pages/page-editor/page-editor.container.vue b/packages/frontend/src/pages/page-editor/page-editor.container.vue
index a96c2c2a77..936a7462dd 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.container.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.container.vue
@@ -61,11 +61,11 @@ function remove() {
position: relative;
overflow: hidden;
background: var(--MI_THEME-panel);
- border: solid 2px var(--MI_THEME-X12);
+ border: solid 2px light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
border-radius: 8px;
&:hover {
- border: solid 2px var(--MI_THEME-X13);
+ border: solid 2px light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15));
}
&.warn {
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index 88171f7d70..22c06bfc85 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -57,7 +57,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onDeactivated, onUnmounted, Ref, ref, watch, computed } from 'vue';
+import { onDeactivated, onUnmounted, ref, watch, computed } from 'vue';
+import type { Ref } from 'vue';
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
import MkContainer from '@/components/MkContainer.vue';
import MkButton from '@/components/MkButton.vue';
@@ -68,11 +69,14 @@ import * as os from '@/os.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui.js';
+import { registerAsUiLib } from '@/scripts/aiscript/ui.js';
+import type { AsUiComponent } from '@/scripts/aiscript/ui.js';
import MkAsUi from '@/components/MkAsUi.vue';
import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement } from '@/scripts/achievements.js';
+import type { AsUiRoot } from '@/scripts/aiscript/ui.js';
+
const parser = new Parser();
let aiscript: Interpreter;
const code = ref('');
diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue
index 4cb149a58b..a390e3fba1 100644
--- a/packages/frontend/src/pages/search.note.vue
+++ b/packages/frontend/src/pages/search.note.vue
@@ -6,69 +6,127 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<div class="_gaps">
- <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter.prevent="search">
+ <MkInput
+ v-model="searchQuery"
+ large
+ autofocus
+ type="search"
+ @enter.prevent="search"
+ >
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
- <MkFoldableSection :expanded="true">
+ <MkFoldableSection expanded>
<template #header>{{ i18n.ts.options }}</template>
<div class="_gaps_m">
- <template v-if="instance.federation !== 'none'">
- <MkRadios v-model="hostSelect">
- <template #label>{{ i18n.ts.host }}</template>
- <option value="all" default>{{ i18n.ts.all }}</option>
- <option value="local">{{ i18n.ts.local }}</option>
- <option v-if="noteSearchableScope === 'global'" value="specified">{{ i18n.ts.specifyHost }}</option>
- </MkRadios>
- <MkInput v-if="noteSearchableScope === 'global'" v-model="hostInput" :disabled="hostSelect !== 'specified'" :large="true" type="search">
+ <MkRadios v-model="searchScope">
+ <option v-if="instance.federation !== 'none' && noteSearchableScope === 'global'" value="all">{{ i18n.ts._search.searchScopeAll }}</option>
+ <option value="local">{{ instance.federation === 'none' ? i18n.ts._search.searchScopeAll : i18n.ts._search.searchScopeLocal }}</option>
+ <option v-if="instance.federation !== 'none' && noteSearchableScope === 'global'" value="server">{{ i18n.ts._search.searchScopeServer }}</option>
+ <option value="user">{{ i18n.ts._search.searchScopeUser }}</option>
+ </MkRadios>
+
+ <div v-if="instance.federation !== 'none' && searchScope === 'server'" :class="$style.subOptionRoot">
+ <MkInput
+ v-model="hostInput"
+ :placeholder="i18n.ts._search.serverHostPlaceholder"
+ @enter.prevent="search"
+ >
+ <template #label>{{ i18n.ts._search.pleaseEnterServerHost }}</template>
<template #prefix><i class="ti ti-server"></i></template>
</MkInput>
- </template>
-
- <MkFolder :defaultOpen="true">
- <template #label>{{ i18n.ts.specifyUser }}</template>
- <template v-if="user" #suffix>@{{ user.username }}{{ user.host ? `@${user.host}` : "" }}</template>
+ </div>
+ <div v-if="searchScope === 'user'" :class="$style.subOptionRoot">
+ <div :class="$style.userSelectLabel">{{ i18n.ts._search.pleaseSelectUser }}</div>
<div class="_gaps">
- <div :class="$style.userItem">
- <MkUserCardMini v-if="user" :class="$style.userCard" :user="user" :withChart="false"/>
- <MkButton v-if="user == null && $i != null" transparent :class="$style.addMeButton" @click="selectSelf"><div :class="$style.addUserButtonInner"><span><i class="ti ti-plus"></i><i class="ti ti-user"></i></span><span>{{ i18n.ts.selectSelf }}</span></div></MkButton>
- <MkButton v-if="user == null" transparent :class="$style.addUserButton" @click="selectUser"><div :class="$style.addUserButtonInner"><i class="ti ti-plus"></i><span>{{ i18n.ts.selectUser }}</span></div></MkButton>
- <button class="_button" :class="$style.remove" :disabled="user == null" @click="removeUser"><i class="ti ti-x"></i></button>
+ <div v-if="user == null" :class="$style.userSelectButtons">
+ <div v-if="$i != null">
+ <MkButton
+ transparent
+ :class="$style.userSelectButton"
+ @click="selectSelf"
+ >
+ <div :class="$style.userSelectButtonInner">
+ <span><i class="ti ti-plus"></i><i class="ti ti-user"></i></span>
+ <span>{{ i18n.ts.selectSelf }}</span>
+ </div>
+ </MkButton>
+ </div>
+ <div :style="$i == null ? 'grid-column: span 2;' : undefined">
+ <MkButton
+ transparent
+ :class="$style.userSelectButton"
+ @click="selectUser"
+ >
+ <div :class="$style.userSelectButtonInner">
+ <span><i class="ti ti-plus"></i></span>
+ <span>{{ i18n.ts.selectUser }}</span>
+ </div>
+ </MkButton>
+ </div>
+ </div>
+ <div v-else :class="$style.userSelectedButtons">
+ <div style="overflow: hidden;">
+ <MkUserCardMini
+ :user="user"
+ :withChart="false"
+ :class="$style.userSelectedCard"
+ />
+ </div>
+ <div>
+ <button
+ class="_button"
+ :class="$style.userSelectedRemoveButton"
+ @click="removeUser"
+ >
+ <i class="ti ti-x"></i>
+ </button>
+ </div>
</div>
</div>
- </MkFolder>
+ </div>
</div>
</MkFoldableSection>
<div>
- <MkButton large primary gradate rounded style="margin: 0 auto;" @click="search">{{ i18n.ts.search }}</MkButton>
+ <MkButton
+ large
+ primary
+ gradate
+ rounded
+ :disabled="searchParams == null"
+ style="margin: 0 auto;"
+ @click="search"
+ >
+ {{ i18n.ts.search }}
+ </MkButton>
</div>
</div>
<MkFoldableSection v-if="notePagination">
<template #header>{{ i18n.ts.searchResult }}</template>
- <MkNotes :key="key" :pagination="notePagination"/>
+ <MkNotes :key="`searchNotes:${key}`" :pagination="notePagination"/>
</MkFoldableSection>
</div>
</template>
<script lang="ts" setup>
-import { computed, ref, toRef, watch } from 'vue';
-import type { UserDetailed } from 'misskey-js/entities.js';
+import { computed, ref, shallowRef, toRef } from 'vue';
+import type * as Misskey from 'misskey-js';
import type { Paging } from '@/components/MkPagination.vue';
-import MkNotes from '@/components/MkNotes.vue';
-import MkInput from '@/components/MkInput.vue';
-import MkButton from '@/components/MkButton.vue';
+import { $i } from '@/account.js';
+import { host as localHost } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
+import { instance } from '@/instance.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
-import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import MkFolder from '@/components/MkFolder.vue';
import { useRouter } from '@/router/supplier.js';
-import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkNotes from '@/components/MkNotes.vue';
import MkRadios from '@/components/MkRadios.vue';
-import { $i } from '@/account.js';
-import { instance } from '@/instance.js';
+import MkUserCardMini from '@/components/MkUserCardMini.vue';
const props = withDefaults(defineProps<{
query?: string;
@@ -83,76 +141,127 @@ const props = withDefaults(defineProps<{
});
const router = useRouter();
+
const key = ref(0);
+const notePagination = ref<Paging<'notes/search'>>();
+
const searchQuery = ref(toRef(props, 'query').value);
-const notePagination = ref<Paging>();
-const user = ref<UserDetailed | null>(null);
const hostInput = ref(toRef(props, 'host').value);
+const user = shallowRef<Misskey.entities.UserDetailed | null>(null);
+
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const noteSearchableScope = instance.noteSearchableScope ?? 'local';
-const hostSelect = ref<'all' | 'local' | 'specified'>('all');
+//#region set user
+let fetchedUser: Misskey.entities.UserDetailed | null = null;
-const setHostSelectWithInput = (after:string|undefined|null, before:string|undefined|null) => {
- if (before === after) return;
- if (after === '') hostSelect.value = 'all';
- else hostSelect.value = 'specified';
+if (props.userId) {
+ fetchedUser = await misskeyApi('users/show', {
+ userId: props.userId,
+ }).catch(() => null);
+}
+
+if (props.username && fetchedUser == null) {
+ fetchedUser = await misskeyApi('users/show', {
+ username: props.username,
+ ...(props.host ? { host: props.host } : {}),
+ }).catch(() => null);
+}
+
+if (fetchedUser != null) {
+ if (!(noteSearchableScope === 'local' && fetchedUser.host != null)) {
+ user.value = fetchedUser;
+ }
+}
+//#endregion
+
+const searchScope = ref<'all' | 'local' | 'server' | 'user'>((() => {
+ if (user.value != null) return 'user';
+ if (noteSearchableScope === 'local') return 'local';
+ if (hostInput.value) return 'server';
+ return 'all';
+})());
+
+type SearchParams = {
+ readonly query: string;
+ readonly host?: string;
+ readonly userId?: string;
};
-setHostSelectWithInput(hostInput.value, undefined);
+const fixHostIfLocal = (target: string | null | undefined) => {
+ if (!target || target === localHost) return '.';
+ return target;
+};
-watch(hostInput, setHostSelectWithInput);
+const searchParams = computed<SearchParams | null>(() => {
+ const trimmedQuery = searchQuery.value.trim();
+ if (!trimmedQuery) return null;
-const searchHost = computed(() => {
- if (hostSelect.value === 'local' || instance.federation === 'none') return '.';
- if (hostSelect.value === 'specified') return hostInput.value;
- return null;
-});
+ if (searchScope.value === 'user') {
+ if (user.value == null) return null;
+ return {
+ query: trimmedQuery,
+ host: fixHostIfLocal(user.value.host),
+ userId: user.value.id,
+ };
+ }
-if (props.userId != null) {
- misskeyApi('users/show', { userId: props.userId }).then(_user => {
- user.value = _user;
- });
-} else if (props.username != null) {
- misskeyApi('users/show', {
- username: props.username,
- ...(props.host != null && props.host !== '') ? { host: props.host } : {},
- }).then(_user => {
- user.value = _user;
- });
-}
+ if (instance.federation !== 'none' && searchScope.value === 'server') {
+ let trimmedHost = hostInput.value?.trim();
+ if (!trimmedHost) return null;
+ if (trimmedHost.startsWith('https://') || trimmedHost.startsWith('http://')) {
+ try {
+ trimmedHost = new URL(trimmedHost).host;
+ } catch (err) { /* empty */ }
+ }
+ return {
+ query: trimmedQuery,
+ host: fixHostIfLocal(trimmedHost),
+ };
+ }
+
+ if (instance.federation === 'none' || searchScope.value === 'local') {
+ return {
+ query: trimmedQuery,
+ host: '.',
+ };
+ }
+
+ return {
+ query: trimmedQuery,
+ };
+});
function selectUser() {
- os.selectUser({ includeSelf: true, localOnly: instance.noteSearchableScope === 'local' }).then(_user => {
+ os.selectUser({
+ includeSelf: true,
+ localOnly: instance.noteSearchableScope === 'local',
+ }).then(_user => {
user.value = _user;
- hostInput.value = _user.host ?? '';
});
}
function selectSelf() {
- user.value = $i as UserDetailed | null;
- hostInput.value = null;
+ user.value = $i;
}
function removeUser() {
user.value = null;
- hostInput.value = '';
}
async function search() {
- const query = searchQuery.value.toString().trim();
-
- if (query == null || query === '') return;
+ if (searchParams.value == null) return;
//#region AP lookup
- if (query.startsWith('https://') && !query.includes(' ')) {
+ if (searchParams.value.query.startsWith('https://') && !searchParams.value.query.includes(' ')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
const promise = misskeyApi('ap/show', {
- uri: query,
+ uri: searchParams.value.query,
});
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
@@ -161,6 +270,7 @@ async function search() {
if (res.type === 'User') {
router.push(`/@${res.object.username}@${res.object.host}`);
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (res.type === 'Note') {
router.push(`/notes/${res.object.id}`);
}
@@ -170,25 +280,25 @@ async function search() {
}
//#endregion
- if (query.length > 1 && !query.includes(' ')) {
- if (query.startsWith('@')) {
+ if (searchParams.value.query.length > 1 && !searchParams.value.query.includes(' ')) {
+ if (searchParams.value.query.startsWith('@')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
- router.push(`/${query}`);
+ router.push(`/${searchParams.value.query}`);
return;
}
}
- if (query.startsWith('#')) {
+ if (searchParams.value.query.startsWith('#')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.openTagPageConfirm,
});
if (!confirm.canceled) {
- router.push(`/tags/${encodeURIComponent(query.substring(1))}`);
+ router.push(`/tags/${encodeURIComponent(searchParams.value.query.substring(1))}`);
return;
}
}
@@ -198,9 +308,7 @@ async function search() {
endpoint: 'notes/search',
limit: 10,
params: {
- query: searchQuery.value,
- userId: user.value ? user.value.id : null,
- ...(searchHost.value ? { host: searchHost.value } : {}),
+ ...searchParams.value,
},
};
@@ -208,41 +316,48 @@ async function search() {
}
</script>
<style lang="scss" module>
-.userItem {
- display: flex;
- justify-content: center;
+.subOptionRoot {
+ background: var(--MI_THEME-panel);
+ border-radius: var(--MI-radius);
+ padding: var(--MI-margin);
}
-.addMeButton {
- border: 2px dashed var(--MI_THEME-fgTransparent);
- padding: 12px;
- margin-right: 16px;
+
+.userSelectLabel {
+ font-size: 0.85em;
+ padding: 0 0 8px;
+ user-select: none;
}
-.addUserButton {
- border: 2px dashed var(--MI_THEME-fgTransparent);
+
+.userSelectButtons {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 16px;
+}
+
+.userSelectButton {
+ width: 100%;
+ height: 100%;
padding: 12px;
- flex-grow: 1;
+ border: 2px dashed var(--MI_THEME-fgTransparent);
}
-.addUserButtonInner {
+
+.userSelectButtonInner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
min-height: 38px;
}
-.userCard {
- flex-grow: 1;
+
+.userSelectedButtons {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
}
-.remove {
+
+.userSelectedRemoveButton {
width: 32px;
height: 32px;
- align-self: center;
-
- & > i:before {
- color: #ff2a2a;
- }
-
- &:disabled {
- opacity: 0;
- }
+ color: #ff2a2a;
}
</style>
diff --git a/packages/frontend/src/pages/search.stories.impl.ts b/packages/frontend/src/pages/search.stories.impl.ts
index 0110a7ab8e..27271615c2 100644
--- a/packages/frontend/src/pages/search.stories.impl.ts
+++ b/packages/frontend/src/pages/search.stories.impl.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import search_ from './search.vue';
import { userDetailed } from '@/../.storybook/fakes.js';
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index e8bc4cd6d3..2b8faf5465 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFoldableSection v-if="userPagination">
<template #header>{{ i18n.ts.searchResult }}</template>
- <MkUserList :key="key" :pagination="userPagination"/>
+ <MkUserList :key="`searchUsers:${key}`" :pagination="userPagination"/>
</MkFoldableSection>
</div>
</template>
@@ -49,14 +49,16 @@ const props = withDefaults(defineProps<{
const router = useRouter();
-const key = ref('');
+const key = ref(0);
+const userPagination = ref<Paging<'users/search'>>();
+
const searchQuery = ref(toRef(props, 'query').value);
const searchOrigin = ref(toRef(props, 'origin').value);
-const userPagination = ref<Paging>();
async function search() {
const query = searchQuery.value.toString().trim();
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (query == null || query === '') return;
//#region AP lookup
@@ -76,6 +78,7 @@ async function search() {
if (res.type === 'User') {
router.push(`/@${res.object.username}@${res.object.host}`);
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (res.type === 'Note') {
router.push(`/notes/${res.object.id}`);
}
@@ -118,6 +121,6 @@ async function search() {
},
};
- key.value = query;
+ key.value++;
}
</script>
diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index c2588736b3..4a7301f405 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
-import type * as Misskey from 'misskey-js';
+import * as Misskey from 'misskey-js';
import FormSuspense from '@/components/form/suspense.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
@@ -38,7 +38,7 @@ 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';
+import type { MenuItem } from '@/types/menu.js';
const storedAccounts = ref<{ id: string, token: string }[] | null>(null);
const accounts = ref(new Map<string, Misskey.entities.UserDetailed | null>());
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
index 6a13984dd7..c5657fce68 100644
--- a/packages/frontend/src/pages/settings/drive-cleaner.vue
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -48,7 +48,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
-import { computed, ref, watch, type StyleValue } from 'vue';
+import { computed, ref, watch } from 'vue';
+import type { StyleValue } from 'vue';
import tinycolor from 'tinycolor2';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
index fd3581d114..b16c943676 100644
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ b/packages/frontend/src/pages/settings/emoji-picker.vue
@@ -126,7 +126,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { computed, ref, Ref, watch } from 'vue';
+import { computed, ref, watch } from 'vue';
+import type { Ref } from 'vue';
import Sortable from 'vuedraggable';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 1ee7909aa8..4449d6169f 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -170,6 +170,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
<MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch>
+ <MkSwitch v-model="confirmOnReact">{{ i18n.ts.confirmOnReact }}</MkSwitch>
</div>
<MkSelect v-model="serverDisconnectedBehavior">
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@@ -320,6 +321,7 @@ const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHori
const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer'));
const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
+const confirmOnReact = computed(defaultStore.makeGetterSetter('confirmOnReact'));
const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
watch(lang, () => {
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index b5a6d719d1..bc6d6d0261 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -35,9 +35,11 @@ import MkSuperMenu from '@/components/MkSuperMenu.vue';
import { signout, $i } from '@/account.js';
import { clearCache } from '@/scripts/clear-cache.js';
import { instance } from '@/instance.js';
-import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
import * as os from '@/os.js';
import { useRouter } from '@/router/supplier.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
+import type { SuperMenuDef } from '@/components/MkSuperMenu.vue';
const indexInfo = {
title: i18n.ts.settings,
@@ -60,7 +62,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.basicSettings,
items: [{
icon: 'ti ti-user',
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index 8ffe0d6a7a..1e7436bf9c 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -63,7 +63,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { shallowRef, computed } from 'vue';
-import XNotificationConfig, { type NotificationConfig } from './notifications.notification-config.vue';
+import XNotificationConfig from './notifications.notification-config.vue';
+import type { NotificationConfig } from './notifications.notification-config.vue';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkFolder from '@/components/MkFolder.vue';
@@ -80,7 +81,7 @@ const $i = signinRequired();
const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] satisfies (typeof notificationTypes[number])[] as string[];
-const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login'] satisfies (typeof notificationTypes[number])[] as string[];
+const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login', 'createToken'] satisfies (typeof notificationTypes[number])[] as string[];
const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index 9fcf564e55..bf461f173b 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -38,7 +38,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { Ref, computed, ref } from 'vue';
+import { computed, ref } from 'vue';
+import type { Ref } from 'vue';
import XSound from './sounds.sound.vue';
import type { SoundType, OperationType } from '@/scripts/sound.js';
import type { SoundStore } from '@/store.js';
diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue
index 579ca6b20b..f63f15fc13 100644
--- a/packages/frontend/src/pages/settings/theme.manage.vue
+++ b/packages/frontend/src/pages/settings/theme.manage.vue
@@ -37,7 +37,8 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
-import { Theme, getBuiltinThemesRef } from '@/scripts/theme.js';
+import { getBuiltinThemesRef } from '@/scripts/theme.js';
+import type { Theme } from '@/scripts/theme.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import * as os from '@/os.js';
import { getThemes, removeTheme } from '@/theme-store.js';
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index f1ec231588..fcf5b3cd9b 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -32,27 +32,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div class="selects">
- <MkSelect v-model="lightThemeId" large class="select">
+ <MkSelect v-model="lightThemeId" large class="select" :items="lightThemeSelectorItems">
<template #label>{{ i18n.ts.themeForLightMode }}</template>
<template #prefix><i class="ti ti-sun"></i></template>
- <option v-if="instanceLightTheme" :key="'instance:' + instanceLightTheme.id" :value="instanceLightTheme.id">{{ instanceLightTheme.name }}</option>
- <optgroup v-if="installedLightThemes.length > 0" :label="i18n.ts._theme.installedThemes">
- <option v-for="x in installedLightThemes" :key="'installed:' + x.id" :value="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="i18n.ts._theme.builtinThemes">
- <option v-for="x in builtinLightThemes" :key="'builtin:' + x.id" :value="x.id">{{ x.name }}</option>
- </optgroup>
</MkSelect>
- <MkSelect v-model="darkThemeId" large class="select">
+ <MkSelect v-model="darkThemeId" large class="select" :items="darkThemeSelectorItems">
<template #label>{{ i18n.ts.themeForDarkMode }}</template>
<template #prefix><i class="ti ti-moon"></i></template>
- <option v-if="instanceDarkTheme" :key="'instance:' + instanceDarkTheme.id" :value="instanceDarkTheme.id">{{ instanceDarkTheme.name }}</option>
- <optgroup v-if="installedDarkThemes.length > 0" :label="i18n.ts._theme.installedThemes">
- <option v-for="x in installedDarkThemes" :key="'installed:' + x.id" :value="x.id">{{ x.name }}</option>
- </optgroup>
- <optgroup :label="i18n.ts._theme.builtinThemes">
- <option v-for="x in builtinDarkThemes" :key="'builtin:' + x.id" :value="x.id">{{ x.name }}</option>
- </optgroup>
</MkSelect>
</div>
@@ -73,6 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, onActivated, ref, watch } from 'vue';
import JSON5 from 'json5';
+import type { MkSelectItem } from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';
import FormSection from '@/components/form/section.vue';
@@ -102,6 +89,70 @@ const installedLightThemes = computed(() => installedThemes.value.filter(t => t.
const builtinLightThemes = computed(() => builtinThemes.value.filter(t => t.base === 'light' || t.kind === 'light'));
const themes = computed(() => uniqueBy([instanceDarkTheme.value, instanceLightTheme.value, ...builtinThemes.value, ...installedThemes.value].filter(x => x != null), theme => theme.id));
+const lightThemeSelectorItems = computed(() => {
+ const items = [] as MkSelectItem[];
+ if (instanceLightTheme.value) {
+ items.push({
+ type: 'option',
+ value: instanceLightTheme.value.id,
+ label: instanceLightTheme.value.name,
+ });
+ }
+ if (installedLightThemes.value.length > 0) {
+ items.push({
+ type: 'group',
+ label: i18n.ts._theme.installedThemes,
+ items: installedLightThemes.value.map(x => ({
+ type: 'option',
+ value: x.id,
+ label: x.name,
+ })),
+ });
+ }
+ items.push({
+ type: 'group',
+ label: i18n.ts._theme.builtinThemes,
+ items: builtinLightThemes.value.map(x => ({
+ type: 'option',
+ value: x.id,
+ label: x.name,
+ })),
+ });
+ return items;
+});
+
+const darkThemeSelectorItems = computed(() => {
+ const items = [] as MkSelectItem[];
+ if (instanceDarkTheme.value) {
+ items.push({
+ type: 'option',
+ value: instanceDarkTheme.value.id,
+ label: instanceDarkTheme.value.name,
+ });
+ }
+ if (installedDarkThemes.value.length > 0) {
+ items.push({
+ type: 'group',
+ label: i18n.ts._theme.installedThemes,
+ items: installedDarkThemes.value.map(x => ({
+ type: 'option',
+ value: x.id,
+ label: x.name,
+ })),
+ });
+ }
+ items.push({
+ type: 'group',
+ label: i18n.ts._theme.builtinThemes,
+ items: builtinDarkThemes.value.map(x => ({
+ type: 'option',
+ value: x.id,
+ label: x.name,
+ })),
+ });
+ return items;
+});
+
const darkTheme = ColdDeviceStorage.ref('darkTheme');
const darkThemeId = computed({
get() {
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index 7025cde879..76567cc403 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -87,7 +87,8 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkFolder from '@/components/MkFolder.vue';
import { $i } from '@/account.js';
-import { Theme, applyTheme } from '@/scripts/theme.js';
+import { applyTheme } from '@/scripts/theme.js';
+import type { Theme } from '@/scripts/theme.js';
import { host } from '@@/js/config.js';
import * as os from '@/os.js';
import { ColdDeviceStorage, defaultStore } from '@/store.js';
diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue
index aa2c791c76..7b74ea67ca 100644
--- a/packages/frontend/src/pages/user/activity.following.vue
+++ b/packages/frontend/src/pages/user/activity.following.vue
@@ -15,7 +15,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, shallowRef, ref } from 'vue';
-import { Chart, ChartDataset } from 'chart.js';
+import { Chart } from 'chart.js';
+import type { ChartDataset } from 'chart.js';
import * as Misskey from 'misskey-js';
import gradient from 'chartjs-plugin-gradient';
import { misskeyApi } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue
index 64514716d6..8c7484ae08 100644
--- a/packages/frontend/src/pages/user/activity.notes.vue
+++ b/packages/frontend/src/pages/user/activity.notes.vue
@@ -15,7 +15,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, shallowRef, ref } from 'vue';
-import { Chart, ChartDataset } from 'chart.js';
+import { Chart } from 'chart.js';
+import type { ChartDataset } from 'chart.js';
import * as Misskey from 'misskey-js';
import gradient from 'chartjs-plugin-gradient';
import { misskeyApi } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index ce24807f93..a073626cbb 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -15,7 +15,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, shallowRef, ref } from 'vue';
-import { Chart, ChartDataset } from 'chart.js';
+import { Chart } from 'chart.js';
+import type { ChartDataset } from 'chart.js';
import * as Misskey from 'misskey-js';
import gradient from 'chartjs-plugin-gradient';
import { misskeyApi } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/pages/user/home.stories.impl.ts b/packages/frontend/src/pages/user/home.stories.impl.ts
index c623ef9ee4..66d3579041 100644
--- a/packages/frontend/src/pages/user/home.stories.impl.ts
+++ b/packages/frontend/src/pages/user/home.stories.impl.ts
@@ -4,7 +4,7 @@
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { StoryObj } from '@storybook/vue3';
+import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { userDetailed } from '../../../.storybook/fakes.js';
import { commonHandlers } from '../../../.storybook/mocks.js';
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index 7740fe0d39..918b81b204 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -5,8 +5,9 @@
// PIZZAX --- A lightweight store
-import { onUnmounted, Ref, ref, watch } from 'vue';
+import { onUnmounted, ref, watch } from 'vue';
import { BroadcastChannel } from 'broadcast-channel';
+import type { Ref } from 'vue';
import { $i } from '@/account.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { get, set } from '@/scripts/idb-proxy.js';
@@ -112,7 +113,6 @@ export class Storage<T extends StateDef> {
this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
} else {
this.reactiveState[k].value = this.state[k] = v.default;
- if (_DEV_) console.log('Use default value', k, v.default);
}
}
@@ -179,12 +179,9 @@ export class Storage<T extends StateDef> {
// (JSON.parse(JSON.stringify(value))の代わり)
const rawValue = deepClone(value);
- if (_DEV_) console.log('set', key, rawValue, value);
-
this.reactiveState[key].value = this.state[key] = rawValue;
return this.addIdbSetJob(async () => {
- if (_DEV_) console.log(`set ${String(key)} start`);
switch (this.def[key].where) {
case 'device': {
this.pizzaxChannel.postMessage({
@@ -223,7 +220,6 @@ export class Storage<T extends StateDef> {
break;
}
}
- if (_DEV_) console.log(`set ${String(key)} complete`);
});
}
@@ -246,9 +242,9 @@ export class Storage<T extends StateDef> {
getter?: (v: T[K]['default']) => R,
setter?: (v: R) => T[K]['default'],
): {
- get: () => R;
- set: (value: R) => void;
- } {
+ get: () => R;
+ set: (value: R) => void;
+ } {
const valueRef = ref(this.state[key]);
const stop = watch(this.reactiveState[key], val => {
diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts
index 81233a5a5e..e319a8c398 100644
--- a/packages/frontend/src/plugin.ts
+++ b/packages/frontend/src/plugin.ts
@@ -7,7 +7,8 @@ import { ref } from 'vue';
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
import { inputText } from '@/os.js';
-import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js';
+import { noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js';
+import type { Plugin } from '@/store.js';
const parser = new Parser();
const pluginContexts = new Map<string, Interpreter>();
diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts
index 217954a7bb..d2a4484c45 100644
--- a/packages/frontend/src/router/definition.ts
+++ b/packages/frontend/src/router/definition.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { AsyncComponentLoader, defineAsyncComponent } from 'vue';
+import { defineAsyncComponent } from 'vue';
+import type { AsyncComponentLoader } from 'vue';
import type { IRouter, RouteDef } from '@/nirax.js';
import { Router } from '@/nirax.js';
import { $i, iAmModerator } from '@/account.js';
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
index 3c25e80d12..3932a8bac8 100644
--- a/packages/frontend/src/router/main.ts
+++ b/packages/frontend/src/router/main.ts
@@ -4,9 +4,10 @@
*/
import { EventEmitter } from 'eventemitter3';
-import { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js';
+import type { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js';
import type { App, ShallowRef } from 'vue';
+import { analytics } from '@/analytics.js';
/**
* {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。
@@ -29,6 +30,14 @@ export function setupRouter(app: App, routerFactory: ((path: string) => IRouter)
window.history.replaceState({ key: ctx.key }, '', ctx.path);
});
+ mainRouter.addListener('change', ctx => {
+ console.log('mainRouter: change', ctx.path);
+ analytics.page({
+ path: ctx.path,
+ title: ctx.path,
+ });
+ });
+
mainRouter.init();
setMainRouter(mainRouter);
diff --git a/packages/frontend/src/router/supplier.ts b/packages/frontend/src/router/supplier.ts
index 7da236f4e7..87f8829854 100644
--- a/packages/frontend/src/router/supplier.ts
+++ b/packages/frontend/src/router/supplier.ts
@@ -4,7 +4,8 @@
*/
import { inject } from 'vue';
-import { IRouter, Router } from '@/nirax.js';
+import { Router } from '@/nirax.js';
+import type { IRouter } from '@/nirax.js';
import { mainRouter } from '@/router/main.js';
/**
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts
index e203c51bba..2c0c8c816e 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/scripts/aiscript/api.ts
@@ -76,7 +76,7 @@ export function createAiScriptEnv(opts: { storageKey: string, token?: string })
// バグがあればundefinedもあり得るため念のため
if (typeof token.value !== 'string') throw new Error('invalid token');
}
- const actualToken: string|null = token?.value ?? opts.token ?? null;
+ const actualToken: string | null = token?.value ?? opts.token ?? null;
if (param == null) {
throw new errors.AiScriptRuntimeError('expected param');
}
diff --git a/packages/frontend/src/scripts/aiscript/common.ts b/packages/frontend/src/scripts/aiscript/common.ts
index de6fa1d633..ba5dfb8368 100644
--- a/packages/frontend/src/scripts/aiscript/common.ts
+++ b/packages/frontend/src/scripts/aiscript/common.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { errors, utils, type values } from '@syuilo/aiscript';
+import { errors, utils } from '@syuilo/aiscript';
+import type { values } from '@syuilo/aiscript';
export function assertStringAndIsIn<A extends readonly string[]>(value: values.Value | undefined, expects: A): asserts value is values.VStr & { value: A[number] } {
utils.assertString(value);
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts
index ca92b27ff5..46e193f7c1 100644
--- a/packages/frontend/src/scripts/aiscript/ui.ts
+++ b/packages/frontend/src/scripts/aiscript/ui.ts
@@ -5,7 +5,8 @@
import { utils, values } from '@syuilo/aiscript';
import { v4 as uuid } from 'uuid';
-import { ref, Ref } from 'vue';
+import { ref } from 'vue';
+import type { Ref } from 'vue';
import * as Misskey from 'misskey-js';
import { assertStringAndIsIn } from './common.js';
diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/scripts/autocomplete.ts
index 7766c44c04..9a603b848c 100644
--- a/packages/frontend/src/scripts/autocomplete.ts
+++ b/packages/frontend/src/scripts/autocomplete.ts
@@ -3,9 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { nextTick, Ref, ref, defineAsyncComponent } from 'vue';
+import { nextTick, ref, defineAsyncComponent } from 'vue';
import getCaretCoordinates from 'textarea-caret';
import { toASCII } from 'punycode.js';
+import type { Ref } from 'vue';
import { popup } from '@/os.js';
export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam';
@@ -97,15 +98,21 @@ export class Autocomplete {
const isMention = mentionIndex !== -1;
const isHashtag = hashtagIndex !== -1;
- const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam?.includes(' ');
+ const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam.includes(' ');
const isMfmTag = mfmTagIndex !== -1 && !isMfmParam;
const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
let opened = false;
if (isMention && this.onlyType.includes('user')) {
- const username = text.substring(mentionIndex + 1);
- if (username !== '' && username.match(/^[a-zA-Z0-9_]+$/)) {
+ // ユーザのサジェスト中に@を入力すると、その位置から新たにユーザ名を取りなおそうとしてしまう
+ // この動きはリモートユーザのサジェストを阻害するので、@を検知したらその位置よりも前の@を探し、
+ // ホスト名を含むリモートのユーザ名を全て拾えるようにする
+ const mentionIndexAlt = text.lastIndexOf('@', mentionIndex - 1);
+ const username = mentionIndexAlt === -1
+ ? text.substring(mentionIndex + 1)
+ : text.substring(mentionIndexAlt + 1);
+ if (username !== '' && username.match(/^[a-zA-Z0-9_@.]+$/)) {
this.open('user', username);
opened = true;
} else if (username === '') {
diff --git a/packages/frontend/src/scripts/chart-legend.ts b/packages/frontend/src/scripts/chart-legend.ts
index 2d534f60c1..e701d18dd2 100644
--- a/packages/frontend/src/scripts/chart-legend.ts
+++ b/packages/frontend/src/scripts/chart-legend.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Plugin } from 'chart.js';
+import type { Plugin } from 'chart.js';
import MkChartLegend from '@/components/MkChartLegend.vue';
export const chartLegend = (legend: InstanceType<typeof MkChartLegend>) => ({
diff --git a/packages/frontend/src/scripts/chart-vline.ts b/packages/frontend/src/scripts/chart-vline.ts
index 24e41245e7..465ca591c6 100644
--- a/packages/frontend/src/scripts/chart-vline.ts
+++ b/packages/frontend/src/scripts/chart-vline.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Plugin } from 'chart.js';
+import type { Plugin } from 'chart.js';
export const chartVLine = (vLineColor: string) => ({
id: 'vLine',
diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts
index c3c3f419a9..281ea2520e 100644
--- a/packages/frontend/src/scripts/check-reaction-permissions.ts
+++ b/packages/frontend/src/scripts/check-reaction-permissions.ts
@@ -4,7 +4,7 @@
*/
import * as Misskey from 'misskey-js';
-import { UnicodeEmojiDef } from '@@/js/emojilist.js';
+import type { UnicodeEmojiDef } from '@@/js/emojilist.js';
export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean {
if (typeof emoji === 'string') return true; // UnicodeEmojiDefにも無い絵文字であれば文字列で来る。Unicode絵文字であることには変わりないので常にリアクション可能とする;
diff --git a/packages/frontend/src/scripts/emoji-picker.ts b/packages/frontend/src/scripts/emoji-picker.ts
index 14b5cbf35e..e704b5fd6f 100644
--- a/packages/frontend/src/scripts/emoji-picker.ts
+++ b/packages/frontend/src/scripts/emoji-picker.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { defineAsyncComponent, Ref, ref } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
+import type { Ref } from 'vue';
import { popup } from '@/os.js';
import { defaultStore } from '@/store.js';
diff --git a/packages/frontend/src/scripts/file-drop.ts b/packages/frontend/src/scripts/file-drop.ts
index c2e863c0dc..4259fe25e9 100644
--- a/packages/frontend/src/scripts/file-drop.ts
+++ b/packages/frontend/src/scripts/file-drop.ts
@@ -15,7 +15,7 @@ export type DroppedDirectory = {
isFile: false;
path: string;
children: DroppedItem[];
-}
+};
export async function extractDroppedItems(ev: DragEvent): Promise<DroppedItem[]> {
const dropItems = ev.dataTransfer?.items;
diff --git a/packages/frontend/src/scripts/format-time-string.ts b/packages/frontend/src/scripts/format-time-string.ts
index 35ad77d982..d383f143e1 100644
--- a/packages/frontend/src/scripts/format-time-string.ts
+++ b/packages/frontend/src/scripts/format-time-string.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-const defaultLocaleStringFormats: {[index: string]: string} = {
+const defaultLocaleStringFormats: { [index: string]: string } = {
'weekday': 'narrow',
'era': 'narrow',
'year': 'numeric',
diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts
deleted file mode 100644
index a85ee01e26..0000000000
--- a/packages/frontend/src/scripts/gen-search-query.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import * as Misskey from 'misskey-js';
-import { host as localHost } from '@@/js/config.js';
-
-export async function genSearchQuery(v: any, q: string) {
- let host: string;
- let userId: string;
- if (q.split(' ').some(x => x.startsWith('@'))) {
- for (const at of q.split(' ').filter(x => x.startsWith('@')).map(x => x.substring(1))) {
- if (at.includes('.')) {
- if (at === localHost || at === '.') {
- host = null;
- } else {
- host = at;
- }
- } else {
- const user = await v.api('users/show', Misskey.acct.parse(at)).catch(x => null);
- if (user) {
- userId = user.id;
- } else {
- // todo: show error
- }
- }
- }
- }
- return {
- query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '),
- host: host,
- userId: userId,
- };
-}
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index bc504077b0..23fe811525 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { defineAsyncComponent, Ref, ShallowRef } from 'vue';
+import { defineAsyncComponent } from 'vue';
+import type { Ref, ShallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import { url } from '@@/js/config.js';
import { claimAchievement } from './achievements.js';
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 94a1d4c855..8f7c3ba3be 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -14,7 +14,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore, userActions } from '@/store.js';
import { $i, iAmModerator } from '@/account.js';
import { notesSearchAvailable, canSearchNonLocalNotes } from '@/scripts/check-permissions.js';
-import { IRouter } from '@/nirax.js';
+import type { IRouter } from '@/nirax.js';
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
import { mainRouter } from '@/router/main.js';
import { genEmbedCode } from '@/scripts/get-embed-code.js';
diff --git a/packages/frontend/src/scripts/install-theme.ts b/packages/frontend/src/scripts/install-theme.ts
index 866f1225bf..cc32adcc6a 100644
--- a/packages/frontend/src/scripts/install-theme.ts
+++ b/packages/frontend/src/scripts/install-theme.ts
@@ -5,7 +5,8 @@
import JSON5 from 'json5';
import { addTheme, getThemes } from '@/theme-store.js';
-import { Theme, applyTheme, validateTheme } from '@/scripts/theme.js';
+import { applyTheme, validateTheme } from '@/scripts/theme.js';
+import type { Theme } from '@/scripts/theme.js';
export function parseThemeCode(code: string): Theme {
let theme;
diff --git a/packages/frontend/src/scripts/key-event.ts b/packages/frontend/src/scripts/key-event.ts
index a72776d48c..020a6c2174 100644
--- a/packages/frontend/src/scripts/key-event.ts
+++ b/packages/frontend/src/scripts/key-event.ts
@@ -7,7 +7,7 @@
* {@link KeyboardEvent.code} の値を表す文字列。不足分は適宜追加する
* @see https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values
*/
-export type KeyCode =
+export type KeyCode = (
| 'Backspace'
| 'Tab'
| 'Enter'
@@ -94,32 +94,32 @@ export type KeyCode =
| 'Quote'
| 'Meta'
| 'AltGraph'
- ;
+);
/**
* 修飾キーを表す文字列。不足分は適宜追加する。
*/
-export type KeyModifier =
+export type KeyModifier = (
| 'Shift'
| 'Control'
| 'Alt'
| 'Meta'
- ;
+);
/**
* 押下されたキー以外の状態を表す文字列。不足分は適宜追加する。
*/
-export type KeyState =
+export type KeyState = (
| 'composing'
| 'repeat'
- ;
+);
export type KeyEventHandler = {
modifiers?: KeyModifier[];
states?: KeyState[];
code: KeyCode | 'any';
handler: (event: KeyboardEvent) => void;
-}
+};
export function handleKeyEvent(event: KeyboardEvent, handlers: KeyEventHandler[]) {
function checkModifier(ev: KeyboardEvent, modifiers? : KeyModifier[]) {
diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts
index ddcbfe1a8d..8ee2a4b99c 100644
--- a/packages/frontend/src/scripts/lookup.ts
+++ b/packages/frontend/src/scripts/lookup.ts
@@ -54,10 +54,6 @@ export async function lookup(router?: Router) {
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;
diff --git a/packages/frontend/src/scripts/mfm-function-picker.ts b/packages/frontend/src/scripts/mfm-function-picker.ts
index bf59fe98a0..a2f777f623 100644
--- a/packages/frontend/src/scripts/mfm-function-picker.ts
+++ b/packages/frontend/src/scripts/mfm-function-picker.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Ref, nextTick } from 'vue';
+import { nextTick } from 'vue';
+import type { Ref } from 'vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { MFM_TAGS } from '@@/js/const.js';
diff --git a/packages/frontend/src/scripts/page-metadata.ts b/packages/frontend/src/scripts/page-metadata.ts
index 0e3b093ecf..671751147c 100644
--- a/packages/frontend/src/scripts/page-metadata.ts
+++ b/packages/frontend/src/scripts/page-metadata.ts
@@ -4,7 +4,8 @@
*/
import * as Misskey from 'misskey-js';
-import { MaybeRefOrGetter, Ref, inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
+import { inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
+import type { MaybeRefOrGetter, Ref } from 'vue';
export type PageMetadata = {
title: string;
diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/scripts/reaction-picker.ts
index 7aec05c0cf..c142b3ed2a 100644
--- a/packages/frontend/src/scripts/reaction-picker.ts
+++ b/packages/frontend/src/scripts/reaction-picker.ts
@@ -4,7 +4,8 @@
*/
import * as Misskey from 'misskey-js';
-import { defineAsyncComponent, Ref, ref } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
+import type { Ref } from 'vue';
import { popup } from '@/os.js';
import { defaultStore } from '@/store.js';
diff --git a/packages/frontend/src/scripts/stream-mock.ts b/packages/frontend/src/scripts/stream-mock.ts
index cb0e607fcb..9b1b368de4 100644
--- a/packages/frontend/src/scripts/stream-mock.ts
+++ b/packages/frontend/src/scripts/stream-mock.ts
@@ -37,9 +37,9 @@ export class StreamMock extends EventEmitter<StreamEvents> implements IStream {
// do nothing
}
- public send(typeOrPayload: string): void
- public send(typeOrPayload: string, payload: any): void
- public send(typeOrPayload: Record<string, any> | any[]): void
+ public send(typeOrPayload: string): void;
+ public send(typeOrPayload: string, payload: any): void;
+ public send(typeOrPayload: Record<string, any> | any[]): void;
public send(typeOrPayload: string | Record<string, any> | any[], payload?: any): void {
// do nothing
}
diff --git a/packages/frontend/src/scripts/theme-editor.ts b/packages/frontend/src/scripts/theme-editor.ts
index 0092af1640..0206e378bf 100644
--- a/packages/frontend/src/scripts/theme-editor.ts
+++ b/packages/frontend/src/scripts/theme-editor.ts
@@ -5,7 +5,8 @@
import { v4 as uuid } from 'uuid';
-import { themeProps, Theme } from './theme.js';
+import { themeProps } from './theme.js';
+import type { Theme } from './theme.js';
export type Default = null;
export type Color = string;
diff --git a/packages/frontend/src/scripts/use-form.ts b/packages/frontend/src/scripts/use-form.ts
index 0d505fe466..26cca839c3 100644
--- a/packages/frontend/src/scripts/use-form.ts
+++ b/packages/frontend/src/scripts/use-form.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { computed, Reactive, reactive, watch } from 'vue';
+import { computed, reactive, watch } from 'vue';
+import type { Reactive } from 'vue';
function copy<T>(v: T): T {
return JSON.parse(JSON.stringify(v));
diff --git a/packages/frontend/src/scripts/use-leave-guard.ts b/packages/frontend/src/scripts/use-leave-guard.ts
index 5f7e56e8a9..395c12a756 100644
--- a/packages/frontend/src/scripts/use-leave-guard.ts
+++ b/packages/frontend/src/scripts/use-leave-guard.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Ref } from 'vue';
+import type { Ref } from 'vue';
export function useLeaveGuard(enabled: Ref<boolean>) {
/* TODO
diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts
index 542d8ab52b..0bc10e90e4 100644
--- a/packages/frontend/src/scripts/use-note-capture.ts
+++ b/packages/frontend/src/scripts/use-note-capture.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { onUnmounted, Ref, ShallowRef } from 'vue';
+import { onUnmounted } from 'vue';
+import type { Ref, ShallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import { useStream } from '@/stream.js';
import { $i } from '@/account.js';
diff --git a/packages/frontend/src/scripts/use-tooltip.ts b/packages/frontend/src/scripts/use-tooltip.ts
index a26d08cce7..d9ddfc8b5d 100644
--- a/packages/frontend/src/scripts/use-tooltip.ts
+++ b/packages/frontend/src/scripts/use-tooltip.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Ref, ref, watch, onUnmounted } from 'vue';
+import { ref, watch, onUnmounted } from 'vue';
+import type { Ref } from 'vue';
export function useTooltip(
elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>,
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index dbe90dba86..e2243e067a 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -10,7 +10,8 @@ import lightTheme from '@@/themes/l-light.json5';
import darkTheme from '@@/themes/d-green-lime.json5';
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 { DEFAULT_DEVICE_KIND } from '@/scripts/device-kind.js';
+import type { DeviceKind } from '@/scripts/device-kind.js';
import { miLocalStorage } from '@/local-storage.js';
import { Storage } from '@/pizzax.js';
@@ -55,7 +56,7 @@ export type SoundStore = {
fileUrl: string;
volume: number;
-}
+};
export const postFormActions: PostFormAction[] = [];
export const userActions: UserAction[] = [];
@@ -478,6 +479,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
+ confirmOnReact: {
+ where: 'device',
+ default: false,
+ },
sound_masterVolume: {
where: 'device',
diff --git a/packages/frontend/src/theme-store.ts b/packages/frontend/src/theme-store.ts
index c41cc17652..fb010ae426 100644
--- a/packages/frontend/src/theme-store.ts
+++ b/packages/frontend/src/theme-store.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Theme, getBuiltinThemes } from '@/scripts/theme.js';
+import { getBuiltinThemes } from '@/scripts/theme.js';
+import type { Theme } from '@/scripts/theme.js';
import { miLocalStorage } from '@/local-storage.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { $i } from '@/account.js';
diff --git a/packages/frontend/src/types/menu.ts b/packages/frontend/src/types/menu.ts
index 138eb7dd62..7cadef136d 100644
--- a/packages/frontend/src/types/menu.ts
+++ b/packages/frontend/src/types/menu.ts
@@ -4,9 +4,9 @@
*/
import * as Misskey from 'misskey-js';
-import { ComputedRef, Ref } from 'vue';
+import type { ComputedRef, Ref } from 'vue';
-interface MenuRadioOptionsDef extends Record<string, any> { }
+type MenuRadioOptionsDef = Record<string, any>;
export type MenuAction = (ev: MouseEvent) => void;
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 5ea9bf7068..da5059bb59 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -52,7 +52,8 @@ import XCommon from './_common_/common.vue';
import { instanceName } from '@@/js/config.js';
import { StickySidebar } from '@/scripts/sticky-sidebar.js';
import * as os from '@/os.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 71a18fbc66..8e99c457ad 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -188,7 +188,7 @@ const addColumn = async (ev) => {
addColumnToStore({
type: column,
id: uuid(),
- name: i18n.ts._deck._columns[column],
+ name: null,
width: 330,
soundSetting: { type: null, volume: 1 },
});
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index a41639e71c..b79cd8408b 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
- <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name }}</span>
+ <i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name || antennaName || i18n.ts._deck._columns.antenna }}</span>
</template>
<MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @note="onNote"/>
@@ -17,14 +17,15 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onMounted, ref, shallowRef, watch, defineAsyncComponent } from 'vue';
import type { entities as MisskeyEntities } from 'misskey-js';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import { updateColumn } from './deck-store.js';
+import type { Column } from './deck-store.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js';
import { antennasCache } from '@/cache.js';
-import { SoundStore } from '@/store.js';
+import type { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js';
@@ -35,6 +36,7 @@ const props = defineProps<{
const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
+const antennaName = ref<string | null>(null);
onMounted(() => {
if (props.column.antennaId == null) {
@@ -42,6 +44,13 @@ onMounted(() => {
}
});
+watch([() => props.column.name, () => props.column.antennaId], () => {
+ if (!props.column.name && props.column.antennaId) {
+ misskeyApi('antennas/show', { antennaId: props.column.antennaId })
+ .then(value => antennaName.value = value.name);
+ }
+});
+
watch(soundSetting, v => {
updateColumn(props.column.id, { soundSetting: v });
});
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index 5479b53d90..9e07c06639 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
- <i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name }}</span>
+ <i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name || channel?.name || i18n.ts._deck._columns.channel }}</span>
</template>
<template v-if="column.channelId">
@@ -19,10 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, shallowRef, watch } from 'vue';
+import { onMounted, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import { updateColumn } from './deck-store.js';
+import type { Column } from './deck-store.js';
import MkTimeline from '@/components/MkTimeline.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
@@ -30,7 +31,7 @@ import { favoritedChannelsCache } from '@/cache.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js';
-import { SoundStore } from '@/store.js';
+import type { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js';
@@ -43,9 +44,18 @@ const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
const channel = shallowRef<Misskey.entities.Channel>();
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
-if (props.column.channelId == null) {
- setChannel();
-}
+onMounted(() => {
+ if (props.column.channelId == null) {
+ setChannel();
+ }
+});
+
+watch([() => props.column.name, () => props.column.channelId], () => {
+ if (!props.column.name && props.column.channelId) {
+ misskeyApi('channels/show', { channelId: props.column.channelId })
+ .then(value => channel.value = value);
+ }
+});
watch(soundSetting, v => {
updateColumn(props.column.id, { soundSetting: v });
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index da0bf24a56..f23e33c748 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -43,9 +43,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, provide, watch, shallowRef, ref, computed } from 'vue';
-import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store.js';
+import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from './deck-store.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
+import type { Column } from './deck-store.js';
import type { MenuItem } from '@/types/menu.js';
provide('shouldHeaderThin', true);
@@ -128,7 +129,8 @@ function getMenu() {
icon: 'ti ti-settings',
text: i18n.ts._deck.configureColumn,
action: async () => {
- const { canceled, result } = await os.form(props.column.name, {
+ const name = props.column.name ?? i18n.ts._deck._columns[props.column.type];
+ const { canceled, result } = await os.form(name, {
name: {
type: 'string',
label: i18n.ts.name,
@@ -143,7 +145,7 @@ function getMenu() {
flexible: {
type: 'boolean',
label: i18n.ts._deck.flexible,
- default: props.column.flexible,
+ default: props.column.flexible ?? null,
},
});
if (canceled) return;
diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts
index 231bf19aa8..9055ea6d43 100644
--- a/packages/frontend/src/ui/deck/deck-store.ts
+++ b/packages/frontend/src/ui/deck/deck-store.ts
@@ -10,7 +10,7 @@ import type { BasicTimelineType } from '@/timelines.js';
import { Storage } from '@/pizzax.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { deepClone } from '@/scripts/clone.js';
-import { SoundStore } from '@/store.js';
+import type { SoundStore } from '@/store.js';
type ColumnWidget = {
name: string;
@@ -51,7 +51,7 @@ export type Column = {
withReplies?: boolean;
withSensitive?: boolean;
onlyFiles?: boolean;
- soundSetting: SoundStore;
+ soundSetting?: SoundStore;
};
export const deckStore = markRaw(new Storage('deck', {
@@ -94,7 +94,7 @@ export const loadDeck = async () => {
key: deckStore.state.profile,
});
} catch (err) {
- if (err.code === 'NO_SUCH_KEY') {
+ if (typeof err === 'object' && err != null && 'code' in err && err.code === 'NO_SUCH_KEY') {
// 後方互換性のため
if (deckStore.state.profile === 'default') {
saveDeck();
@@ -180,6 +180,7 @@ export function swapLeftColumn(id: Column['id']) {
}
return true;
}
+ return false;
});
saveDeck();
}
@@ -196,6 +197,7 @@ export function swapRightColumn(id: Column['id']) {
}
return true;
}
+ return false;
});
saveDeck();
}
@@ -216,6 +218,7 @@ export function swapUpColumn(id: Column['id']) {
}
return true;
}
+ return false;
});
saveDeck();
}
@@ -236,6 +239,7 @@ export function swapDownColumn(id: Column['id']) {
}
return true;
}
+ return false;
});
saveDeck();
}
@@ -286,7 +290,8 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
const columns = deepClone(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = deepClone(deckStore.state.columns[columnIndex]);
- if (column == null || column.widgets == null) return;
+ if (column == null) return;
+ if (column.widgets == null) column.widgets = [];
column.widgets = column.widgets.filter(w => w.id !== widget.id);
columns[columnIndex] = column;
deckStore.set('columns', columns);
@@ -308,7 +313,8 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, widgetDat
const columns = deepClone(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = deepClone(deckStore.state.columns[columnIndex]);
- if (column == null || column.widgets == null) return;
+ if (column == null) return;
+ if (column.widgets == null) column.widgets = [];
column.widgets = column.widgets.map(w => w.id === widgetId ? {
...w,
data: widgetData,
diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue
index d12a18f760..2cecd6c669 100644
--- a/packages/frontend/src/ui/deck/direct-column.vue
+++ b/packages/frontend/src/ui/deck/direct-column.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()">
- <template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name }}</template>
+ <template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.direct }}</template>
<MkNotes ref="tlComponent" :pagination="pagination"/>
</XColumn>
@@ -14,8 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import XColumn from './column.vue';
-import { Column } from './deck-store.js';
+import type { Column } from './deck-store.js';
import MkNotes from '@/components/MkNotes.vue';
+import { i18n } from '@/i18n.js';
defineProps<{
column: Column;
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index 8bb8fe7225..83961d02bc 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
- <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span>
+ <i class="ti ti-list"></i><span style="margin-left: 8px;">{{ (column.name || listName) ?? i18n.ts._deck._columns.list }}</span>
</template>
<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes" @note="onNote"/>
@@ -14,16 +14,17 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { watch, shallowRef, ref } from 'vue';
+import { watch, shallowRef, ref, onMounted } from 'vue';
import type { entities as MisskeyEntities } from 'misskey-js';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import { updateColumn } from './deck-store.js';
+import type { Column } from './deck-store.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js';
-import { SoundStore } from '@/store.js';
+import type { SoundStore } from '@/store.js';
import { userListsCache } from '@/cache.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js';
@@ -36,10 +37,20 @@ const props = defineProps<{
const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
const withRenotes = ref(props.column.withRenotes ?? true);
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
+const listName = ref<string | null>(null);
-if (props.column.listId == null) {
- setList();
-}
+onMounted(() => {
+ if (props.column.listId == null) {
+ setList();
+ }
+});
+
+watch([() => props.column.name, () => props.column.listId], () => {
+ if (!props.column.name && props.column.listId) {
+ misskeyApi('users/lists/show', { listId: props.column.listId })
+ .then(value => listName.value = value.name);
+ }
+});
watch(withRenotes, v => {
updateColumn(props.column.id, {
diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index f8c712c371..45c39a5cad 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -21,10 +21,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { provide, shallowRef, ref } from 'vue';
import XColumn from './column.vue';
-import { deckStore, Column } from '@/ui/deck/deck-store.js';
+import { deckStore } from '@/ui/deck/deck-store.js';
+import type { Column } from '@/ui/deck/deck-store.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import { useScrollPositionManager } from '@/nirax.js';
import { getScrollContainer } from '@@/js/scroll.js';
import { isLink } from '@@/js/is-link.js';
diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue
index 7b25a55ec3..233fba554b 100644
--- a/packages/frontend/src/ui/deck/mentions-column.vue
+++ b/packages/frontend/src/ui/deck/mentions-column.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :column="column" :isStacked="isStacked" :refresher="() => reloadTimeline()">
- <template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name }}</template>
+ <template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.mentions }}</template>
<MkNotes ref="tlComponent" :pagination="pagination"/>
</XColumn>
@@ -14,8 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import XColumn from './column.vue';
-import { Column } from './deck-store.js';
+import type { Column } from './deck-store.js';
import MkNotes from '@/components/MkNotes.vue';
+import { i18n } from '../../i18n.js';
defineProps<{
column: Column;
diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue
index 19ccfc1f7c..c0303e86dc 100644
--- a/packages/frontend/src/ui/deck/notifications-column.vue
+++ b/packages/frontend/src/ui/deck/notifications-column.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :column="column" :isStacked="isStacked" :menu="menu" :refresher="async () => { await notificationsComponent?.reload() }">
- <template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name }}</template>
+ <template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns.notifications }}</template>
<XNotifications ref="notificationsComponent" :excludeTypes="props.column.excludeTypes"/>
</XColumn>
@@ -14,7 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, shallowRef } from 'vue';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import { updateColumn } from './deck-store.js';
+import type { Column } from './deck-store.js';
import XNotifications from '@/components/MkNotifications.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index beb4237978..5b1420570d 100644
--- a/packages/frontend/src/ui/deck/role-timeline-column.vue
+++ b/packages/frontend/src/ui/deck/role-timeline-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
- <i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name }}</span>
+ <i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name || roleName || i18n.ts._deck._columns.roleTimeline }}</span>
</template>
<MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId" @note="onNote"/>
@@ -14,15 +14,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, ref, shallowRef, watch } from 'vue';
+import { computed, onMounted, ref, shallowRef, watch } from 'vue';
import XColumn from './column.vue';
-import { updateColumn, Column } from './deck-store.js';
+import { updateColumn } from './deck-store.js';
+import type { Column } from './deck-store.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js';
-import { SoundStore } from '@/store.js';
+import type { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js';
@@ -33,6 +34,7 @@ const props = defineProps<{
const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
+const roleName = ref<string | null>(null);
onMounted(() => {
if (props.column.roleId == null) {
@@ -40,6 +42,13 @@ onMounted(() => {
}
});
+watch([() => props.column.name, () => props.column.roleId], () => {
+ if (!props.column.name && props.column.roleId) {
+ misskeyApi('roles/show', { roleId: props.column.roleId })
+ .then(value => roleName.value = value.name);
+ }
+});
+
watch(soundSetting, v => {
updateColumn(props.column.id, { soundSetting: v });
});
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index 74c4fb504b..b9b3746abf 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
<template #header>
<i v-if="column.tl != null" :class="basicTimelineIconClass(column.tl)"/>
- <span style="margin-left: 8px;">{{ column.name }}</span>
+ <span style="margin-left: 8px;">{{ column.name || (column.tl ? i18n.ts._timelines[column.tl] : null) || i18n.ts._deck._columns.tl }}</span>
</template>
<div v-if="!isAvailableBasicTimeline(column.tl)" :class="$style.disabled">
@@ -34,14 +34,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, watch, ref, shallowRef, computed } from 'vue';
import XColumn from './column.vue';
-import { removeColumn, updateColumn, Column } from './deck-store.js';
+import { removeColumn, updateColumn } from './deck-store.js';
+import type { Column } from './deck-store.js';
import type { MenuItem } from '@/types/menu.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { hasWithReplies, isAvailableBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
import { instance } from '@/instance.js';
-import { SoundStore } from '@/store.js';
+import type { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js';
diff --git a/packages/frontend/src/ui/deck/tl-note-notification.ts b/packages/frontend/src/ui/deck/tl-note-notification.ts
index 275ea56ba0..03d4b3a580 100644
--- a/packages/frontend/src/ui/deck/tl-note-notification.ts
+++ b/packages/frontend/src/ui/deck/tl-note-notification.ts
@@ -4,9 +4,10 @@
*/
import * as Misskey from 'misskey-js';
-import { Ref } from 'vue';
-import { SoundStore } from '@/store.js';
-import { getSoundDuration, playMisskeySfxFile, soundsTypes, SoundType } from '@/scripts/sound.js';
+import type { Ref } from 'vue';
+import type { SoundStore } from '@/store.js';
+import type { SoundType } from '@/scripts/sound.js';
+import { getSoundDuration, playMisskeySfxFile, soundsTypes } from '@/scripts/sound.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue
index a0e62c8264..20284d8c9f 100644
--- a/packages/frontend/src/ui/deck/widgets-column.vue
+++ b/packages/frontend/src/ui/deck/widgets-column.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<XColumn :menu="menu" :naked="true" :column="column" :isStacked="isStacked">
- <template #header><i class="ti ti-apps" style="margin-right: 8px;"></i>{{ column.name }}</template>
+ <template #header><i class="ti ti-apps" style="margin-right: 8px;"></i>{{ column.name || i18n.ts._deck._columns[props.column.type] }}</template>
<div :class="$style.root">
<div v-if="!(column.widgets && column.widgets.length > 0) && !edit" :class="$style.intro">{{ i18n.ts._deck.widgetsIntroduction }}</div>
@@ -17,7 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import XColumn from './column.vue';
-import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store.js';
+import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store.js';
+import type { Column } from './deck-store.js';
import XWidgets from '@/components/MkWidgets.vue';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
index 9e41c48c5b..5cbcd69b2c 100644
--- a/packages/frontend/src/ui/minimum.vue
+++ b/packages/frontend/src/ui/minimum.vue
@@ -16,7 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, provide, ref } from 'vue';
import XCommon from './_common_/common.vue';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import { instanceName } from '@@/js/config.js';
import { mainRouter } from '@/router/main.js';
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 94998b7be6..9fff5efe51 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -95,7 +95,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef, Ref } from 'vue';
+import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef } from 'vue';
+import type { Ref } from 'vue';
import { instanceName } from '@@/js/config.js';
import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js';
import { isLink } from '@@/js/is-link.js';
@@ -107,7 +108,8 @@ import { defaultStore } from '@/store.js';
import { navbarItemDef } from '@/navbar.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import { deviceKind } from '@/scripts/device-kind.js';
import { miLocalStorage } from '@/local-storage.js';
import { useScrollPositionManager } from '@/nirax.js';
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index 7d8677e3be..8bcb260677 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -77,7 +77,8 @@ import { instance } from '@/instance.js';
import XSigninDialog from '@/components/MkSigninDialog.vue';
import XSignupDialog from '@/components/MkSignupDialog.vue';
import { ColdDeviceStorage, defaultStore } from '@/store.js';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
import { mainRouter } from '@/router/main.js';
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index 1f73b5fcaf..2e31d056c1 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -24,7 +24,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, provide, ref } from 'vue';
import XCommon from './_common_/common.vue';
-import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import type { PageMetadata } from '@/scripts/page-metadata.js';
import { instanceName, ui } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { mainRouter } from '@/router/main.js';
diff --git a/packages/frontend/src/widgets/WidgetActivity.vue b/packages/frontend/src/widgets/WidgetActivity.vue
index 0aaf18ddd1..cf1110da2b 100644
--- a/packages/frontend/src/widgets/WidgetActivity.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.vue
@@ -21,10 +21,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
import XCalendar from './WidgetActivity.calendar.vue';
import XChart from './WidgetActivity.chart.vue';
-import { GetFormResultType } from '@/scripts/form.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
import { $i } from '@/account.js';
diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue
index 00001005de..9b04c463ba 100644
--- a/packages/frontend/src/widgets/WidgetAichan.vue
+++ b/packages/frontend/src/widgets/WidgetAichan.vue
@@ -11,8 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, onUnmounted, shallowRef } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
const name = 'ai';
diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue
index cf7e9c5a3e..80573d2dc4 100644
--- a/packages/frontend/src/widgets/WidgetAiscript.vue
+++ b/packages/frontend/src/widgets/WidgetAiscript.vue
@@ -21,8 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import * as os from '@/os.js';
import MkContainer from '@/components/MkContainer.vue';
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
index fa79e4aeb7..f0f81a4a89 100644
--- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue
+++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
@@ -13,16 +13,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, Ref, ref, watch } from 'vue';
+import { onMounted, ref, watch } from 'vue';
+import type { Ref } from 'vue';
import { Interpreter, Parser } from '@syuilo/aiscript';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import * as os from '@/os.js';
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
import { $i } from '@/account.js';
import MkAsUi from '@/components/MkAsUi.vue';
import MkContainer from '@/components/MkContainer.vue';
-import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui.js';
+import { registerAsUiLib } from '@/scripts/aiscript/ui.js';
+import type { AsUiComponent, AsUiRoot } from '@/scripts/aiscript/ui.js';
const name = 'aiscriptApp';
diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
index c2bda85ac7..98e186f836 100644
--- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
+++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
@@ -26,8 +26,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue
index 6080e120ec..3e455bee3b 100644
--- a/packages/frontend/src/widgets/WidgetButton.vue
+++ b/packages/frontend/src/widgets/WidgetButton.vue
@@ -13,8 +13,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { Interpreter, Parser } from '@syuilo/aiscript';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import * as os from '@/os.js';
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
import { $i } from '@/account.js';
diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue
index 2443e40789..94169d5e40 100644
--- a/packages/frontend/src/widgets/WidgetCalendar.vue
+++ b/packages/frontend/src/widgets/WidgetCalendar.vue
@@ -39,8 +39,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { i18n } from '@/i18n.js';
import { useInterval } from '@@/js/use-interval.js';
@@ -207,7 +208,7 @@ defineExpose<WidgetComponentExpose>({
.meter {
width: 100%;
overflow: hidden;
- background: var(--MI_THEME-X11);
+ background: light-dark(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.3));
border-radius: 8px;
}
diff --git a/packages/frontend/src/widgets/WidgetClicker.vue b/packages/frontend/src/widgets/WidgetClicker.vue
index 5c978fdf72..2b3663d35b 100644
--- a/packages/frontend/src/widgets/WidgetClicker.vue
+++ b/packages/frontend/src/widgets/WidgetClicker.vue
@@ -12,8 +12,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkClickerGame from '@/components/MkClickerGame.vue';
diff --git a/packages/frontend/src/widgets/WidgetClock.vue b/packages/frontend/src/widgets/WidgetClock.vue
index b3128ef27e..2d3f57a92f 100644
--- a/packages/frontend/src/widgets/WidgetClock.vue
+++ b/packages/frontend/src/widgets/WidgetClock.vue
@@ -30,8 +30,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkAnalogClock from '@/components/MkAnalogClock.vue';
import MkDigitalClock from '@/components/MkDigitalClock.vue';
diff --git a/packages/frontend/src/widgets/WidgetDigitalClock.vue b/packages/frontend/src/widgets/WidgetDigitalClock.vue
index fa9a98d571..6e0c9e6dfc 100644
--- a/packages/frontend/src/widgets/WidgetDigitalClock.vue
+++ b/packages/frontend/src/widgets/WidgetDigitalClock.vue
@@ -15,8 +15,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { timezones } from '@/scripts/timezones.js';
import MkDigitalClock from '@/components/MkDigitalClock.vue';
diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue
index 4a10a612e2..89716575a9 100644
--- a/packages/frontend/src/widgets/WidgetFederation.vue
+++ b/packages/frontend/src/widgets/WidgetFederation.vue
@@ -27,8 +27,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkMiniChart from '@/components/MkMiniChart.vue';
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
index d090372b9a..aee9066731 100644
--- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
@@ -20,8 +20,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkTagCloud from '@/components/MkTagCloud.vue';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
index ec12aa265c..69832332b1 100644
--- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
@@ -20,8 +20,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { host } from '@@/js/config.js';
import { instance } from '@/instance.js';
diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue
index 0ee6b863dc..84ba05b5d3 100644
--- a/packages/frontend/src/widgets/WidgetJobQueue.vue
+++ b/packages/frontend/src/widgets/WidgetJobQueue.vue
@@ -52,8 +52,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onUnmounted, reactive, ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { useStream } from '@/stream.js';
import kmg from '@/filters/kmg.js';
import * as sound from '@/scripts/sound.js';
diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue
index 7b179ce703..65ab7a7075 100644
--- a/packages/frontend/src/widgets/WidgetMemo.vue
+++ b/packages/frontend/src/widgets/WidgetMemo.vue
@@ -17,8 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/widgets/WidgetNotifications.vue b/packages/frontend/src/widgets/WidgetNotifications.vue
index 773c078b49..8aaed2624d 100644
--- a/packages/frontend/src/widgets/WidgetNotifications.vue
+++ b/packages/frontend/src/widgets/WidgetNotifications.vue
@@ -17,8 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import XNotifications from '@/components/MkNotifications.vue';
import * as os from '@/os.js';
diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
index d8c4e259c8..4a3cdb9ba3 100644
--- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue
+++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
@@ -15,8 +15,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue
index 40e2d8fbc7..6d13ba09cc 100644
--- a/packages/frontend/src/widgets/WidgetPhotos.vue
+++ b/packages/frontend/src/widgets/WidgetPhotos.vue
@@ -24,8 +24,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onUnmounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { useStream } from '@/stream.js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/widgets/WidgetPostForm.vue b/packages/frontend/src/widgets/WidgetPostForm.vue
index 7f344505d8..b0a62d1be2 100644
--- a/packages/frontend/src/widgets/WidgetPostForm.vue
+++ b/packages/frontend/src/widgets/WidgetPostForm.vue
@@ -9,8 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkPostForm from '@/components/MkPostForm.vue';
const name = 'postForm';
diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue
index ae39098305..9f006945ab 100644
--- a/packages/frontend/src/widgets/WidgetProfile.vue
+++ b/packages/frontend/src/widgets/WidgetProfile.vue
@@ -22,8 +22,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { $i } from '@/account.js';
import { userPage } from '@/filters/user.js';
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index 3e43687709..0d7ce55be3 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -25,8 +25,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch, computed } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import { url as base } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index 4f594b720f..5ecc1ab022 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -29,9 +29,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch, computed } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
import MarqueeText from '@/components/MkMarquee.vue';
-import { GetFormResultType } from '@/scripts/form.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import { shuffle } from '@/scripts/shuffle.js';
import { url as base } from '@@/js/config.js';
diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue
index 3fea1d7053..67d35d71db 100644
--- a/packages/frontend/src/widgets/WidgetSlideshow.vue
+++ b/packages/frontend/src/widgets/WidgetSlideshow.vue
@@ -19,8 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue
index a4685fd1fc..02db9454a8 100644
--- a/packages/frontend/src/widgets/WidgetTimeline.vue
+++ b/packages/frontend/src/widgets/WidgetTimeline.vue
@@ -32,8 +32,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue
index 47a4efc106..3354912c07 100644
--- a/packages/frontend/src/widgets/WidgetTrends.vue
+++ b/packages/frontend/src/widgets/WidgetTrends.vue
@@ -26,8 +26,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import MkMiniChart from '@/components/MkMiniChart.vue';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/widgets/WidgetUnixClock.vue b/packages/frontend/src/widgets/WidgetUnixClock.vue
index 832cd575cc..f85d27d6aa 100644
--- a/packages/frontend/src/widgets/WidgetUnixClock.vue
+++ b/packages/frontend/src/widgets/WidgetUnixClock.vue
@@ -17,8 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onUnmounted, ref, watch } from 'vue';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
const name = 'unixClock';
diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue
index 72391d622e..805e14c669 100644
--- a/packages/frontend/src/widgets/WidgetUserList.vue
+++ b/packages/frontend/src/widgets/WidgetUserList.vue
@@ -26,8 +26,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
-import { GetFormResultType } from '@/scripts/form.js';
+import { useWidgetPropsManager } from './widget.js';
+import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import MkContainer from '@/components/MkContainer.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts
index c5be25e7df..ea17d484c5 100644
--- a/packages/frontend/src/widgets/index.ts
+++ b/packages/frontend/src/widgets/index.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { App, defineAsyncComponent } from 'vue';
+import { defineAsyncComponent } from 'vue';
+import type { App } from 'vue';
export default function(app: App) {
app.component('WidgetProfile', defineAsyncComponent(() => import('./WidgetProfile.vue')));
diff --git a/packages/frontend/src/widgets/server-metric/index.vue b/packages/frontend/src/widgets/server-metric/index.vue
index 86d84b4f33..3264c8dc04 100644
--- a/packages/frontend/src/widgets/server-metric/index.vue
+++ b/packages/frontend/src/widgets/server-metric/index.vue
@@ -22,14 +22,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onUnmounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget.js';
+import { useWidgetPropsManager } from '../widget.js';
+import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from '../widget.js';
import XCpuMemory from './cpu-mem.vue';
import XNet from './net.vue';
import XCpu from './cpu.vue';
import XMemory from './mem.vue';
import XDisk from './disk.vue';
import MkContainer from '@/components/MkContainer.vue';
-import { GetFormResultType } from '@/scripts/form.js';
+import type { GetFormResultType } from '@/scripts/form.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/widgets/widget.ts b/packages/frontend/src/widgets/widget.ts
index bfe8067adf..98e1e44cd8 100644
--- a/packages/frontend/src/widgets/widget.ts
+++ b/packages/frontend/src/widgets/widget.ts
@@ -5,7 +5,7 @@
import { reactive, watch } from 'vue';
import { throttle } from 'throttle-debounce';
-import { Form, GetFormResultType } from '@/scripts/form.js';
+import type { Form, GetFormResultType } from '@/scripts/form.js';
import * as os from '@/os.js';
import { deepClone } from '@/scripts/clone.js';