refactor(frontend): router refactoring
This commit is contained in:
parent
7d4045e8b4
commit
409cd4fbd3
|
@ -273,7 +273,6 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
|
|||
query?: Record<string, string>;
|
||||
loginRequired?: boolean;
|
||||
hash?: string;
|
||||
globalCacheKey?: string;
|
||||
children?: RouteDef[];
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 ?? '');
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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>>();
|
||||
|
|
|
@ -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<{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 === '';
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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('');
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<{
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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[];
|
|
@ -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);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
export const mainRouter = createRouter(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();
|
||||
|
||||
export function useRouter(): Router {
|
||||
return inject(DI.router, null) ?? mainRouter;
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue