import type { AsyncComponentLoader } from "vue"; import { defineAsyncComponent, inject } from "vue"; import { Router } from "@/nirax"; import MkError from "@/pages/_error_.vue"; import MkLoading from "@/pages/_loading_.vue"; import { $i, isEmojiMod, isModerator, isSignedIn } from "@/reactiveAccount"; const page = (loader: AsyncComponentLoader) => defineAsyncComponent({ loader, loadingComponent: MkLoading, errorComponent: MkError, }); export const routes = [ { path: "/@:initUser/pages/:initPageName/view-source", component: page(() => import("./pages/page-editor/page-editor.vue")), }, { path: "/@:username/pages/:pageName", component: page(() => import("./pages/page.vue")), }, { path: "/@:acct/following", component: page(() => import("./pages/user/following.vue")), }, { path: "/@:acct/followers", component: page(() => import("./pages/user/followers.vue")), }, { name: "user", path: "/@:acct/:page?", component: page(() => import("./pages/user/index.vue")), }, { name: "note", path: "/notes/:noteId", component: page(() => import("./pages/note.vue")), }, { path: "/clips/:clipId", component: page(() => import("./pages/clip.vue")), }, { path: "/user-info/:userId", component: page(() => import("./pages/user-info.vue")), }, { path: "/instance-info/:host", component: page(() => import("./pages/instance-info.vue")), }, { name: "settings", path: "/settings", component: page(() => import("./pages/settings/index.vue")), loginRequired: true, children: [ { path: "/profile", name: "profile", component: page(() => import("./pages/settings/profile.vue")), }, { path: "/privacy", name: "privacy", component: page(() => import("./pages/settings/privacy.vue")), }, { path: "/reaction", name: "reaction", component: page(() => import("./pages/settings/reaction.vue")), }, { path: "/drive", name: "drive", component: page(() => import("./pages/settings/drive.vue")), }, { path: "/notifications", name: "notifications", component: page(() => import("./pages/settings/notifications.vue")), }, { path: "/email", name: "email", component: page(() => import("./pages/settings/email.vue")), }, { path: "/security", name: "security", component: page(() => import("./pages/settings/security.vue")), }, { path: "/general", name: "general", component: page(() => import("./pages/settings/general.vue")), }, { path: "/theme/install", name: "theme", component: page(() => import("./pages/settings/theme.install.vue")), }, { path: "/theme/manage", name: "theme", component: page(() => import("./pages/settings/theme.manage.vue")), }, { path: "/theme", name: "theme", component: page(() => import("./pages/settings/theme.vue")), }, { path: "/custom-css", name: "custom-css", component: page(() => import("./pages/settings/custom-css.vue")), }, { path: "/custom-katex-macro", name: "custom-katex-macro", component: page( () => import("./pages/settings/custom-katex-macro.vue"), ), }, { path: "/account-info", name: "account-info", component: page(() => import("./pages/settings/account-info.vue")), }, { path: "/navbar", name: "navbar", component: page(() => import("./pages/settings/navbar.vue")), }, { path: "/statusbar", name: "statusbar", component: page(() => import("./pages/settings/statusbar.vue")), }, { path: "/sounds", name: "sounds", component: page(() => import("./pages/settings/sounds.vue")), }, { path: "/plugin/install", name: "plugin", component: page(() => import("./pages/settings/plugin.install.vue")), }, { path: "/plugin", name: "plugin", component: page(() => import("./pages/settings/plugin.vue")), }, { path: "/import-export", name: "import-export", component: page(() => import("./pages/settings/import-export.vue")), }, { path: "/instance-mute", name: "instance-mute", component: page(() => import("./pages/settings/instance-mute.vue")), }, { path: "/mute-block", name: "mute-block", component: page(() => import("./pages/settings/mute-block.vue")), }, { path: "/word-mute", name: "word-mute", component: page(() => import("./pages/settings/word-mute.vue")), }, { path: "/api", name: "api", component: page(() => import("./pages/settings/api.vue")), }, { path: "/apps", name: "apps", component: page(() => import("./pages/settings/apps.vue")), }, { path: "/webhook/edit/:webhookId", name: "webhook", component: page(() => import("./pages/settings/webhook.edit.vue")), }, { path: "/webhook/new", name: "webhook", component: page(() => import("./pages/settings/webhook.new.vue")), }, { path: "/webhook", name: "webhook", component: page(() => import("./pages/settings/webhook.vue")), }, { path: "/deck", name: "deck", component: page(() => import("./pages/settings/deck.vue")), }, { path: "/delete-account", name: "delete-account", component: page(() => import("./pages/settings/delete-account.vue")), }, { path: "/preferences-backups", name: "preferences-backups", component: page( () => import("./pages/settings/preferences-backups.vue"), ), }, { path: "/migration", name: "migration", component: page(() => import("./pages/settings/migration.vue")), }, { path: "/custom-css", name: "general", component: page(() => import("./pages/settings/custom-css.vue")), }, { path: "/custom-katex-macro", name: "general", component: page( () => import("./pages/settings/custom-katex-macro.vue"), ), }, { path: "/accounts", name: "profile", component: page(() => import("./pages/settings/accounts.vue")), }, { path: "/account-info", name: "other", component: page(() => import("./pages/settings/account-info.vue")), }, { path: "/delete-account", name: "other", component: page(() => import("./pages/settings/delete-account.vue")), }, { path: "/other", name: "other", component: page(() => import("./pages/settings/other.vue")), }, { path: "/", component: page(() => import("./pages/_empty_.vue")), }, ], }, { path: "/reset-password/:token?", component: page(() => import("./pages/reset-password.vue")), }, { path: "/signup-complete/:code", component: page(() => import("./pages/signup-complete.vue")), }, { path: "/verify-email/:code", component: page(() => import("./pages/verify-email.vue")), }, { path: "/announcements", component: page(() => import("./pages/announcements.vue")), }, { path: "/about", component: page(() => import("./pages/about.vue")), hash: "initialTab", }, { path: "/about-firefish", component: page(() => import("./pages/about-firefish.vue")), }, { path: "/theme-editor", component: page(() => import("./pages/theme-editor.vue")), loginRequired: true, }, { path: "/explore/tags/:tag", component: page(() => import("./pages/explore.vue")), }, { path: "/explore", component: page(() => import("./pages/explore.vue")), }, { path: "/search", component: isSignedIn ? page(() => import("./pages/search.vue")) : page(() => import("./pages/not-found.vue")), query: { q: "query", user: "user", host: "host", since: "since", until: "until", withFiles: "withFiles", channel: "channel", }, }, { path: "/authorize-follow", component: page(() => import("./pages/follow.vue")), loginRequired: true, }, { path: "/share", component: page(() => import("./pages/share.vue")), loginRequired: true, }, { path: "/api-console", component: page(() => import("./pages/api-console.vue")), loginRequired: true, }, { path: "/mfm-cheat-sheet", component: page(() => import("./pages/mfm-cheat-sheet.vue")), }, { path: "/scratchpad", component: page(() => import("./pages/scratchpad.vue")), }, { path: "/preview", component: page(() => import("./pages/preview.vue")), }, { path: "/auth/:token", component: page(() => import("./pages/auth.vue")), }, { path: "/miauth/:session", component: page(() => import("./pages/miauth.vue")), query: { callback: "callback", name: "name", icon: "icon", permission: "permission", }, }, { path: "/tags/:tag", component: page(() => import("./pages/tag.vue")), }, { path: "/pages/new", component: page(() => import("./pages/page-editor/page-editor.vue")), loginRequired: true, }, { path: "/pages/edit/:initPageId", component: page(() => import("./pages/page-editor/page-editor.vue")), loginRequired: true, }, { path: "/pages", component: page(() => import("./pages/pages.vue")), }, { path: "/gallery/:postId/edit", component: page(() => import("./pages/gallery/edit.vue")), loginRequired: true, }, { path: "/gallery/new", component: page(() => import("./pages/gallery/edit.vue")), loginRequired: true, }, { path: "/gallery/:postId", component: page(() => import("./pages/gallery/post.vue")), }, { path: "/gallery", component: page(() => import("./pages/gallery/index.vue")), }, { path: "/channels/:channelId/edit", component: page(() => import("./pages/channel-editor.vue")), loginRequired: true, }, { path: "/channels/new", component: page(() => import("./pages/channel-editor.vue")), loginRequired: true, }, { path: "/channels/:channelId", component: page(() => import("./pages/channel.vue")), }, { path: "/channels", component: page(() => import("./pages/channels.vue")), }, { path: "/registry/keys/system/:path(*)?", component: page(() => import("./pages/registry.keys.vue")), }, { path: "/registry/value/system/:path(*)?", component: page(() => import("./pages/registry.value.vue")), }, { path: "/registry", component: page(() => import("./pages/registry.vue")), }, { path: "/admin/file/:fileId", component: isModerator ? page(() => import("./pages/admin-file.vue")) : page(() => import("./pages/not-found.vue")), }, { path: "/admin/emojis", name: "emojis", component: isModerator || isEmojiMod ? page(() => import("./pages/admin/emojis.vue")) : page(() => import("./pages/not-found.vue")), }, { path: "/admin", component: isModerator ? page(() => import("./pages/admin/index.vue")) : page(() => import("./pages/not-found.vue")), children: [ { path: "/overview", name: "overview", component: page(() => import("./pages/admin/overview.vue")), }, { path: "/users", name: "users", component: page(() => import("./pages/admin/users.vue")), }, { path: "/hashtags", name: "hashtags", component: page(() => import("./pages/admin/hashtags.vue")), }, { path: "/federation", name: "federation", component: page(() => import("./pages/admin/federation.vue")), }, { path: "/queue", name: "queue", component: page(() => import("./pages/admin/queue.vue")), }, { path: "/files", name: "files", component: page(() => import("./pages/admin/files.vue")), }, { path: "/announcements", name: "announcements", component: page(() => import("./pages/admin/announcements.vue")), }, { path: "/ads", name: "ads", component: page(() => import("./pages/admin/promotions.vue")), }, { path: "/database", name: "database", component: page(() => import("./pages/admin/database.vue")), }, { path: "/abuses", name: "abuses", component: page(() => import("./pages/admin/abuses.vue")), }, { path: "/settings", name: "settings", component: page(() => import("./pages/admin/settings.vue")), }, { path: "/email-settings", name: "email-settings", component: page(() => import("./pages/admin/email-settings.vue")), }, { path: "/object-storage", name: "object-storage", component: page(() => import("./pages/admin/object-storage.vue")), }, { path: "/security", name: "security", component: page(() => import("./pages/admin/security.vue")), }, { path: "/relays", name: "relays", component: page(() => import("./pages/admin/relays.vue")), }, { path: "/instance-block", name: "instance-block", component: page(() => import("./pages/admin/instance-block.vue")), }, { path: "/proxy-account", name: "proxy-account", component: page(() => import("./pages/admin/proxy-account.vue")), }, { path: "/other-settings", name: "other-settings", component: page(() => import("./pages/admin/other-settings.vue")), }, { path: "/other-settings", name: "other-settings", component: page(() => import("./pages/admin/custom-css.vue")), }, { path: "/experiments", name: "experiments", component: page(() => import("./pages/admin/experiments.vue")), }, { path: "/", component: page(() => import("./pages/_empty_.vue")), }, ], }, { path: "/my/notifications", component: page(() => import("./pages/notifications.vue")), loginRequired: true, }, { path: "/my/favorites", component: page(() => import("./pages/favorites.vue")), loginRequired: true, }, { name: "messaging", path: "/my/messaging", component: page(() => import("./pages/messaging/index.vue")), loginRequired: true, }, { path: "/my/messaging/:userAcct", component: page(() => import("./pages/messaging/messaging-room.vue")), loginRequired: true, }, { path: "/my/messaging/group/:groupId", component: page(() => import("./pages/messaging/messaging-room.vue")), loginRequired: true, }, { path: "/my/drive/folder/:folder", component: page(() => import("./pages/drive.vue")), loginRequired: true, }, { path: "/my/drive/file/:fileId/attached", component: page(() => import("./pages/attached-files.vue")), loginRequired: true, }, { path: "/my/drive", component: page(() => import("./pages/drive.vue")), loginRequired: true, }, { path: "/my/follow-requests", component: page(() => import("./pages/follow-requests.vue")), loginRequired: true, }, { path: "/my/lists/:listId", component: page(() => import("./pages/my-lists/list.vue")), loginRequired: true, }, { path: "/my/lists", component: page(() => import("./pages/my-lists/index.vue")), loginRequired: true, }, { path: "/my/clips", component: page(() => import("./pages/my-clips/index.vue")), loginRequired: true, }, { path: "/my/groups", component: page(() => import("./pages/my-groups/index.vue")), loginRequired: true, }, { path: "/my/groups/:groupId", component: page(() => import("./pages/my-groups/group.vue")), loginRequired: true, }, { path: "/my/antennas/create", component: page(() => import("./pages/my-antennas/create.vue")), loginRequired: true, }, { path: "/my/antennas/:antennaId", component: page(() => import("./pages/my-antennas/edit.vue")), loginRequired: true, }, { path: "/my/antennas", component: page(() => import("./pages/my-antennas/index.vue")), loginRequired: true, }, { path: "/timeline", component: page(() => import("./pages/timeline.vue")), }, { path: "/timeline/list/:listId", component: page(() => import("./pages/user-list-timeline.vue")), loginRequired: true, }, { path: "/timeline/antenna/:antennaId", component: page(() => import("./pages/antenna-timeline.vue")), loginRequired: true, }, { name: "index", path: "/", component: $i ? page(() => import("./pages/timeline.vue")) : page(() => import("./pages/welcome.vue")), globalCacheKey: "index", }, { path: "/:(*)", component: page(() => import("./pages/not-found.vue")), }, ]; export const mainRouter = new Router( routes, location.pathname + location.search + location.hash, ); window.history.replaceState( { key: mainRouter.getCurrentKey() }, "", location.href, ); // TODO: このファイルでスクロール位置も管理する設計だとdeckに対応できないのでなんとかする // スクロール位置取得+スクロール位置設定関数をprovideする感じでも良いかも const scrollPosStore = new Map(); window.setInterval(() => { scrollPosStore.set(window.history.state?.key, window.scrollY); }, 1000); mainRouter.addListener("push", (ctx) => { window.history.pushState({ key: ctx.key }, "", ctx.path); const scrollPos = scrollPosStore.get(ctx.key) ?? 0; window.scroll({ top: scrollPos, behavior: "instant" }); if (scrollPos !== 0) { window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール window.scroll({ top: scrollPos, behavior: "instant" }); }, 100); } }); mainRouter.addListener("replace", (ctx) => { window.history.replaceState({ key: ctx.key }, "", ctx.path); }); mainRouter.addListener("same", () => { window.scroll({ top: 0, behavior: "smooth" }); }); window.addEventListener("popstate", (event) => { mainRouter.replace( location.pathname + location.search + location.hash, event.state?.key, false, ); const scrollPos = scrollPosStore.get(event.state?.key) ?? 0; window.scroll({ top: scrollPos, behavior: "instant" }); window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール window.scroll({ top: scrollPos, behavior: "instant" }); }, 100); }); export function useRouter(): Router { return inject("router", null) ?? mainRouter; }