1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-20 23:25:28 +02:00

fix(frontend): routerがmatchAllに入った際に一度 location.href による遷移を試みる挙動に関する修正 (#17281)

* fix(frontend): follow-up of #13509

* fix: fix use of inappropriate method

* Update CHANGELOG.md [ci skip]
This commit is contained in:
かっこかり
2026-04-07 20:35:06 +09:00
committed by GitHub
parent b9923d0a23
commit d4a5048aae
6 changed files with 99 additions and 28 deletions

View File

@@ -5,6 +5,7 @@
### Client ### Client
- Enhance: チャンネル指定リノートでリノート先のチャンネルに移動できるように - Enhance: チャンネル指定リノートでリノート先のチャンネルに移動できるように
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
### Server ### Server
- Fix: `/api-doc` にアクセスできない問題を修正 - Fix: `/api-doc` にアクセスできない問題を修正

View File

@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, onUnmounted, provide, ref, useTemplateRef } from 'vue'; import { computed, onMounted, onUnmounted, provide, ref, useTemplateRef, nextTick } from 'vue';
import { url } from '@@/js/config.js'; import { url } from '@@/js/config.js';
import type { PageMetadata } from '@/page.js'; import type { PageMetadata } from '@/page.js';
import RouterView from '@/components/global/RouterView.vue'; import RouterView from '@/components/global/RouterView.vue';
@@ -98,6 +98,24 @@ windowRouter.addListener('replace', ctx => {
_history_.value.push({ path: ctx.fullPath }); _history_.value.push({ path: ctx.fullPath });
}); });
windowRouter.addListener('forcePush', ctx => {
window.open(url + ctx.fullPath, '_blank', 'noopener');
if (ctx.onInit) {
nextTick(() => {
windowEl.value?.close();
});
}
});
windowRouter.addListener('forceReplace', ctx => {
window.open(url + ctx.fullPath, '_blank', 'noopener');
if (ctx.onInit) {
nextTick(() => {
windowEl.value?.close();
});
}
});
windowRouter.addListener('change', ctx => { windowRouter.addListener('change', ctx => {
if (_DEV_) console.log('windowRouter: change', ctx.fullPath); if (_DEV_) console.log('windowRouter: change', ctx.fullPath);
searchMarkerId.value = getSearchMarker(ctx.fullPath); searchMarkerId.value = getSearchMarker(ctx.fullPath);
@@ -107,7 +125,7 @@ windowRouter.addListener('change', ctx => {
}); });
}); });
windowRouter.init(); windowRouter.init(true);
provide(DI.router, windowRouter); provide(DI.router, windowRouter);
provide(DI.inAppSearchMarkerId, searchMarkerId); provide(DI.inAppSearchMarkerId, searchMarkerId);

View File

