refactor(frontend): router refactoring
This commit is contained in:
		| @@ -59,7 +59,7 @@ const windowRouter = createRouter(props.initialPath); | ||||
| const pageMetadata = ref<null | PageMetadata>(null); | ||||
| const windowEl = shallowRef<InstanceType<typeof MkWindow>>(); | ||||
| const history = ref<{ path: string; }[]>([{ | ||||
| 	path: windowRouter.getCurrentPath(), | ||||
| 	path: windowRouter.getCurrentFullPath(), | ||||
| }]); | ||||
| const buttonsLeft = computed(() => { | ||||
| 	const buttons: Record<string, unknown>[] = []; | ||||
| @@ -97,20 +97,20 @@ function getSearchMarker(path: string) { | ||||
| const searchMarkerId = ref<string | null>(getSearchMarker(props.initialPath)); | ||||
|  | ||||
| windowRouter.addListener('push', ctx => { | ||||
| 	history.value.push({ path: ctx.path }); | ||||
| 	history.value.push({ path: ctx.fullPath }); | ||||
| }); | ||||
|  | ||||
| windowRouter.addListener('replace', ctx => { | ||||
| 	history.value.pop(); | ||||
| 	history.value.push({ path: ctx.path }); | ||||
| 	history.value.push({ path: ctx.fullPath }); | ||||
| }); | ||||
|  | ||||
| windowRouter.addListener('change', ctx => { | ||||
| 	if (_DEV_) console.log('windowRouter: change', ctx.path); | ||||
| 	searchMarkerId.value = getSearchMarker(ctx.path); | ||||
| 	if (_DEV_) console.log('windowRouter: change', ctx.fullPath); | ||||
| 	searchMarkerId.value = getSearchMarker(ctx.fullPath); | ||||
| 	analytics.page({ | ||||
| 		path: ctx.path, | ||||
| 		title: ctx.path, | ||||
| 		path: ctx.fullPath, | ||||
| 		title: ctx.fullPath, | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| @@ -139,14 +139,14 @@ const contextmenu = computed(() => ([{ | ||||
| 	icon: 'ti ti-external-link', | ||||
| 	text: i18n.ts.openInNewTab, | ||||
| 	action: () => { | ||||
| 		window.open(url + windowRouter.getCurrentPath(), '_blank', 'noopener'); | ||||
| 		window.open(url + windowRouter.getCurrentFullPath(), '_blank', 'noopener'); | ||||
| 		windowEl.value?.close(); | ||||
| 	}, | ||||
| }, { | ||||
| 	icon: 'ti ti-link', | ||||
| 	text: i18n.ts.copyLink, | ||||
| 	action: () => { | ||||
| 		copyToClipboard(url + windowRouter.getCurrentPath()); | ||||
| 		copyToClipboard(url + windowRouter.getCurrentFullPath()); | ||||
| 	}, | ||||
| }])); | ||||
|  | ||||
| @@ -164,12 +164,12 @@ function close() { | ||||
| } | ||||
|  | ||||
| function expand() { | ||||
| 	mainRouter.push(windowRouter.getCurrentPath(), 'forcePage'); | ||||
| 	mainRouter.push(windowRouter.getCurrentFullPath(), 'forcePage'); | ||||
| 	windowEl.value?.close(); | ||||
| } | ||||
|  | ||||
| function popout() { | ||||
| 	_popout(windowRouter.getCurrentPath(), windowEl.value?.$el); | ||||
| 	_popout(windowRouter.getCurrentFullPath(), windowEl.value?.$el); | ||||
| 	windowEl.value?.close(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -14,8 +14,9 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { inject, onBeforeUnmount, provide, ref, shallowRef } from 'vue'; | ||||
| import type { Router, Resolved } from '@/router.js'; | ||||
| import { inject, provide, ref, shallowRef } from 'vue'; | ||||
| import type { Router } from '@/router.js'; | ||||
| import type { PathResolvedResult } from '@/lib/nirax.js'; | ||||
| import MkLoadingPage from '@/pages/_loading_.vue'; | ||||
| import { DI } from '@/di.js'; | ||||
|  | ||||
| @@ -32,7 +33,7 @@ if (router == null) { | ||||
| const currentDepth = inject(DI.routerCurrentDepth, 0); | ||||
| provide(DI.routerCurrentDepth, currentDepth + 1); | ||||
|  | ||||
| function resolveNested(current: Resolved, d = 0): Resolved | null { | ||||
| function resolveNested(current: PathResolvedResult, d = 0): PathResolvedResult | null { | ||||
| 	if (d === currentDepth) { | ||||
| 		return current; | ||||
| 	} else { | ||||
| @@ -47,19 +48,13 @@ function resolveNested(current: Resolved, d = 0): Resolved | null { | ||||
| const current = resolveNested(router.current)!; | ||||
| const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage); | ||||
| const currentPageProps = ref(current.props); | ||||
| const key = ref(router.getCurrentPath()); | ||||
| const key = ref(router.getCurrentFullPath()); | ||||
|  | ||||
| function onChange({ resolved }) { | ||||
| router.useListener('change', ({ resolved }) => { | ||||
| 	const current = resolveNested(resolved); | ||||
| 	if (current == null || 'redirect' in current.route) return; | ||||
| 	currentPageComponent.value = current.route.component; | ||||
| 	currentPageProps.value = current.props; | ||||
| 	key.value = router.getCurrentPath(); | ||||
| } | ||||
|  | ||||
| router.addListener('change', onChange); | ||||
|  | ||||
| onBeforeUnmount(() => { | ||||
| 	router.removeListener('change', onChange); | ||||
| 	key.value = router.getCurrentFullPath(); | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -5,10 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
|  | ||||
| <template> | ||||
| <div class="_pageContainer" style="height: 100%;"> | ||||
| 	<KeepAlive | ||||
| 		:max="prefer.s.numberOfPageCache" | ||||
| 		:exclude="pageCacheController" | ||||
| 	> | ||||
| 	<KeepAlive :max="prefer.s.numberOfPageCache"> | ||||
| 		<Suspense :timeout="0"> | ||||
| 			<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/> | ||||
|  | ||||
| @@ -21,10 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue'; | ||||
| import type { Router, Resolved, RouteDef } from '@/router.js'; | ||||
| import { inject, provide, ref, shallowRef } from 'vue'; | ||||
| import type { Router } from '@/router.js'; | ||||
| import { prefer } from '@/preferences.js'; | ||||
| import { globalEvents } from '@/events.js'; | ||||
| import MkLoadingPage from '@/pages/_loading_.vue'; | ||||
| import { DI } from '@/di.js'; | ||||
|  | ||||
| @@ -44,45 +40,12 @@ provide(DI.routerCurrentDepth, currentDepth + 1); | ||||
| const current = router.current!; | ||||
| const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage); | ||||
| const currentPageProps = ref(current.props); | ||||
| const key = ref(router.getCurrentPath()); | ||||
| const key = ref(router.getCurrentFullPath()); | ||||
|  | ||||
| function onChange({ resolved }) { | ||||
| router.useListener('change', ({ resolved }) => { | ||||
| 	if (resolved == null || 'redirect' in resolved.route) return; | ||||
| 	currentPageComponent.value = resolved.route.component; | ||||
| 	currentPageProps.value = resolved.props; | ||||
| 	key.value = router.getCurrentPath(); | ||||
|  | ||||
| 	nextTick(() => { | ||||
| 		// ページ遷移完了後に再びキャッシュを有効化 | ||||
| 		if (clearCacheRequested.value) { | ||||
| 			clearCacheRequested.value = false; | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| router.addListener('change', onChange); | ||||
|  | ||||
| // #region キャッシュ制御 | ||||
|  | ||||
| /** | ||||
| 	 * キャッシュクリアが有効になったら、全キャッシュをクリアする | ||||
| 	 * | ||||
| 	 * keepAlive側にwatcherがあるのですぐ消えるとはおもうけど、念のためページ遷移完了まではキャッシュを無効化しておく。 | ||||
| 	 * キャッシュ有効時向けにexcludeを使いたい場合は、pageCacheControllerに並列に突っ込むのではなく、下に追記すること | ||||
| 	 */ | ||||
| const pageCacheController = computed(() => clearCacheRequested.value ? /.*/ : undefined); | ||||
| const clearCacheRequested = ref(false); | ||||
|  | ||||
| globalEvents.on('requestClearPageCache', () => { | ||||
| 	if (_DEV_) console.log('clear page cache requested'); | ||||
| 	if (!clearCacheRequested.value) { | ||||
| 		clearCacheRequested.value = true; | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| // #endregion | ||||
|  | ||||
| onBeforeUnmount(() => { | ||||
| 	router.removeListener('change', onChange); | ||||
| 	key.value = router.getCurrentFullPath(); | ||||
| }); | ||||
| </script> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 	:duration="200" | ||||
| 	tag="div" :class="$style.tabs" | ||||
| > | ||||
| 	<div v-for="(tab, i) in tabs" :key="tab.key" :class="$style.tab" :style="{ '--i': i - 1 }"> | ||||
| 	<div v-for="(tab, i) in tabs" :key="tab.fullPath" :class="$style.tab" :style="{ '--i': i - 1 }"> | ||||
| 		<div v-if="i > 0" :class="$style.tabBg" @click="back()"></div> | ||||
| 		<div :class="$style.tabFg" @click.stop="back()"> | ||||
| 			<div v-if="i > 0" :class="$style.tabMenu"> | ||||
| @@ -41,10 +41,9 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue'; | ||||
| import type { Router, Resolved, RouteDef } from '@/router.js'; | ||||
| import { inject, provide, shallowRef } from 'vue'; | ||||
| import type { Router } from '@/router.js'; | ||||
| import { prefer } from '@/preferences.js'; | ||||
| import { globalEvents } from '@/events.js'; | ||||
| import MkLoadingPage from '@/pages/_loading_.vue'; | ||||
| import { DI } from '@/di.js'; | ||||
| import { deepEqual } from '@/utility/deep-equal.js'; | ||||
| @@ -63,26 +62,36 @@ const currentDepth = inject(DI.routerCurrentDepth, 0); | ||||
| provide(DI.routerCurrentDepth, currentDepth + 1); | ||||
|  | ||||
| const tabs = shallowRef([{ | ||||
| 	key: router.getCurrentPath(), | ||||
| 	path: router.getCurrentPath(), | ||||
| 	route: router.current.route.path, | ||||
| 	fullPath: router.getCurrentFullPath(), | ||||
| 	routePath: router.current.route.path, | ||||
| 	component: 'component' in router.current.route ? router.current.route.component : MkLoadingPage, | ||||
| 	props: router.current.props, | ||||
| }]); | ||||
|  | ||||
| function onChange({ resolved }) { | ||||
| function mount() { | ||||
| 	const currentTab = tabs.value[tabs.value.length - 1]; | ||||
| 	const route = resolved.route.path; | ||||
| 	if (resolved == null || 'redirect' in resolved.route) return; | ||||
| 	if (resolved.route.path === currentTab.path && deepEqual(resolved.props, currentTab.props)) return; | ||||
| 	const fullPath = router.getCurrentPath(); | ||||
| 	tabs.value = [currentTab]; | ||||
| } | ||||
|  | ||||
| 	if (tabs.value.some(tab => tab.route === route && deepEqual(resolved.props, tab.props))) { | ||||
| function back() { | ||||
| 	const prev = tabs.value[tabs.value.length - 2]; | ||||
| 	tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)]; | ||||
| 	router.replace(prev.fullPath); | ||||
| } | ||||
|  | ||||
| router.useListener('change', ({ resolved }) => { | ||||
| 	const currentTab = tabs.value[tabs.value.length - 1]; | ||||
| 	const routePath = resolved.route.path; | ||||
| 	if (resolved == null || 'redirect' in resolved.route) return; | ||||
| 	if (resolved.route.path === currentTab.routePath && deepEqual(resolved.props, currentTab.props)) return; | ||||
| 	const fullPath = router.getCurrentFullPath(); | ||||
|  | ||||
| 	if (tabs.value.some(tab => tab.routePath === routePath && deepEqual(resolved.props, tab.props))) { | ||||
| 		const newTabs = []; | ||||
| 		for (const tab of tabs.value) { | ||||
| 			newTabs.push(tab); | ||||
|  | ||||
| 			if (tab.route === route && deepEqual(resolved.props, tab.props)) { | ||||
| 			if (tab.routePath === routePath && deepEqual(resolved.props, tab.props)) { | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| @@ -93,45 +102,23 @@ function onChange({ resolved }) { | ||||
| 	tabs.value = tabs.value.length >= prefer.s.numberOfPageCache ? [ | ||||
| 		...tabs.value.slice(1), | ||||
| 		{ | ||||
| 			key: fullPath, | ||||
| 			path: fullPath, | ||||
| 			route, | ||||
| 			fullPath: fullPath, | ||||
| 			routePath, | ||||
| 			component: resolved.route.component, | ||||
| 			props: resolved.props, | ||||
| 		}, | ||||
| 	] : [...tabs.value, { | ||||
| 		key: fullPath, | ||||
| 		path: fullPath, | ||||
| 		route, | ||||
| 		fullPath: fullPath, | ||||
| 		routePath, | ||||
| 		component: resolved.route.component, | ||||
| 		props: resolved.props, | ||||
| 	}]; | ||||
| } | ||||
| }); | ||||
|  | ||||
| function onReplace({ path }) { | ||||
| router.useListener('replace', ({ fullPath }) => { | ||||
| 	const currentTab = tabs.value[tabs.value.length - 1]; | ||||
| 	console.log('replace', currentTab.path, path); | ||||
| 	currentTab.path = path; | ||||
| 	currentTab.fullPath = fullPath; | ||||
| 	tabs.value = [...tabs.value.slice(0, tabs.value.length - 1), currentTab]; | ||||
| } | ||||
|  | ||||
| function mount() { | ||||
| 	const currentTab = tabs.value[tabs.value.length - 1]; | ||||
| 	tabs.value = [currentTab]; | ||||
| } | ||||
|  | ||||
| function back() { | ||||
| 	const prev = tabs.value[tabs.value.length - 2]; | ||||
| 	tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)]; | ||||
| 	router.replace(prev.path); | ||||
| } | ||||
|  | ||||
| router.addListener('replace', onReplace); | ||||
| router.addListener('change', onChange); | ||||
|  | ||||
| onBeforeUnmount(() => { | ||||
| 	router.removeListener('replace', onReplace); | ||||
| 	router.removeListener('change', onChange); | ||||
| }); | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -10,5 +10,4 @@ export const globalEvents = new EventEmitter<{ | ||||
| 	themeChanging: () => void; | ||||
| 	themeChanged: () => void; | ||||
| 	clientNotification: (notification: Misskey.entities.Notification) => void; | ||||
| 	requestClearPageCache: () => void; | ||||
| }>(); | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|  | ||||
| // NIRAX --- A lightweight router | ||||
|  | ||||
| import { onMounted, shallowRef } from 'vue'; | ||||
| import { onBeforeUnmount, onMounted, shallowRef } from 'vue'; | ||||
| import { EventEmitter } from 'eventemitter3'; | ||||
| import type { Component, ShallowRef } from 'vue'; | ||||
|  | ||||
| @@ -45,28 +45,28 @@ type ParsedPath = (string | { | ||||
| 	optional?: boolean; | ||||
| })[]; | ||||
|  | ||||
| export type RouterEvent = { | ||||
| export type RouterEvents = { | ||||
| 	change: (ctx: { | ||||
| 		beforePath: string; | ||||
| 		path: string; | ||||
| 		resolved: Resolved; | ||||
| 		beforeFullPath: string; | ||||
| 		fullPath: string; | ||||
| 		resolved: PathResolvedResult; | ||||
| 	}) => void; | ||||
| 	replace: (ctx: { | ||||
| 		path: string; | ||||
| 		fullPath: string; | ||||
| 	}) => void; | ||||
| 	push: (ctx: { | ||||
| 		beforePath: string; | ||||
| 		path: string; | ||||
| 		beforeFullPath: string; | ||||
| 		fullPath: string; | ||||
| 		route: RouteDef | null; | ||||
| 		props: Map<string, string> | null; | ||||
| 	}) => void; | ||||
| 	same: () => void; | ||||
| }; | ||||
|  | ||||
| export type Resolved = { | ||||
| export type PathResolvedResult = { | ||||
| 	route: RouteDef; | ||||
| 	props: Map<string, string | boolean>; | ||||
| 	child?: Resolved; | ||||
| 	child?: PathResolvedResult; | ||||
| 	redirected?: boolean; | ||||
|  | ||||
| 	/** @internal */ | ||||
| @@ -102,39 +102,39 @@ function parsePath(path: string): ParsedPath { | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvent> { | ||||
| export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> { | ||||
| 	private routes: DEF; | ||||
| 	public current: Resolved; | ||||
| 	public currentRef: ShallowRef<Resolved>; | ||||
| 	public current: PathResolvedResult; | ||||
| 	public currentRef: ShallowRef<PathResolvedResult>; | ||||
| 	public currentRoute: ShallowRef<RouteDef>; | ||||
| 	private currentPath: string; | ||||
| 	private currentFullPath: string; // /foo/bar?baz=qux#hash | ||||
| 	private isLoggedIn: boolean; | ||||
| 	private notFoundPageComponent: Component; | ||||
| 	private redirectCount = 0; | ||||
|  | ||||
| 	public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null; | ||||
| 	public navHook: ((fullPath: string, flag?: RouterFlag) => boolean) | null = null; | ||||
|  | ||||
| 	constructor(routes: DEF, currentPath: Nirax<DEF>['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) { | ||||
| 	constructor(routes: DEF, currentFullPath: Nirax<DEF>['currentFullPath'], isLoggedIn: boolean, notFoundPageComponent: Component) { | ||||
| 		super(); | ||||
|  | ||||
| 		this.routes = routes; | ||||
| 		this.current = this.resolve(currentPath)!; | ||||
| 		this.current = this.resolve(currentFullPath)!; | ||||
| 		this.currentRef = shallowRef(this.current); | ||||
| 		this.currentRoute = shallowRef(this.current.route); | ||||
| 		this.currentPath = currentPath; | ||||
| 		this.currentFullPath = currentFullPath; | ||||
| 		this.isLoggedIn = isLoggedIn; | ||||
| 		this.notFoundPageComponent = notFoundPageComponent; | ||||
| 	} | ||||
|  | ||||
| 	public init() { | ||||
| 		const res = this.navigate(this.currentPath, false); | ||||
| 		const res = this.navigate(this.currentFullPath, false); | ||||
| 		this.emit('replace', { | ||||
| 			path: res._parsedRoute.fullPath, | ||||
| 			fullPath: res._parsedRoute.fullPath, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	public resolve(path: string): Resolved | null { | ||||
| 		const fullPath = path; | ||||
| 	public resolve(fullPath: string): PathResolvedResult | null { | ||||
| 		let path = fullPath; | ||||
| 		let queryString: string | null = null; | ||||
| 		let hash: string | null = null; | ||||
| 		if (path[0] === '/') path = path.substring(1); | ||||
| @@ -153,7 +153,7 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvent> { | ||||
| 			hash, | ||||
| 		}; | ||||
|  | ||||
| 		function check(routes: RouteDef[], _parts: string[]): Resolved | null { | ||||
| 		function check(routes: RouteDef[], _parts: string[]): PathResolvedResult | null { | ||||
| 			forEachRouteLoop: | ||||
| 			for (const route of routes) { | ||||
| 				let parts = [..._parts]; | ||||
| @@ -256,14 +256,14 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvent> { | ||||
| 		return check(this.routes, _parts); | ||||
| 	} | ||||
|  | ||||
| 	private navigate(path: string, emitChange = true, _redirected = false): Resolved { | ||||
| 		const beforePath = this.currentPath; | ||||
| 		this.currentPath = path; | ||||
| 	private navigate(fullPath: string, emitChange = true, _redirected = false): PathResolvedResult { | ||||
| 		const beforeFullPath = this.currentFullPath; | ||||
| 		this.currentFullPath = fullPath; | ||||
|  | ||||
| 		const res = this.resolve(this.currentPath); | ||||
| 		const res = this.resolve(this.currentFullPath); | ||||
|  | ||||
| 		if (res == null) { | ||||
| 			throw new Error('no route found for: ' + path); | ||||
| 			throw new Error('no route found for: ' + fullPath); | ||||
| 		} | ||||
|  | ||||
| 		if ('redirect' in res.route) { | ||||
| @@ -291,8 +291,8 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvent> { | ||||
|  | ||||
| 		if (emitChange && res.route.path !== '/:(*)') { | ||||
| 			this.emit('change', { | ||||
| 				beforePath, | ||||
| 				path, | ||||
| 				beforeFullPath, | ||||
| 				fullPath, | ||||
| 				resolved: res, | ||||
| 			}); | ||||
| 		} | ||||
| @@ -304,37 +304,45 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvent> { | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	public getCurrentPath() { | ||||
| 		return this.currentPath; | ||||
| 	public getCurrentFullPath() { | ||||
| 		return this.currentFullPath; | ||||
| 	} | ||||
|  | ||||
| 	public push(path: string, flag?: RouterFlag) { | ||||
| 		const beforePath = this.currentPath; | ||||
| 		if (path === beforePath) { | ||||
| 	public push(fullPath: string, flag?: RouterFlag) { | ||||
| 		const beforeFullPath = this.currentFullPath; | ||||
| 		if (fullPath === beforeFullPath) { | ||||
| 			this.emit('same'); | ||||
| 			return; | ||||
| 		} | ||||
| 		if (this.navHook) { | ||||
| 			const cancel = this.navHook(path, flag); | ||||
| 			const cancel = this.navHook(fullPath, flag); | ||||
| 			if (cancel) return; | ||||
| 		} | ||||
| 		const res = this.navigate(path); | ||||
| 		const res = this.navigate(fullPath); | ||||
| 		if (res.route.path === '/:(*)') { | ||||
| 			location.href = path; | ||||
| 			location.href = fullPath; | ||||
| 		} else { | ||||
| 			this.emit('push', { | ||||
| 				beforePath, | ||||
| 				path: res._parsedRoute.fullPath, | ||||
| 				beforeFullPath, | ||||
| 				fullPath: res._parsedRoute.fullPath, | ||||
| 				route: res.route, | ||||
| 				props: res.props, | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public replace(path: string) { | ||||
| 		const res = this.navigate(path); | ||||
| 	public replace(fullPath: string) { | ||||
| 		const res = this.navigate(fullPath); | ||||
| 		this.emit('replace', { | ||||
| 			path: res._parsedRoute.fullPath, | ||||
| 			fullPath: res._parsedRoute.fullPath, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	public useListener<E extends keyof RouterEvents, L = RouterEvents[E]>(event: E, listener: L) { | ||||
| 		this.addListener(event, listener); | ||||
|  | ||||
| 		onBeforeUnmount(() => { | ||||
| 			this.removeListener(event, listener); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -169,7 +169,6 @@ import { langmap } from '@/utility/langmap.js'; | ||||
| import { definePage } from '@/page.js'; | ||||
| import { claimAchievement } from '@/utility/achievements.js'; | ||||
| import { store } from '@/store.js'; | ||||
| import { globalEvents } from '@/events.js'; | ||||
| import MkInfo from '@/components/MkInfo.vue'; | ||||
| import MkTextarea from '@/components/MkTextarea.vue'; | ||||
|  | ||||
| @@ -223,7 +222,6 @@ function saveFields() { | ||||
| 	os.apiWithDialog('i/update', { | ||||
| 		fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })), | ||||
| 	}); | ||||
| 	globalEvents.emit('requestClearPageCache'); | ||||
| } | ||||
|  | ||||
| function save() { | ||||
| @@ -249,7 +247,6 @@ function save() { | ||||
| 			text: i18n.ts.yourNameContainsProhibitedWordsDescription, | ||||
| 		}, | ||||
| 	}); | ||||
| 	globalEvents.emit('requestClearPageCache'); | ||||
| 	claimAchievement('profileFilled'); | ||||
| 	if (profile.name === 'syuilo' || profile.name === 'しゅいろ') { | ||||
| 		claimAchievement('setNameToSyuilo'); | ||||
| @@ -281,7 +278,6 @@ function changeAvatar(ev) { | ||||
| 		}); | ||||
| 		$i.avatarId = i.avatarId; | ||||
| 		$i.avatarUrl = i.avatarUrl; | ||||
| 		globalEvents.emit('requestClearPageCache'); | ||||
| 		claimAchievement('profileFilled'); | ||||
| 	}); | ||||
| } | ||||
| @@ -308,7 +304,6 @@ function changeBanner(ev) { | ||||
| 		}); | ||||
| 		$i.bannerId = i.bannerId; | ||||
| 		$i.bannerUrl = i.bannerUrl; | ||||
| 		globalEvents.emit('requestClearPageCache'); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,8 +13,8 @@ import { DI } from '@/di.js'; | ||||
|  | ||||
| export type Router = Nirax<typeof ROUTE_DEF>; | ||||
|  | ||||
| export function createRouter(path: string): Router { | ||||
| 	return new Nirax(ROUTE_DEF, path, !!$i, page(() => import('@/pages/not-found.vue'))); | ||||
| export function createRouter(fullPath: string): Router { | ||||
| 	return new Nirax(ROUTE_DEF, fullPath, !!$i, page(() => import('@/pages/not-found.vue'))); | ||||
| } | ||||
|  | ||||
| export const mainRouter = createRouter(location.pathname + location.search + location.hash); | ||||
| @@ -24,23 +24,23 @@ window.addEventListener('popstate', (event) => { | ||||
| }); | ||||
|  | ||||
| mainRouter.addListener('push', ctx => { | ||||
| 	window.history.pushState({ }, '', ctx.path); | ||||
| 	window.history.pushState({ }, '', ctx.fullPath); | ||||
| }); | ||||
|  | ||||
| mainRouter.addListener('replace', ctx => { | ||||
| 	window.history.replaceState({ }, '', ctx.path); | ||||
| 	window.history.replaceState({ }, '', ctx.fullPath); | ||||
| }); | ||||
|  | ||||
| mainRouter.addListener('change', ctx => { | ||||
| 	console.log('mainRouter: change', ctx.path); | ||||
| 	console.log('mainRouter: change', ctx.fullPath); | ||||
| 	analytics.page({ | ||||
| 		path: ctx.path, | ||||
| 		title: ctx.path, | ||||
| 		path: ctx.fullPath, | ||||
| 		title: ctx.fullPath, | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| mainRouter.init(); | ||||
|  | ||||
| export function useRouter(): Router { | ||||
| 	return inject(DI.router, null) ?? mainRouter; | ||||
| 	return inject(DI.router) ?? mainRouter; | ||||
| } | ||||
|   | ||||
| @@ -112,7 +112,7 @@ function onContextmenu(ev: MouseEvent) { | ||||
| 	if (isLink(ev.target)) return; | ||||
| 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; | ||||
| 	if (window.getSelection().toString() !== '') return; | ||||
| 	const path = mainRouter.getCurrentPath(); | ||||
| 	const path = mainRouter.getCurrentFullPath(); | ||||
| 	os.contextMenu([{ | ||||
| 		type: 'label', | ||||
| 		text: path, | ||||
|   | ||||
| @@ -186,7 +186,7 @@ const onContextmenu = (ev) => { | ||||
| 	if (isLink(ev.target)) return; | ||||
| 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; | ||||
| 	if (window.getSelection()?.toString() !== '') return; | ||||
| 	const path = mainRouter.getCurrentPath(); | ||||
| 	const path = mainRouter.getCurrentFullPath(); | ||||
| 	os.contextMenu([{ | ||||
| 		type: 'label', | ||||
| 		text: path, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo