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:
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: チャンネル指定リノートでリノート先のチャンネルに移動できるように
|
- Enhance: チャンネル指定リノートでリノート先のチャンネルに移動できるように
|
||||||
|
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Fix: `/api-doc` にアクセスできない問題を修正
|
- Fix: `/api-doc` にアクセスできない問題を修正
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>) {
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user