@@ -31,6 +31,7 @@ const props = withDefaults(defineProps<{
}); });
const behavior = props.behavior ?? inject<MkABehavior>('linkNavigationBehavior', null); const behavior = props.behavior ?? inject<MkABehavior>('linkNavigationBehavior', null);
const isWindow = inject<boolean>('inWindow', false);
const el = useTemplateRef('el'); const el = useTemplateRef('el');
@@ -92,7 +93,11 @@ function nav(ev: PointerEvent) {
ev.preventDefault(); ev.preventDefault();
if (behavior === 'browser') { if (behavior === 'browser') {
window.location.href = props.to; if (isWindow) {
window.open(props.to, '_blank', 'noopener');
} else {
window.location.href = props.to;
}
return; return;
} }

View File

@@ -46,20 +46,34 @@ type ParsedPath = (string | {
})[]; })[];
export type RouterEvents = { export type RouterEvents = {
/** ページ内遷移を検知した場合analytics用 */
change: (ctx: { change: (ctx: {
beforeFullPath: string; beforeFullPath: string;
fullPath: string; fullPath: string;
resolved: PathResolvedResult; resolved: PathResolvedResult;
}) => void; }) => void;
/** history stateのreplaceを行う場合 */
replace: (ctx: { replace: (ctx: {
fullPath: string; fullPath: string;
}) => void; }) => void;
/** location.replace相当の処理が必要な場合 */
forceReplace: (ctx: {
onInit: boolean;
fullPath: string;
}) => void;
/** history stateのpushを行う場合 */
push: (ctx: { push: (ctx: {
beforeFullPath: string; beforeFullPath: string;
fullPath: string; fullPath: string;
route: RouteDef | null; route: RouteDef | null;
props: Map<string, string | boolean> | null; props: Map<string, string | boolean> | null;
}) => void; }) => void;
/** location.hrefへの代入相当の処理が必要な場合 */
forcePush: (ctx: {
onInit: boolean;
fullPath: string;
}) => void;
/** 遷移先が現在のページと同じだった場合 */
same: () => void; same: () => void;
}; };
@@ -216,7 +230,6 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
private currentFullPath: string; // /foo/bar?baz=qux#hash private currentFullPath: string; // /foo/bar?baz=qux#hash
private isLoggedIn: boolean; private isLoggedIn: boolean;
private notFoundPageComponent: Component; private notFoundPageComponent: Component;
private redirectCount = 0;
public navHook: ((fullPath: string, flag?: RouterFlag) => boolean) | null = null; public navHook: ((fullPath: string, flag?: RouterFlag) => boolean) | null = null;
@@ -232,8 +245,17 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
this.notFoundPageComponent = notFoundPageComponent; this.notFoundPageComponent = notFoundPageComponent;
} }
public init() { public init(triggerForceReplace = false) {
const res = this.navigate(this.currentFullPath, false); const res = this.resolveForNavigation(this.currentFullPath);
if (triggerForceReplace && res.route.path === '/:(*)') {
this.emit('forceReplace', {
onInit: true,
fullPath: res._parsedRoute.fullPath,
});
}
this.navigate(res, false);
this.emit('replace', { this.emit('replace', {
fullPath: res._parsedRoute.fullPath, fullPath: res._parsedRoute.fullPath,
}); });
@@ -362,17 +384,15 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
return check(this.routes, _parts); return check(this.routes, _parts);
} }
private navigate(fullPath: string, emitChange = true, _redirected = false): PathResolvedResult { /** 通常のresolve + リダイレクト解決 */
const beforeFullPath = this.currentFullPath; private resolveForNavigation(fullPath: string, _redirectCount = 0): PathResolvedResult {
this.currentFullPath = fullPath; const res = this.resolve(fullPath);
const res = this.resolve(this.currentFullPath);
if (res == null) { if (res == null) {
throw new Error('no route found for: ' + fullPath); throw new Error('no route found for: ' + fullPath);
} }
for (let current: PathResolvedResult | undefined = res; current; current = current.child) { for (let current: PathResolvedResult | undefined = res; current != null; current = current.child) {
if ('redirect' in current.route) { if ('redirect' in current.route) {
let redirectPath: string; let redirectPath: string;
if (typeof current.route.redirect === 'function') { if (typeof current.route.redirect === 'function') {
@@ -380,14 +400,25 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
} else { } else {
redirectPath = current.route.redirect + (current._parsedRoute.queryString ? '?' + current._parsedRoute.queryString : '') + (current._parsedRoute.hash ? '#' + current._parsedRoute.hash : ''); redirectPath = current.route.redirect + (current._parsedRoute.queryString ? '?' + current._parsedRoute.queryString : '') + (current._parsedRoute.hash ? '#' + current._parsedRoute.hash : '');
} }
if (_DEV_) console.log('Redirecting to: ', redirectPath); if (_DEV_) console.log('Redirecting from', current._parsedRoute.fullPath, 'to', redirectPath);
if (_redirected && this.redirectCount++ > 10) { if (_redirectCount > 10) {
throw new Error('redirect loop detected'); throw new Error('redirect loop detected');
} }
return this.navigate(redirectPath, emitChange, true); return this.resolveForNavigation(redirectPath, _redirectCount + 1);
} }
} }
return {
...res,
redirected: _redirectCount > 0,
};
}
/** 解決された`res`に応じてrouterの状態を更新する。 */
private navigate(res: PathResolvedResult, emitChange = true) {
const beforeFullPath = this.currentFullPath;
this.currentFullPath = res._parsedRoute.fullPath;
if (res.route.loginRequired && !this.isLoggedIn && 'component' in res.route) { if (res.route.loginRequired && !this.isLoggedIn && 'component' in res.route) {
res.route.component = this.notFoundPageComponent; res.route.component = this.notFoundPageComponent;
res.props.set('showLoginPopup', true); res.props.set('showLoginPopup', true);
@@ -400,16 +431,12 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
if (emitChange && res.route.path !== '/:(*)') { if (emitChange && res.route.path !== '/:(*)') {
this.emit('change', { this.emit('change', {
beforeFullPath, beforeFullPath,
fullPath, fullPath: res._parsedRoute.fullPath,
resolved: res, resolved: res,
}); });
} }
this.redirectCount = 0; return res;
return {
...res,
redirected: _redirected,
};
} }
public getCurrentFullPath() { public getCurrentFullPath() {
@@ -447,10 +474,14 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
const cancel = this.navHook(fullPath, flag ?? undefined); const cancel = this.navHook(fullPath, flag ?? undefined);
if (cancel) return; if (cancel) return;
} }
const res = this.navigate(fullPath); const res = this.resolveForNavigation(fullPath);
if (res.route.path === '/:(*)') { if (res.route.path === '/:(*)') {
window.location.href = fullPath; this.emit('forcePush', {
fullPath: res._parsedRoute.fullPath,
onInit: false,
});
} else { } else {
this.navigate(res);
this.emit('push', { this.emit('push', {
beforeFullPath, beforeFullPath,
fullPath: res._parsedRoute.fullPath, fullPath: res._parsedRoute.fullPath,
@@ -462,10 +493,18 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
/** どうしても必要な場合に使用(パスが確定している場合は `Nirax.replace` を使用すること) */ /** どうしても必要な場合に使用(パスが確定している場合は `Nirax.replace` を使用すること) */
public replaceByPath(fullPath: string) { public replaceByPath(fullPath: string) {
const res = this.navigate(fullPath); const res = this.resolveForNavigation(fullPath);
this.emit('replace', { if (res.route.path === '/:(*)') {
fullPath: res._parsedRoute.fullPath, this.emit('forceReplace', {
}); fullPath: res._parsedRoute.fullPath,
onInit: false,
});
} else {
this.navigate(res);
this.emit('replace', {
fullPath: res._parsedRoute.fullPath,
});
}
} }
public useListener<E extends keyof RouterEvents>(event: E, listener: EventEmitter.EventListener<RouterEvents, E>) { public useListener<E extends keyof RouterEvents>(event: E, listener: EventEmitter.EventListener<RouterEvents, E>) {

View File

@@ -215,7 +215,7 @@ async function deleteFile() {
globalEvents.emit('driveFilesDeleted', [file.value]); globalEvents.emit('driveFilesDeleted', [file.value]);
router.push('/my/drive'); router.replace('/my/drive');
} }
onMounted(async () => { onMounted(async () => {

View File

@@ -31,6 +31,14 @@ mainRouter.addListener('replace', ctx => {
window.history.replaceState({ }, '', ctx.fullPath); window.history.replaceState({ }, '', ctx.fullPath);
}); });
mainRouter.addListener('forceReplace', ctx => {
window.location.replace(ctx.fullPath);
});
mainRouter.addListener('forcePush', ctx => {
window.location.href = ctx.fullPath;
});
mainRouter.addListener('change', ctx => { mainRouter.addListener('change', ctx => {
if (_DEV_) console.log('mainRouter: change', ctx.fullPath); if (_DEV_) console.log('mainRouter: change', ctx.fullPath);
analytics.page({ analytics.page({