summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md1
-rw-r--r--packages/frontend/src/boot/common.ts4
-rw-r--r--packages/frontend/src/boot/main-boot.ts2
-rw-r--r--packages/frontend/src/components/MkAbuseReport.vue7
-rw-r--r--packages/frontend/src/components/MkDrive.file.vue2
-rw-r--r--packages/frontend/src/components/MkPageWindow.vue6
-rw-r--r--packages/frontend/src/components/MkSuperMenu.vue2
-rw-r--r--packages/frontend/src/components/global/MkA.vue2
-rw-r--r--packages/frontend/src/lib/nirax.ts340
-rw-r--r--packages/frontend/src/pages/admin/index.vue2
-rw-r--r--packages/frontend/src/pages/admin/roles.edit.vue2
-rw-r--r--packages/frontend/src/pages/admin/roles.role.vue2
-rw-r--r--packages/frontend/src/pages/admin/roles.vue2
-rw-r--r--packages/frontend/src/pages/antenna-timeline.vue2
-rw-r--r--packages/frontend/src/pages/channel-editor.vue2
-rw-r--r--packages/frontend/src/pages/channel.vue2
-rw-r--r--packages/frontend/src/pages/channels.vue2
-rw-r--r--packages/frontend/src/pages/drive.file.info.vue2
-rw-r--r--packages/frontend/src/pages/flash/flash-edit.vue2
-rw-r--r--packages/frontend/src/pages/flash/flash-index.vue2
-rw-r--r--packages/frontend/src/pages/gallery/edit.vue2
-rw-r--r--packages/frontend/src/pages/gallery/index.vue2
-rw-r--r--packages/frontend/src/pages/gallery/post.vue2
-rw-r--r--packages/frontend/src/pages/lookup.vue2
-rw-r--r--packages/frontend/src/pages/my-antennas/create.vue2
-rw-r--r--packages/frontend/src/pages/my-antennas/edit.vue2
-rw-r--r--packages/frontend/src/pages/my-lists/list.vue2
-rw-r--r--packages/frontend/src/pages/page-editor/page-editor.vue2
-rw-r--r--packages/frontend/src/pages/page.vue2
-rw-r--r--packages/frontend/src/pages/pages.vue2
-rw-r--r--packages/frontend/src/pages/reset-password.vue2
-rw-r--r--packages/frontend/src/pages/reversi/game.setting.vue2
-rw-r--r--packages/frontend/src/pages/reversi/game.vue2
-rw-r--r--packages/frontend/src/pages/reversi/index.vue2
-rw-r--r--packages/frontend/src/pages/search.note.vue2
-rw-r--r--packages/frontend/src/pages/search.user.vue2
-rw-r--r--packages/frontend/src/pages/settings/index.vue2
-rw-r--r--packages/frontend/src/pages/settings/plugin.install.vue2
-rw-r--r--packages/frontend/src/pages/settings/theme.install.vue2
-rw-r--r--packages/frontend/src/pages/settings/webhook.edit.vue2
-rw-r--r--packages/frontend/src/pages/user-list-timeline.vue2
-rw-r--r--packages/frontend/src/pages/user/home.vue2
-rw-r--r--packages/frontend/src/router.definition.ts (renamed from packages/frontend/src/router/definition.ts)12
-rw-r--r--packages/frontend/src/router.ts355
-rw-r--r--packages/frontend/src/router/main.ts199
-rw-r--r--packages/frontend/src/router/supplier.ts31
-rw-r--r--packages/frontend/src/ui/_common_/navbar.vue2
-rw-r--r--packages/frontend/src/ui/_common_/sw-inject.ts2
-rw-r--r--packages/frontend/src/ui/classic.vue2
-rw-r--r--packages/frontend/src/ui/deck.vue2
-rw-r--r--packages/frontend/src/ui/deck/main-column.vue2
-rw-r--r--packages/frontend/src/ui/minimum.vue2
-rw-r--r--packages/frontend/src/ui/universal.vue2
-rw-r--r--packages/frontend/src/ui/visitor.vue2
-rw-r--r--packages/frontend/src/ui/zen.vue2
-rw-r--r--packages/frontend/src/utility/get-user-menu.ts2
-rw-r--r--packages/frontend/src/utility/lookup.ts4
57 files changed, 427 insertions, 626 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0c63f69cf1..a3aedfa9eb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -273,7 +273,6 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
query?: Record<string, string>;
loginRequired?: boolean;
hash?: string;
- globalCacheKey?: string;
children?: RouteDef[];
}
```
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 10bcddbde7..a520be9f8a 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -26,8 +26,6 @@ import { deckStore } from '@/ui/deck/deck-store.js';
import { analytics, initAnalytics } from '@/analytics.js';
import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
-import { setupRouter } from '@/router/main.js';
-import { createMainRouter } from '@/router/definition.js';
import { prefer } from '@/preferences.js';
import { $i } from '@/i.js';
@@ -267,8 +265,6 @@ export async function common(createVue: () => App<Element>) {
const app = createVue();
- setupRouter(app, createMainRouter);
-
if (_DEV_) {
app.config.performance = true;
}
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index 46668cb934..62ee0c5d72 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -24,7 +24,7 @@ import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
import { initializeSw } from '@/utility/initialize-sw.js';
import { emojiPicker } from '@/utility/emoji-picker.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { makeHotkey } from '@/utility/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import { prefer } from '@/preferences.js';
diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue
index 6892435a65..d6f65eca5d 100644
--- a/packages/frontend/src/components/MkAbuseReport.vue
+++ b/packages/frontend/src/components/MkAbuseReport.vue
@@ -88,9 +88,9 @@ import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js';
import MkFolder from '@/components/MkFolder.vue';
import RouterView from '@/components/global/RouterView.vue';
-import { useRouterFactory } from '@/router/supplier';
import MkTextarea from '@/components/MkTextarea.vue';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import { createRouter } from '@/router.js';
const props = defineProps<{
report: Misskey.entities.AdminAbuseUserReportsResponse[number];
@@ -100,10 +100,9 @@ const emit = defineEmits<{
(ev: 'resolved', reportId: string): void;
}>();
-const routerFactory = useRouterFactory();
-const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`);
+const targetRouter = createRouter(`/admin/user/${props.report.targetUserId}`);
targetRouter.init();
-const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`);
+const reporterRouter = createRouter(`/admin/user/${props.report.reporterId}`);
reporterRouter.init();
const moderationNote = ref(props.report.moderationNote ?? '');
diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue
index f02a767186..c54d9eb4d5 100644
--- a/packages/frontend/src/components/MkDrive.file.vue
+++ b/packages/frontend/src/components/MkDrive.file.vue
@@ -47,7 +47,7 @@ import { i18n } from '@/i18n.js';
import { $i } from '@/i.js';
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
import { deviceKind } from '@/utility/device-kind.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index a6049b4d91..fae4246335 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -41,8 +41,7 @@ import { i18n } from '@/i18n.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/utility/achievements.js';
-import { useRouterFactory } from '@/router/supplier.js';
-import { mainRouter } from '@/router/main.js';
+import { createRouter, mainRouter } from '@/router.js';
import { analytics } from '@/analytics.js';
import { DI } from '@/di.js';
import { prefer } from '@/preferences.js';
@@ -55,8 +54,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const routerFactory = useRouterFactory();
-const windowRouter = routerFactory(props.initialPath);
+const windowRouter = createRouter(props.initialPath);
const pageMetadata = ref<null | PageMetadata>(null);
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index fddf3934bb..a094718382 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -98,7 +98,7 @@ import type { SearchIndexItem } from '@/utility/autogen/settings-search-index.js
import MkInput from '@/components/MkInput.vue';
import { i18n } from '@/i18n.js';
import { getScrollContainer } from '@@/js/scroll.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import { initIntlString, compareStringIncludes } from '@/utility/intl-string.js';
const props = defineProps<{
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 173f6a849f..3403418991 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -19,7 +19,7 @@ import { url } from '@@/js/config.js';
import * as os from '@/os.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const props = withDefaults(defineProps<{
to: string;
diff --git a/packages/frontend/src/lib/nirax.ts b/packages/frontend/src/lib/nirax.ts
new file mode 100644
index 0000000000..cc20d497e6
--- /dev/null
+++ b/packages/frontend/src/lib/nirax.ts
@@ -0,0 +1,340 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+// NIRAX --- A lightweight router
+
+import { onMounted, shallowRef } from 'vue';
+import { EventEmitter } from 'eventemitter3';
+import type { Component, ShallowRef } from 'vue';
+
+function safeURIDecode(str: string): string {
+ try {
+ return decodeURIComponent(str);
+ } catch {
+ return str;
+ }
+}
+
+interface RouteDefBase {
+ path: string;
+ query?: Record<string, string>;
+ loginRequired?: boolean;
+ name?: string;
+ hash?: string;
+ children?: RouteDef[];
+}
+
+interface RouteDefWithComponent extends RouteDefBase {
+ component: Component,
+}
+
+interface RouteDefWithRedirect extends RouteDefBase {
+ redirect: string | ((props: Map<string, string | boolean>) => string);
+}
+
+export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
+
+export type RouterFlag = 'forcePage';
+
+type ParsedPath = (string | {
+ name: string;
+ startsWith?: string;
+ wildcard?: boolean;
+ optional?: boolean;
+})[];
+
+export type RouterEvent = {
+ change: (ctx: {
+ beforePath: string;
+ path: string;
+ resolved: Resolved;
+ }) => void;
+ replace: (ctx: {
+ path: string;
+ }) => void;
+ push: (ctx: {
+ beforePath: string;
+ path: string;
+ route: RouteDef | null;
+ props: Map<string, string> | null;
+ }) => void;
+ same: () => void;
+};
+
+export type Resolved = {
+ route: RouteDef;
+ props: Map<string, string | boolean>;
+ child?: Resolved;
+ redirected?: boolean;
+
+ /** @internal */
+ _parsedRoute: {
+ fullPath: string;
+ queryString: string | null;
+ hash: string | null;
+ };
+};
+
+function parsePath(path: string): ParsedPath {
+ const res = [] as ParsedPath;
+
+ path = path.substring(1);
+
+ for (const part of path.split('/')) {
+ if (part.includes(':')) {
+ const prefix = part.substring(0, part.indexOf(':'));
+ const placeholder = part.substring(part.indexOf(':') + 1);
+ const wildcard = placeholder.includes('(*)');
+ const optional = placeholder.endsWith('?');
+ res.push({
+ name: placeholder.replace('(*)', '').replace('?', ''),
+ startsWith: prefix !== '' ? prefix : undefined,
+ wildcard,
+ optional,
+ });
+ } else if (part.length !== 0) {
+ res.push(part);
+ }
+ }
+
+ return res;
+}
+
+export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvent> {
+ private routes: DEF;
+ public current: Resolved;
+ public currentRef: ShallowRef<Resolved>;
+ public currentRoute: ShallowRef<RouteDef>;
+ private currentPath: string;
+ private isLoggedIn: boolean;
+ private notFoundPageComponent: Component;
+ private redirectCount = 0;
+
+ public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null;
+
+ constructor(routes: DEF, currentPath: Nirax<DEF>['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
+ super();
+
+ this.routes = routes;
+ this.current = this.resolve(currentPath)!;
+ this.currentRef = shallowRef(this.current);
+ this.currentRoute = shallowRef(this.current.route);
+ this.currentPath = currentPath;
+ this.isLoggedIn = isLoggedIn;
+ this.notFoundPageComponent = notFoundPageComponent;
+ }
+
+ public init() {
+ const res = this.navigate(this.currentPath, false);
+ this.emit('replace', {
+ path: res._parsedRoute.fullPath,
+ });
+ }
+
+ public resolve(path: string): Resolved | null {
+ const fullPath = path;
+ let queryString: string | null = null;
+ let hash: string | null = null;
+ if (path[0] === '/') path = path.substring(1);
+ if (path.includes('#')) {
+ hash = path.substring(path.indexOf('#') + 1);
+ path = path.substring(0, path.indexOf('#'));
+ }
+ if (path.includes('?')) {
+ queryString = path.substring(path.indexOf('?') + 1);
+ path = path.substring(0, path.indexOf('?'));
+ }
+
+ const _parsedRoute = {
+ fullPath,
+ queryString,
+ hash,
+ };
+
+ function check(routes: RouteDef[], _parts: string[]): Resolved | null {
+ forEachRouteLoop:
+ for (const route of routes) {
+ let parts = [..._parts];
+ const props = new Map<string, string>();
+
+ pathMatchLoop:
+ for (const p of parsePath(route.path)) {
+ if (typeof p === 'string') {
+ if (p === parts[0]) {
+ parts.shift();
+ } else {
+ continue forEachRouteLoop;
+ }
+ } else {
+ if (parts[0] == null && !p.optional) {
+ continue forEachRouteLoop;
+ }
+ if (p.wildcard) {
+ if (parts.length !== 0) {
+ props.set(p.name, safeURIDecode(parts.join('/')));
+ parts = [];
+ }
+ break pathMatchLoop;
+ } else {
+ if (p.startsWith) {
+ if (parts[0] == null || !parts[0].startsWith(p.startsWith)) continue forEachRouteLoop;
+
+ props.set(p.name, safeURIDecode(parts[0].substring(p.startsWith.length)));
+ parts.shift();
+ } else {
+ if (parts[0]) {
+ props.set(p.name, safeURIDecode(parts[0]));
+ }
+ parts.shift();
+ }
+ }
+ }
+ }
+
+ if (parts.length === 0) {
+ if (route.children) {
+ const child = check(route.children, []);
+ if (child) {
+ return {
+ route,
+ props,
+ child,
+ _parsedRoute,
+ };
+ } else {
+ continue forEachRouteLoop;
+ }
+ }
+
+ if (route.hash != null && hash != null) {
+ props.set(route.hash, safeURIDecode(hash));
+ }
+
+ if (route.query != null && queryString != null) {
+ const queryObject = [...new URLSearchParams(queryString).entries()]
+ .reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {});
+
+ for (const q in route.query) {
+ const as = route.query[q];
+ if (queryObject[q]) {
+ props.set(as, safeURIDecode(queryObject[q]));
+ }
+ }
+ }
+
+ return {
+ route,
+ props,
+ _parsedRoute,
+ };
+ } else {
+ if (route.children) {
+ const child = check(route.children, parts);
+ if (child) {
+ return {
+ route,
+ props,
+ child,
+ _parsedRoute,
+ };
+ } else {
+ continue forEachRouteLoop;
+ }
+ } else {
+ continue forEachRouteLoop;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ const _parts = path.split('/').filter(part => part.length !== 0);
+
+ return check(this.routes, _parts);
+ }
+
+ private navigate(path: string, emitChange = true, _redirected = false): Resolved {
+ const beforePath = this.currentPath;
+ this.currentPath = path;
+
+ const res = this.resolve(this.currentPath);
+
+ if (res == null) {
+ throw new Error('no route found for: ' + path);
+ }
+
+ if ('redirect' in res.route) {
+ let redirectPath: string;
+ if (typeof res.route.redirect === 'function') {
+ redirectPath = res.route.redirect(res.props);
+ } else {
+ redirectPath = res.route.redirect + (res._parsedRoute.queryString ? '?' + res._parsedRoute.queryString : '') + (res._parsedRoute.hash ? '#' + res._parsedRoute.hash : '');
+ }
+ if (_DEV_) console.log('Redirecting to: ', redirectPath);
+ if (_redirected && this.redirectCount++ > 10) {
+ throw new Error('redirect loop detected');
+ }
+ return this.navigate(redirectPath, emitChange, true);
+ }
+
+ if (res.route.loginRequired && !this.isLoggedIn) {
+ res.route.component = this.notFoundPageComponent;
+ res.props.set('showLoginPopup', true);
+ }
+
+ this.current = res;
+ this.currentRef.value = res;
+ this.currentRoute.value = res.route;
+
+ if (emitChange && res.route.path !== '/:(*)') {
+ this.emit('change', {
+ beforePath,
+ path,
+ resolved: res,
+ });
+ }
+
+ this.redirectCount = 0;
+ return {
+ ...res,
+ redirected: _redirected,
+ };
+ }
+
+ public getCurrentPath() {
+ return this.currentPath;
+ }
+
+ public push(path: string, flag?: RouterFlag) {
+ const beforePath = this.currentPath;
+ if (path === beforePath) {
+ this.emit('same');
+ return;
+ }
+ if (this.navHook) {
+ const cancel = this.navHook(path, flag);
+ if (cancel) return;
+ }
+ const res = this.navigate(path);
+ if (res.route.path === '/:(*)') {
+ location.href = path;
+ } else {
+ this.emit('push', {
+ beforePath,
+ path: res._parsedRoute.fullPath,
+ route: res.route,
+ props: res.props,
+ });
+ }
+ }
+
+ public replace(path: string) {
+ const res = this.navigate(path);
+ this.emit('replace', {
+ path: res._parsedRoute.fullPath,
+ });
+ }
+}
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 7e7467859b..cdf3b4230c 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -43,7 +43,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { lookupUser, lookupUserByEmail, lookupFile } from '@/utility/admin-lookup.js';
import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const isEmpty = (x: string | null) => x == null || x === '';
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index 129fabf489..7741064685 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -33,7 +33,7 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import MkButton from '@/components/MkButton.vue';
import { rolesCache } from '@/cache.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index e7ebd30a3b..631873a076 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -75,7 +75,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkPagination from '@/components/MkPagination.vue';
import { infoImageUrl } from '@/instance.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index e16fca8286..0428352350 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -295,7 +295,7 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { instance, fetchInstance } from '@/instance.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
const baseRoleQ = ref('');
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 542fa72126..f0587a5ca0 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -32,7 +32,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 80cefe12c3..d0656a163c 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -82,7 +82,7 @@ import { i18n } from '@/i18n.js';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkTextarea from '@/components/MkTextarea.vue';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 1245561169..1419e83df7 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -99,7 +99,7 @@ import { isSupportShare } from '@/utility/navigator.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { notesSearchAvailable } from '@/utility/check-permissions.js';
import { miLocalStorage } from '@/local-storage.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 071f5a048b..cf047fcd5d 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -71,7 +71,7 @@ import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue
index c2f57cb665..5390a48be5 100644
--- a/packages/frontend/src/pages/drive.file.info.vue
+++ b/packages/frontend/src/pages/drive.file.info.vue
@@ -86,7 +86,7 @@ import { infoImageUrl } from '@/instance.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index b04974b7dc..16eeba7eea 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -53,7 +53,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const PRESET_DEFAULT = `/// @ ${AISCRIPT_VERSION}
diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index 3cd7c46c1e..6aee91dfda 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -47,7 +47,7 @@ import MkButton from '@/components/MkButton.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index 9cd59d0aa5..c85823ba86 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -50,7 +50,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index 14b3f7bf3c..04445c913c 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -54,7 +54,7 @@ import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index 6a9737e30f..9d37daf1ed 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -80,7 +80,7 @@ import { prefer } from '@/preferences.js';
import { $i } from '@/i.js';
import { isSupportShare } from '@/utility/navigator.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/lookup.vue b/packages/frontend/src/pages/lookup.vue
index e92ee0a4cc..fafad8af4a 100644
--- a/packages/frontend/src/pages/lookup.vue
+++ b/packages/frontend/src/pages/lookup.vue
@@ -25,7 +25,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import MkButton from '@/components/MkButton.vue';
const state = ref<'fetching' | 'done'>('fetching');
diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue
index 42d8b7be4c..e7460f0f93 100644
--- a/packages/frontend/src/pages/my-antennas/create.vue
+++ b/packages/frontend/src/pages/my-antennas/create.vue
@@ -16,7 +16,7 @@ import { computed } from 'vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { antennasCache } from '@/cache.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import MkAntennaEditor from '@/components/MkAntennaEditor.vue';
const router = useRouter();
diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue
index acd368b5e2..83d1183ddd 100644
--- a/packages/frontend/src/pages/my-antennas/edit.vue
+++ b/packages/frontend/src/pages/my-antennas/edit.vue
@@ -19,7 +19,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { antennasCache } from '@/cache.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index 782c069839..08ac3b4625 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -68,7 +68,7 @@ import MkInput from '@/components/MkInput.vue';
import { userListsCache } from '@/cache.js';
import { ensureSignin } from '@/i.js';
import MkPagination from '@/components/MkPagination.vue';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
const $i = ensureSignin();
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index e2f6084252..95a2d6d616 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -76,7 +76,7 @@ import { selectFile } from '@/utility/select-file.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { $i } from '@/i.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { getPageBlockList } from '@/pages/page-editor/common.js';
const props = defineProps<{
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 523d443359..cad5f2e109 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -120,7 +120,7 @@ import { isSupportShare } from '@/utility/navigator.js';
import { instance } from '@/instance.js';
import { getStaticImageUrl } from '@/utility/media-proxy.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js';
diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue
index f9bb825bd0..e41a7da055 100644
--- a/packages/frontend/src/pages/pages.vue
+++ b/packages/frontend/src/pages/pages.vue
@@ -45,7 +45,7 @@ import MkButton from '@/components/MkButton.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/reset-password.vue b/packages/frontend/src/pages/reset-password.vue
index d84c8f33dd..fda365fe52 100644
--- a/packages/frontend/src/pages/reset-password.vue
+++ b/packages/frontend/src/pages/reset-password.vue
@@ -26,7 +26,7 @@ import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
const props = defineProps<{
token?: string;
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
index 75d89be6b0..d2720a79fc 100644
--- a/packages/frontend/src/pages/reversi/game.setting.vue
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -122,7 +122,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js';
import type { MenuItem } from '@/types/menu.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const $i = ensureSignin();
diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue
index fb2019ae3c..a447572cc0 100644
--- a/packages/frontend/src/pages/reversi/game.vue
+++ b/packages/frontend/src/pages/reversi/game.vue
@@ -18,7 +18,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
import { useStream } from '@/stream.js';
import { ensureSignin } from '@/i.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import * as os from '@/os.js';
import { url } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
index d66ff8db05..e3f01d9938 100644
--- a/packages/frontend/src/pages/reversi/index.vue
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -115,7 +115,7 @@ import MkFolder from '@/components/MkFolder.vue';
import { i18n } from '@/i18n.js';
import { $i } from '@/i.js';
import MkPagination from '@/components/MkPagination.vue';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import * as os from '@/os.js';
import { useInterval } from '@@/js/use-interval.js';
import { pleaseLogin } from '@/utility/please-login.js';
diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue
index 4801e9bc27..1dc55d002c 100644
--- a/packages/frontend/src/pages/search.note.vue
+++ b/packages/frontend/src/pages/search.note.vue
@@ -121,7 +121,7 @@ import { instance } from '@/instance.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { apLookup } from '@/utility/lookup.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import MkButton from '@/components/MkButton.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkInput from '@/components/MkInput.vue';
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index 40019cc870..101de6a64f 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -37,7 +37,7 @@ import { instance } from '@/instance.js';
import * as os from '@/os.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const props = withDefaults(defineProps<{
query?: string,
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index f6feaee453..89dc9581c2 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -42,7 +42,7 @@ import { clearCache } from '@/utility/clear-cache.js';
import { instance } from '@/instance.js';
import { definePage, provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import * as os from '@/os.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import { searchIndexes } from '@/utility/autogen/settings-search-index.js';
import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utility.js';
import { store } from '@/store.js';
diff --git a/packages/frontend/src/pages/settings/plugin.install.vue b/packages/frontend/src/pages/settings/plugin.install.vue
index e984ed7f8a..22b53b4b96 100644
--- a/packages/frontend/src/pages/settings/plugin.install.vue
+++ b/packages/frontend/src/pages/settings/plugin.install.vue
@@ -26,7 +26,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { installPlugin } from '@/plugin.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
const code = ref<string | null>(null);
diff --git a/packages/frontend/src/pages/settings/theme.install.vue b/packages/frontend/src/pages/settings/theme.install.vue
index 68e4bef5c4..ac95279402 100644
--- a/packages/frontend/src/pages/settings/theme.install.vue
+++ b/packages/frontend/src/pages/settings/theme.install.vue
@@ -24,7 +24,7 @@ import { parseThemeCode, previewTheme, installTheme } from '@/theme.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
const installThemeCode = ref<string | null>(null);
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index 2de948c69d..6a6cec70ba 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -79,7 +79,7 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index d4c7c9386d..e4857c7d30 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -31,7 +31,7 @@ import { scroll } from '@@/js/scroll.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
const router = useRouter();
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 149481f99b..dfa43e1ef2 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -181,7 +181,7 @@ import { dateString } from '@/filters/date.js';
import { confetti } from '@/utility/confetti.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/utility/isFfVisibleForMe.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import { getStaticImageUrl } from '@/utility/media-proxy.js';
import MkSparkle from '@/components/MkSparkle.vue';
import { prefer } from '@/preferences.js';
diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router.definition.ts
index 923903ec3d..3b60ee68e3 100644
--- a/packages/frontend/src/router/definition.ts
+++ b/packages/frontend/src/router.definition.ts
@@ -5,8 +5,7 @@
import { defineAsyncComponent } from 'vue';
import type { AsyncComponentLoader } from 'vue';
-import type { RouteDef } from '@/router.js';
-import { Router } from '@/router.js';
+import type { RouteDef } from '@/lib/nirax.js';
import { $i, iAmModerator } from '@/i.js';
import MkLoading from '@/pages/_loading_.vue';
import MkError from '@/pages/_error_.vue';
@@ -17,7 +16,7 @@ export const page = (loader: AsyncComponentLoader) => defineAsyncComponent({
errorComponent: MkError,
});
-const routes: RouteDef[] = [{
+export const ROUTE_DEF = [{
path: '/@:username/pages/:pageName(*)',
component: page(() => import('@/pages/page.vue')),
}, {
@@ -567,7 +566,6 @@ const routes: RouteDef[] = [{
name: 'index',
path: '/',
component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')),
- globalCacheKey: 'index',
}, {
// テスト用リダイレクト設定。ログイン中ユーザのプロフィールにリダイレクトする
path: '/redirect-test',
@@ -576,8 +574,4 @@ const routes: RouteDef[] = [{
}, {
path: '/:(*)',
component: page(() => import('@/pages/not-found.vue')),
-}];
-
-export function createMainRouter(path: string): Router {
- return new Router(routes, path, !!$i, page(() => import('@/pages/not-found.vue')));
-}
+}] satisfies RouteDef[];
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index c6f3937cde..b5f59b30c1 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -3,339 +3,44 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-// NIRAX --- A lightweight router
+import { inject } from 'vue';
+import { page } from '@/router.definition.js';
+import { $i } from '@/i.js';
+import { Nirax } from '@/lib/nirax.js';
+import { ROUTE_DEF } from '@/router.definition.js';
+import { analytics } from '@/analytics.js';
+import { DI } from '@/di.js';
-import { onMounted, shallowRef } from 'vue';
-import { EventEmitter } from 'eventemitter3';
-import type { Component, ShallowRef } from 'vue';
+export type Router = Nirax<typeof ROUTE_DEF>;
-function safeURIDecode(str: string): string {
- try {
- return decodeURIComponent(str);
- } catch {
- return str;
- }
+export function createRouter(path: string): Router {
+ return new Nirax(ROUTE_DEF, path, !!$i, page(() => import('@/pages/not-found.vue')));
}
-interface RouteDefBase {
- path: string;
- query?: Record<string, string>;
- loginRequired?: boolean;
- name?: string;
- hash?: string;
- globalCacheKey?: string;
- children?: RouteDef[];
-}
-
-interface RouteDefWithComponent extends RouteDefBase {
- component: Component,
-}
-
-interface RouteDefWithRedirect extends RouteDefBase {
- redirect: string | ((props: Map<string, string | boolean>) => string);
-}
-
-export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
-
-export type RouterFlag = 'forcePage';
-
-type ParsedPath = (string | {
- name: string;
- startsWith?: string;
- wildcard?: boolean;
- optional?: boolean;
-})[];
-
-export type RouterEvent = {
- change: (ctx: {
- beforePath: string;
- path: string;
- resolved: Resolved;
- }) => void;
- replace: (ctx: {
- path: string;
- }) => void;
- push: (ctx: {
- beforePath: string;
- path: string;
- route: RouteDef | null;
- props: Map<string, string> | null;
- }) => void;
- same: () => void;
-};
-
-export type Resolved = {
- route: RouteDef;
- props: Map<string, string | boolean>;
- child?: Resolved;
- redirected?: boolean;
-
- /** @internal */
- _parsedRoute: {
- fullPath: string;
- queryString: string | null;
- hash: string | null;
- };
-};
-
-function parsePath(path: string): ParsedPath {
- const res = [] as ParsedPath;
-
- path = path.substring(1);
-
- for (const part of path.split('/')) {
- if (part.includes(':')) {
- const prefix = part.substring(0, part.indexOf(':'));
- const placeholder = part.substring(part.indexOf(':') + 1);
- const wildcard = placeholder.includes('(*)');
- const optional = placeholder.endsWith('?');
- res.push({
- name: placeholder.replace('(*)', '').replace('?', ''),
- startsWith: prefix !== '' ? prefix : undefined,
- wildcard,
- optional,
- });
- } else if (part.length !== 0) {
- res.push(part);
- }
- }
-
- return res;
-}
-
-export class Router extends EventEmitter<RouterEvent> {
- private routes: RouteDef[];
- public current: Resolved;
- public currentRef: ShallowRef<Resolved>;
- public currentRoute: ShallowRef<RouteDef>;
- private currentPath: string;
- private isLoggedIn: boolean;
- private notFoundPageComponent: Component;
- private redirectCount = 0;
-
- public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null;
-
- constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
- super();
-
- this.routes = routes;
- this.current = this.resolve(currentPath)!;
- this.currentRef = shallowRef(this.current);
- this.currentRoute = shallowRef(this.current.route);
- this.currentPath = currentPath;
- this.isLoggedIn = isLoggedIn;
- this.notFoundPageComponent = notFoundPageComponent;
- }
-
- public init() {
- const res = this.navigate(this.currentPath, false);
- this.emit('replace', {
- path: res._parsedRoute.fullPath,
- });
- }
-
- public resolve(path: string): Resolved | null {
- const fullPath = path;
- let queryString: string | null = null;
- let hash: string | null = null;
- if (path[0] === '/') path = path.substring(1);
- if (path.includes('#')) {
- hash = path.substring(path.indexOf('#') + 1);
- path = path.substring(0, path.indexOf('#'));
- }
- if (path.includes('?')) {
- queryString = path.substring(path.indexOf('?') + 1);
- path = path.substring(0, path.indexOf('?'));
- }
-
- const _parsedRoute = {
- fullPath,
- queryString,
- hash,
- };
-
- function check(routes: RouteDef[], _parts: string[]): Resolved | null {
- forEachRouteLoop:
- for (const route of routes) {
- let parts = [..._parts];
- const props = new Map<string, string>();
-
- pathMatchLoop:
- for (const p of parsePath(route.path)) {
- if (typeof p === 'string') {
- if (p === parts[0]) {
- parts.shift();
- } else {
- continue forEachRouteLoop;
- }
- } else {
- if (parts[0] == null && !p.optional) {
- continue forEachRouteLoop;
- }
- if (p.wildcard) {
- if (parts.length !== 0) {
- props.set(p.name, safeURIDecode(parts.join('/')));
- parts = [];
- }
- break pathMatchLoop;
- } else {
- if (p.startsWith) {
- if (parts[0] == null || !parts[0].startsWith(p.startsWith)) continue forEachRouteLoop;
-
- props.set(p.name, safeURIDecode(parts[0].substring(p.startsWith.length)));
- parts.shift();
- } else {
- if (parts[0]) {
- props.set(p.name, safeURIDecode(parts[0]));
- }
- parts.shift();
- }
- }
- }
- }
-
- if (parts.length === 0) {
- if (route.children) {
- const child = check(route.children, []);
- if (child) {
- return {
- route,
- props,
- child,
- _parsedRoute,
- };
- } else {
- continue forEachRouteLoop;
- }
- }
-
- if (route.hash != null && hash != null) {
- props.set(route.hash, safeURIDecode(hash));
- }
-
- if (route.query != null && queryString != null) {
- const queryObject = [...new URLSearchParams(queryString).entries()]
- .reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {});
-
- for (const q in route.query) {
- const as = route.query[q];
- if (queryObject[q]) {
- props.set(as, safeURIDecode(queryObject[q]));
- }
- }
- }
-
- return {
- route,
- props,
- _parsedRoute,
- };
- } else {
- if (route.children) {
- const child = check(route.children, parts);
- if (child) {
- return {
- route,
- props,
- child,
- _parsedRoute,
- };
- } else {
- continue forEachRouteLoop;
- }
- } else {
- continue forEachRouteLoop;
- }
- }
- }
-
- return null;
- }
-
- const _parts = path.split('/').filter(part => part.length !== 0);
-
- return check(this.routes, _parts);
- }
-
- private navigate(path: string, emitChange = true, _redirected = false): Resolved {
- const beforePath = this.currentPath;
- this.currentPath = path;
-
- const res = this.resolve(this.currentPath);
-
- if (res == null) {
- throw new Error('no route found for: ' + path);
- }
-
- if ('redirect' in res.route) {
- let redirectPath: string;
- if (typeof res.route.redirect === 'function') {
- redirectPath = res.route.redirect(res.props);
- } else {
- redirectPath = res.route.redirect + (res._parsedRoute.queryString ? '?' + res._parsedRoute.queryString : '') + (res._parsedRoute.hash ? '#' + res._parsedRoute.hash : '');
- }
- if (_DEV_) console.log('Redirecting to: ', redirectPath);
- if (_redirected && this.redirectCount++ > 10) {
- throw new Error('redirect loop detected');
- }
- return this.navigate(redirectPath, emitChange, true);
- }
-
- if (res.route.loginRequired && !this.isLoggedIn) {
- res.route.component = this.notFoundPageComponent;
- res.props.set('showLoginPopup', true);
- }
+export const mainRouter = createRouter(location.pathname + location.search + location.hash);
- this.current = res;
- this.currentRef.value = res;
- this.currentRoute.value = res.route;
+window.addEventListener('popstate', (event) => {
+ mainRouter.replace(location.pathname + location.search + location.hash);
+});
- if (emitChange && res.route.path !== '/:(*)') {
- this.emit('change', {
- beforePath,
- path,
- resolved: res,
- });
- }
+mainRouter.addListener('push', ctx => {
+ window.history.pushState({ }, '', ctx.path);
+});
- this.redirectCount = 0;
- return {
- ...res,
- redirected: _redirected,
- };
- }
+mainRouter.addListener('replace', ctx => {
+ window.history.replaceState({ }, '', ctx.path);
+});
- public getCurrentPath() {
- return this.currentPath;
- }
+mainRouter.addListener('change', ctx => {
+ console.log('mainRouter: change', ctx.path);
+ analytics.page({
+ path: ctx.path,
+ title: ctx.path,
+ });
+});
- public push(path: string, flag?: RouterFlag) {
- const beforePath = this.currentPath;
- if (path === beforePath) {
- this.emit('same');
- return;
- }
- if (this.navHook) {
- const cancel = this.navHook(path, flag);
- if (cancel) return;
- }
- const res = this.navigate(path);
- if (res.route.path === '/:(*)') {
- location.href = path;
- } else {
- this.emit('push', {
- beforePath,
- path: res._parsedRoute.fullPath,
- route: res.route,
- props: res.props,
- });
- }
- }
+mainRouter.init();
- public replace(path: string) {
- const res = this.navigate(path);
- this.emit('replace', {
- path: res._parsedRoute.fullPath,
- });
- }
+export function useRouter(): Router {
+ return inject(DI.router, null) ?? mainRouter;
}
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
deleted file mode 100644
index f294af059d..0000000000
--- a/packages/frontend/src/router/main.ts
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { EventEmitter } from 'eventemitter3';
-import type { Router, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/router.js';
-
-import type { App, ShallowRef } from 'vue';
-import { analytics } from '@/analytics.js';
-
-/**
- * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。
- * また、{@link Router}のインスタンスを作成するためのファクトリも{@link provide}経由で公開する(`routerFactory`というキーで取得可能)
- */
-export function setupRouter(app: App, routerFactory: ((path: string) => Router)): void {
- app.provide('routerFactory', routerFactory);
-
- const mainRouter = routerFactory(location.pathname + location.search + location.hash);
-
- window.addEventListener('popstate', (event) => {
- mainRouter.replace(location.pathname + location.search + location.hash);
- });
-
- mainRouter.addListener('push', ctx => {
- window.history.pushState({ }, '', ctx.path);
- });
-
- mainRouter.addListener('replace', ctx => {
- window.history.replaceState({ }, '', ctx.path);
- });
-
- mainRouter.addListener('change', ctx => {
- console.log('mainRouter: change', ctx.path);
- analytics.page({
- path: ctx.path,
- title: ctx.path,
- });
- });
-
- mainRouter.init();
-
- setMainRouter(mainRouter);
-}
-
-function getMainRouter(): Router {
- const router = mainRouterHolder;
- if (!router) {
- throw new Error('mainRouter is not found.');
- }
-
- return router;
-}
-
-/**
- * メインルータを設定する。一度設定すると、それ以降は変更できない。
- * {@link setupRouter}から呼び出されることのみを想定している。
- */
-export function setMainRouter(router: Router) {
- if (mainRouterHolder) {
- throw new Error('mainRouter is already exists.');
- }
-
- mainRouterHolder = router;
-}
-
-/**
- * {@link mainRouter}用のプロキシ実装。
- * {@link mainRouter}は起動シーケンスの一部にて初期化されるため、僅かにundefinedになる期間がある。
- * その僅かな期間のためだけに型をundefined込みにしたくないのでこのクラスを緩衝材として使用する。
- */
-class MainRouterProxy implements Router {
- private supplier: () => Router;
-
- constructor(supplier: () => Router) {
- this.supplier = supplier;
- }
-
- get current(): Resolved {
- return this.supplier().current;
- }
-
- get currentRef(): ShallowRef<Resolved> {
- return this.supplier().currentRef;
- }
-
- get currentRoute(): ShallowRef<RouteDef> {
- return this.supplier().currentRoute;
- }
-
- get navHook(): ((path: string, flag?: RouterFlag) => boolean) | null {
- return this.supplier().navHook;
- }
-
- set navHook(value) {
- this.supplier().navHook = value;
- }
-
- getCurrentPath(): string {
- return this.supplier().getCurrentPath();
- }
-
- push(path: string, flag?: RouterFlag): void {
- this.supplier().push(path, flag);
- }
-
- replace(path: string, key?: string | null): void {
- this.supplier().replace(path, key);
- }
-
- resolve(path: string): Resolved | null {
- return this.supplier().resolve(path);
- }
-
- init(): void {
- this.supplier().init();
- }
-
- eventNames(): Array<EventEmitter.EventNames<RouterEvent>> {
- return this.supplier().eventNames();
- }
-
- listeners<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- ): Array<EventEmitter.EventListener<RouterEvent, T>> {
- return this.supplier().listeners(event);
- }
-
- listenerCount(
- event: EventEmitter.EventNames<RouterEvent>,
- ): number {
- return this.supplier().listenerCount(event);
- }
-
- emit<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- ...args: EventEmitter.EventArgs<RouterEvent, T>
- ): boolean {
- return this.supplier().emit(event, ...args);
- }
-
- on<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- ): this {
- this.supplier().on(event, fn, context);
- return this;
- }
-
- addListener<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- ): this {
- this.supplier().addListener(event, fn, context);
- return this;
- }
-
- once<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- ): this {
- this.supplier().once(event, fn, context);
- return this;
- }
-
- removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn?: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- once?: boolean,
- ): this {
- this.supplier().removeListener(event, fn, context, once);
- return this;
- }
-
- off<T extends EventEmitter.EventNames<RouterEvent>>(
- event: T,
- fn?: EventEmitter.EventListener<RouterEvent, T>,
- context?: any,
- once?: boolean,
- ): this {
- this.supplier().off(event, fn, context, once);
- return this;
- }
-
- removeAllListeners(
- event?: EventEmitter.EventNames<RouterEvent>,
- ): this {
- this.supplier().removeAllListeners(event);
- return this;
- }
-}
-
-let mainRouterHolder: Router | null = null;
-
-export const mainRouter: Router = new MainRouterProxy(getMainRouter);
diff --git a/packages/frontend/src/router/supplier.ts b/packages/frontend/src/router/supplier.ts
deleted file mode 100644
index 3c05b41c20..0000000000
--- a/packages/frontend/src/router/supplier.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { inject } from 'vue';
-import type { Router } from '@/router.js';
-import { mainRouter } from '@/router/main.js';
-import { DI } from '@/di.js';
-
-/**
- * メインの{@link Router}を取得する。
- * あらかじめ{@link setupRouter}を実行しておく必要がある({@link provide}により{@link Router}のインスタンスを注入可能であるならばこの限りではない)
- */
-export function useRouter(): Router {
- return inject(DI.router, null) ?? mainRouter;
-}
-
-/**
- * 任意の{@link Router}を取得するためのファクトリを取得する。
- * あらかじめ{@link setupRouter}を実行しておく必要がある。
- */
-export function useRouterFactory(): (path: string) => Router {
- const factory = inject<(path: string) => Router>('routerFactory');
- if (!factory) {
- console.error('routerFactory is not defined.');
- throw new Error('routerFactory is not defined.');
- }
-
- return factory;
-}
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 1810ec1743..754bf070fa 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -97,7 +97,7 @@ import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
-import { useRouter } from '@/router/supplier.js';
+import { useRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
import { $i } from '@/i.js';
diff --git a/packages/frontend/src/ui/_common_/sw-inject.ts b/packages/frontend/src/ui/_common_/sw-inject.ts
index ae61e497b5..1459881ba1 100644
--- a/packages/frontend/src/ui/_common_/sw-inject.ts
+++ b/packages/frontend/src/ui/_common_/sw-inject.ts
@@ -8,7 +8,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { $i } from '@/i.js';
import { getAccountFromId } from '@/utility/get-account-from-id.js';
import { deepClone } from '@/utility/clone.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { login } from '@/accounts.js';
export function swInject() {
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 969e30b3a9..fa63586ef7 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -58,7 +58,7 @@ import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index a5db4031e2..4e9ec7c586 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -114,7 +114,7 @@ import XWidgetsColumn from '@/ui/deck/widgets-column.vue';
import XMentionsColumn from '@/ui/deck/mentions-column.vue';
import XDirectColumn from '@/ui/deck/direct-column.vue';
import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { columns, layout, columnTypes, switchProfileMenu, addColumn as addColumnToStore, deleteProfile as deleteProfile_ } from '@/deck.js';
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index b4d494fb09..78454d2e49 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -28,7 +28,7 @@ import type { PageMetadata } from '@/page.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
index 7b58829ff5..4bfd240805 100644
--- a/packages/frontend/src/ui/minimum.vue
+++ b/packages/frontend/src/ui/minimum.vue
@@ -19,7 +19,7 @@ import { instanceName } from '@@/js/config.js';
import XCommon from './_common_/common.vue';
import type { PageMetadata } from '@/page.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { DI } from '@/di.js';
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index be933d5324..a6695de39d 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -110,7 +110,7 @@ import { $i } from '@/i.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { deviceKind } from '@/utility/device-kind.js';
import { miLocalStorage } from '@/local-storage.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
import { shouldSuggestRestoreBackup } from '@/preferences/utility.js';
import { DI } from '@/di.js';
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index 976c3584a7..ddc3761b04 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -36,7 +36,7 @@ import { instance } from '@/instance.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { i18n } from '@/i18n.js';
import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { DI } from '@/di.js';
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index d60611a19c..8a09ad80d9 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -30,7 +30,7 @@ import XCommon from './_common_/common.vue';
import type { PageMetadata } from '@/page.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { i18n } from '@/i18n.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { DI } from '@/di.js';
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
diff --git a/packages/frontend/src/utility/get-user-menu.ts b/packages/frontend/src/utility/get-user-menu.ts
index c28b2db6d7..de20f2678e 100644
--- a/packages/frontend/src/utility/get-user-menu.ts
+++ b/packages/frontend/src/utility/get-user-menu.ts
@@ -16,7 +16,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { $i, iAmModerator } from '@/i.js';
import { notesSearchAvailable, canSearchNonLocalNotes } from '@/utility/check-permissions.js';
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
import { genEmbedCode } from '@/utility/get-embed-code.js';
import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js';
diff --git a/packages/frontend/src/utility/lookup.ts b/packages/frontend/src/utility/lookup.ts
index e0b945e49b..90611094fa 100644
--- a/packages/frontend/src/utility/lookup.ts
+++ b/packages/frontend/src/utility/lookup.ts
@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import type { Router } from '@/router.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { Router } from '@/router.js';
-import { mainRouter } from '@/router/main.js';
+import { mainRouter } from '@/router.js';
export async function lookup(router?: Router) {
const _router = router ?? mainRouter;