diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index 9547423227..be067f81a4 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -98,6 +98,15 @@ windowRouter.addListener('replace', ctx => { history.value.push({ path: ctx.path, key: ctx.key }); }); +windowRouter.navHook = (path) => { + const res = windowRouter.resolve(path); + if (res?.route.path === '/:(*)') { + window.open(path, '_blank', 'noopener'); + return true; + } + return res ?? false; +}; + windowRouter.init(); provide('router', windowRouter); diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts index 965bd6f0bc..367bf5ca66 100644 --- a/packages/frontend/src/nirax.ts +++ b/packages/frontend/src/nirax.ts @@ -109,7 +109,13 @@ export interface IRouter extends EventEmitter { current: Resolved; currentRef: ShallowRef; currentRoute: ShallowRef; - navHook: ((path: string, flag?: RouterFlag) => boolean) | null; + + /** + * ナビゲーションフック + * + * `true`でナビゲーションをキャンセル、`false`またはResolvedオブジェクトでナビゲーションを続行 + */ + navHook: ((path: string, flag?: RouterFlag) => boolean | Resolved) | null; /** * ルートの初期化(eventListenerの定義後に必ず呼び出すこと) @@ -199,7 +205,7 @@ export class Router extends EventEmitter implements IRouter { private currentKey = Date.now().toString(); private redirectCount = 0; - public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null; + public navHook: ((path: string, flag?: RouterFlag) => boolean | Resolved) | null = null; constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) { super(); @@ -412,11 +418,20 @@ export class Router extends EventEmitter implements IRouter { this.emit('same'); return; } + + let res: Resolved | null = null; if (this.navHook) { - const cancel = this.navHook(path, flag); - if (cancel) return; + const hookRes = this.navHook(path, flag); + if (hookRes === true) return; + if (hookRes !== false) { + res = hookRes; + } } - const res = this.navigate(path, null); + + if (res == null) { + res = this.navigate(path, null); + } + if (res.route.path === '/:(*)') { location.href = path; } else { diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index a1a76a7e7d..a03c9e6e19 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -135,11 +135,16 @@ const columnComponents = { roleTimeline: XRoleTimelineColumn, }; -mainRouter.navHook = (path, flag): boolean => { +mainRouter.navHook = (path, flag) => { if (flag === 'forcePage') return false; const noMainColumn = !deckStore.state.columns.some(x => x.type === 'main'); if (deckStore.state.navWindow || noMainColumn) { - os.pageWindow(path); + const res = mainRouter.resolve(path); + if (res?.route.path === '/:(*)') { + window.open(path, '_blank', 'noopener'); + } else { + os.pageWindow(path); + } return true; } return false;