summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-07-30 12:30:35 +0900
committerGitHub <noreply@github.com>2025-07-30 12:30:35 +0900
commit4f653f2fbc9f48f2d3069dd587907ebee667386c (patch)
tree7a01cec63c94f56c5da8af3da9356ce74c265def /packages/frontend/src
parentperf(frontend): draw-blurhash workerの結果をpostMessageする際にImageB... (diff)
downloadmisskey-4f653f2fbc9f48f2d3069dd587907ebee667386c.tar.gz
misskey-4f653f2fbc9f48f2d3069dd587907ebee667386c.tar.bz2
misskey-4f653f2fbc9f48f2d3069dd587907ebee667386c.zip
enhance(frontend): typed nirax (#16309)
* enhance(frontend): typed nirax * migrate router.replace * fix
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.vue2
-rw-r--r--packages/frontend/src/components/MkPageWindow.vue4
-rw-r--r--packages/frontend/src/components/MkSuperMenu.vue2
-rw-r--r--packages/frontend/src/components/global/MkA.vue4
-rw-r--r--packages/frontend/src/components/global/StackingRouterView.vue2
-rw-r--r--packages/frontend/src/lib/nirax.ts138
-rw-r--r--packages/frontend/src/pages/admin/roles.edit.vue12
-rw-r--r--packages/frontend/src/pages/admin/roles.role.vue6
-rw-r--r--packages/frontend/src/pages/antenna-timeline.vue6
-rw-r--r--packages/frontend/src/pages/channel-editor.vue6
-rw-r--r--packages/frontend/src/pages/channel.vue6
-rw-r--r--packages/frontend/src/pages/chat/home.home.vue12
-rw-r--r--packages/frontend/src/pages/chat/home.invitations.vue6
-rw-r--r--packages/frontend/src/pages/flash/flash-edit.vue6
-rw-r--r--packages/frontend/src/pages/gallery/edit.vue12
-rw-r--r--packages/frontend/src/pages/gallery/post.vue6
-rw-r--r--packages/frontend/src/pages/lookup.vue19
-rw-r--r--packages/frontend/src/pages/page-editor/page-editor.vue12
-rw-r--r--packages/frontend/src/pages/page.vue6
-rw-r--r--packages/frontend/src/pages/reversi/index.vue6
-rw-r--r--packages/frontend/src/pages/search.note.vue20
-rw-r--r--packages/frontend/src/pages/search.user.vue20
-rw-r--r--packages/frontend/src/pages/settings/webhook.edit.vue2
-rw-r--r--packages/frontend/src/pages/user-list-timeline.vue6
-rw-r--r--packages/frontend/src/router.definition.ts2
-rw-r--r--packages/frontend/src/router.ts2
-rw-r--r--packages/frontend/src/ui/_common_/sw-inject.ts2
-rw-r--r--packages/frontend/src/utility/get-user-menu.ts13
-rw-r--r--packages/frontend/src/utility/lookup.ts20
29 files changed, 308 insertions, 52 deletions
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 68da098439..654aceb8f5 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -495,7 +495,7 @@ function done(query?: string): boolean | void {
function settings() {
emit('esc');
- router.push('settings/emoji-palette');
+ router.push('/settings/emoji-palette');
}
onMounted(() => {
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 1310ea6a77..cf60c1ca3e 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -151,7 +151,7 @@ const contextmenu = computed(() => ([{
function back() {
history.value.pop();
- windowRouter.replace(history.value.at(-1)!.path);
+ windowRouter.replaceByPath(history.value.at(-1)!.path);
}
function reload() {
@@ -163,7 +163,7 @@ function close() {
}
function expand() {
- mainRouter.push(windowRouter.getCurrentFullPath(), 'forcePage');
+ mainRouter.pushByPath(windowRouter.getCurrentFullPath(), 'forcePage');
windowEl.value?.close();
}
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index 3f8d92a61d..5c89a6530d 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -186,7 +186,7 @@ function searchOnKeyDown(ev: KeyboardEvent) {
if (ev.key === 'Enter' && searchSelectedIndex.value != null) {
ev.preventDefault();
- router.push(searchResult.value[searchSelectedIndex.value].path + '#' + searchResult.value[searchSelectedIndex.value].id);
+ router.pushByPath(searchResult.value[searchSelectedIndex.value].path + '#' + searchResult.value[searchSelectedIndex.value].id);
} else if (ev.key === 'ArrowDown') {
ev.preventDefault();
const current = searchSelectedIndex.value ?? -1;
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 4004db5b12..ae1b4549ec 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -64,7 +64,7 @@ function onContextmenu(ev) {
icon: 'ti ti-player-eject',
text: i18n.ts.showInPage,
action: () => {
- router.push(props.to, 'forcePage');
+ router.pushByPath(props.to, 'forcePage');
},
}, { type: 'divider' }, {
icon: 'ti ti-external-link',
@@ -99,6 +99,6 @@ function nav(ev: MouseEvent) {
return openWindow();
}
- router.push(props.to, ev.ctrlKey ? 'forcePage' : null);
+ router.pushByPath(props.to, ev.ctrlKey ? 'forcePage' : null);
}
</script>
diff --git a/packages/frontend/src/components/global/StackingRouterView.vue b/packages/frontend/src/components/global/StackingRouterView.vue
index c95c74aef3..9e47517244 100644
--- a/packages/frontend/src/components/global/StackingRouterView.vue
+++ b/packages/frontend/src/components/global/StackingRouterView.vue
@@ -76,7 +76,7 @@ function mount() {
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?.replaceByPath(prev.fullPath);
}
router.useListener('change', ({ resolved }) => {
diff --git a/packages/frontend/src/lib/nirax.ts b/packages/frontend/src/lib/nirax.ts
index a166df9eb0..70db47e24e 100644
--- a/packages/frontend/src/lib/nirax.ts
+++ b/packages/frontend/src/lib/nirax.ts
@@ -58,7 +58,7 @@ export type RouterEvents = {
beforeFullPath: string;
fullPath: string;
route: RouteDef | null;
- props: Map<string, string> | null;
+ props: Map<string, string | boolean> | null;
}) => void;
same: () => void;
};
@@ -77,6 +77,110 @@ export type PathResolvedResult = {
};
};
+//#region Path Types
+type Prettify<T> = {
+ [K in keyof T]: T[K]
+} & {};
+
+type RemoveNever<T> = {
+ [K in keyof T as T[K] extends never ? never : K]: T[K];
+} & {};
+
+type IsPathParameter<Part extends string> = Part extends `${string}:${infer Parameter}` ? Parameter : never;
+
+type GetPathParamKeys<Path extends string> =
+ Path extends `${infer A}/${infer B}`
+ ? IsPathParameter<A> | GetPathParamKeys<B>
+ : IsPathParameter<Path>;
+
+type GetPathParams<Path extends string> = Prettify<{
+ [Param in GetPathParamKeys<Path> as Param extends `${string}?` ? never : Param]: string;
+} & {
+ [Param in GetPathParamKeys<Path> as Param extends `${infer OptionalParam}?` ? OptionalParam : never]?: string;
+}>;
+
+type UnwrapReadOnly<T> = T extends ReadonlyArray<infer U>
+ ? U
+ : T extends Readonly<infer U>
+ ? U
+ : T;
+
+type GetPaths<Def extends RouteDef> = Def extends { path: infer Path }
+ ? Path extends string
+ ? Def extends { children: infer Children }
+ ? Children extends RouteDef[]
+ ? Path | `${Path}${FlattenAllPaths<Children>}`
+ : Path
+ : Path
+ : never
+ : never;
+
+type FlattenAllPaths<Defs extends RouteDef[]> = GetPaths<Defs[number]>;
+
+type GetSinglePathQuery<Def extends RouteDef, Path extends FlattenAllPaths<RouteDef[]>> = RemoveNever<
+ Def extends { path: infer BasePath, children: infer Children }
+ ? BasePath extends string
+ ? Path extends `${BasePath}${infer ChildPath}`
+ ? Children extends RouteDef[]
+ ? ChildPath extends FlattenAllPaths<Children>
+ ? GetPathQuery<Children, ChildPath>
+ : Record<string, never>
+ : never
+ : never
+ : never
+ : Def['path'] extends Path
+ ? Def extends { query: infer Query }
+ ? Query extends Record<string, string>
+ ? UnwrapReadOnly<{ [Key in keyof Query]?: string; }>
+ : Record<string, never>
+ : Record<string, never>
+ : Record<string, never>
+ >;
+
+type GetPathQuery<Defs extends RouteDef[], Path extends FlattenAllPaths<Defs>> = GetSinglePathQuery<Defs[number], Path>;
+
+type RequiredIfNotEmpty<K extends string, T extends Record<string, unknown>> = T extends Record<string, never>
+ ? { [Key in K]?: T }
+ : { [Key in K]: T };
+
+type NotRequiredIfEmpty<T extends Record<string, unknown>> = T extends Record<string, never> ? T | undefined : T;
+
+type GetRouterOperationProps<Defs extends RouteDef[], Path extends FlattenAllPaths<Defs>> = NotRequiredIfEmpty<RequiredIfNotEmpty<'params', GetPathParams<Path>> & {
+ query?: GetPathQuery<Defs, Path>;
+ hash?: string;
+}>;
+//#endregion
+
+function buildFullPath(args: {
+ path: string;
+ params?: Record<string, string>;
+ query?: Record<string, string>;
+ hash?: string;
+}) {
+ let fullPath = args.path;
+
+ if (args.params) {
+ for (const key in args.params) {
+ const value = args.params[key];
+ const replaceRegex = new RegExp(`:${key}(\\?)?`, 'g');
+ fullPath = fullPath.replace(replaceRegex, value ? encodeURIComponent(value) : '');
+ }
+ }
+
+ if (args.query) {
+ const queryString = new URLSearchParams(args.query).toString();
+ if (queryString) {
+ fullPath += '?' + queryString;
+ }
+ }
+
+ if (args.hash) {
+ fullPath += '#' + encodeURIComponent(args.hash);
+ }
+
+ return fullPath;
+}
+
function parsePath(path: string): ParsedPath {
const res = [] as ParsedPath;
@@ -282,7 +386,7 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
}
}
- if (res.route.loginRequired && !this.isLoggedIn) {
+ if (res.route.loginRequired && !this.isLoggedIn && 'component' in res.route) {
res.route.component = this.notFoundPageComponent;
res.props.set('showLoginPopup', true);
}
@@ -310,14 +414,35 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
return this.currentFullPath;
}
- public push(fullPath: string, flag?: RouterFlag) {
+ public push<P extends FlattenAllPaths<DEF>>(path: P, props?: GetRouterOperationProps<DEF, P>, flag?: RouterFlag | null) {
+ const fullPath = buildFullPath({
+ path,
+ params: props?.params,
+ query: props?.query,
+ hash: props?.hash,
+ });
+ this.pushByPath(fullPath, flag);
+ }
+
+ public replace<P extends FlattenAllPaths<DEF>>(path: P, props?: GetRouterOperationProps<DEF, P>) {
+ const fullPath = buildFullPath({
+ path,
+ params: props?.params,
+ query: props?.query,
+ hash: props?.hash,
+ });
+ this.replaceByPath(fullPath);
+ }
+
+ /** どうしても必要な場合に使用(パスが確定している場合は `Nirax.push` を使用すること) */
+ public pushByPath(fullPath: string, flag?: RouterFlag | null) {
const beforeFullPath = this.currentFullPath;
if (fullPath === beforeFullPath) {
this.emit('same');
return;
}
if (this.navHook) {
- const cancel = this.navHook(fullPath, flag);
+ const cancel = this.navHook(fullPath, flag ?? undefined);
if (cancel) return;
}
const res = this.navigate(fullPath);
@@ -333,14 +458,15 @@ export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
}
}
- public replace(fullPath: string) {
+ /** どうしても必要な場合に使用(パスが確定している場合は `Nirax.replace` を使用すること) */
+ public replaceByPath(fullPath: string) {
const res = this.navigate(fullPath);
this.emit('replace', {
fullPath: res._parsedRoute.fullPath,
});
}
- public useListener<E extends keyof RouterEvents, L = RouterEvents[E]>(event: E, listener: L) {
+ public useListener<E extends keyof RouterEvents>(event: E, listener: EventEmitter.EventListener<RouterEvents, E>) {
this.addListener(event, listener);
onBeforeUnmount(() => {
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index 1a903eedb9..b24b640527 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -72,12 +72,20 @@ async function save() {
roleId: role.value.id,
...data.value,
});
- router.push('/admin/roles/' + role.value.id);
+ router.push('/admin/roles/:id', {
+ params: {
+ id: role.value.id,
+ }
+ });
} else {
const created = await os.apiWithDialog('admin/roles/create', {
...data.value,
});
- router.push('/admin/roles/' + created.id);
+ router.push('/admin/roles/:id', {
+ params: {
+ id: created.id,
+ }
+ });
}
}
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 1816aec21e..c6c3165828 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -88,7 +88,11 @@ const role = reactive(await misskeyApi('admin/roles/show', {
}));
function edit() {
- router.push('/admin/roles/' + role.id + '/edit');
+ router.push('/admin/roles/:id/edit', {
+ params: {
+ id: role.id,
+ }
+ });
}
async function del() {
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 7d2393dba5..88ae39d5e1 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -47,7 +47,11 @@ async function timetravel() {
}
function settings() {
- router.push(`/my/antennas/${props.antennaId}`);
+ router.push('/my/antennas/:antennaId', {
+ params: {
+ antennaId: props.antennaId,
+ }
+ });
}
function focus() {
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 72281ea882..80dfb8e84e 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -165,7 +165,11 @@ function save() {
os.apiWithDialog('channels/update', params);
} else {
os.apiWithDialog('channels/create', params).then(created => {
- router.push(`/channels/${created.id}`);
+ router.push('/channels/:channelId', {
+ params: {
+ channelId: created.id,
+ },
+ });
});
}
}
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 116aabaee2..7ce42ea0cb 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -147,7 +147,11 @@ watch(() => props.channelId, async () => {
}, { immediate: true });
function edit() {
- router.push(`/channels/${channel.value?.id}/edit`);
+ router.push('/channels/:channelId/edit', {
+ params: {
+ channelId: props.channelId,
+ }
+ });
}
function openPostForm() {
diff --git a/packages/frontend/src/pages/chat/home.home.vue b/packages/frontend/src/pages/chat/home.home.vue
index a0853fb0c9..756bf8a342 100644
--- a/packages/frontend/src/pages/chat/home.home.vue
+++ b/packages/frontend/src/pages/chat/home.home.vue
@@ -86,7 +86,11 @@ function start(ev: MouseEvent) {
async function startUser() {
// TODO: localOnly は連合に対応したら消す
os.selectUser({ localOnly: true }).then(user => {
- router.push(`/chat/user/${user.id}`);
+ router.push('/chat/user/:userId', {
+ params: {
+ userId: user.id,
+ }
+ });
});
}
@@ -101,7 +105,11 @@ async function createRoom() {
name: result,
});
- router.push(`/chat/room/${room.id}`);
+ router.push('/chat/room/:roomId', {
+ params: {
+ roomId: room.id,
+ }
+ });
}
async function search() {
diff --git a/packages/frontend/src/pages/chat/home.invitations.vue b/packages/frontend/src/pages/chat/home.invitations.vue
index 3cbe186e9d..19d57ea205 100644
--- a/packages/frontend/src/pages/chat/home.invitations.vue
+++ b/packages/frontend/src/pages/chat/home.invitations.vue
@@ -61,7 +61,11 @@ async function join(invitation: Misskey.entities.ChatRoomInvitation) {
roomId: invitation.room.id,
});
- router.push(`/chat/room/${invitation.room.id}`);
+ router.push('/chat/room/:roomId', {
+ params: {
+ roomId: invitation.room.id,
+ },
+ });
}
async function ignore(invitation: Misskey.entities.ChatRoomInvitation) {
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index 4386209f7c..a964b33a52 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -429,7 +429,11 @@ async function save() {
script: script.value,
visibility: visibility.value,
});
- router.push('/play/' + created.id + '/edit');
+ router.push('/play/:id/edit', {
+ params: {
+ id: created.id,
+ },
+ });
}
}
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index 9c0078e15a..cf0d700962 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -85,7 +85,11 @@ async function save() {
fileIds: files.value.map(file => file.id),
isSensitive: isSensitive.value,
});
- router.push(`/gallery/${props.postId}`);
+ router.push('/gallery/:postId', {
+ params: {
+ postId: props.postId,
+ }
+ });
} else {
const created = await os.apiWithDialog('gallery/posts/create', {
title: title.value,
@@ -93,7 +97,11 @@ async function save() {
fileIds: files.value.map(file => file.id),
isSensitive: isSensitive.value,
});
- router.push(`/gallery/${created.id}`);
+ router.push('/gallery/:postId', {
+ params: {
+ postId: created.id,
+ }
+ });
}
}
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index d02b72dd99..eab435c002 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -150,7 +150,11 @@ async function unlike() {
}
function edit() {
- router.push(`/gallery/${post.value.id}/edit`);
+ router.push('/gallery/:postId/edit', {
+ params: {
+ postId: props.postId,
+ },
+ });
}
async function reportAbuse() {
diff --git a/packages/frontend/src/pages/lookup.vue b/packages/frontend/src/pages/lookup.vue
index c969473b19..d5ee0cdf97 100644
--- a/packages/frontend/src/pages/lookup.vue
+++ b/packages/frontend/src/pages/lookup.vue
@@ -45,11 +45,20 @@ function fetch() {
promise = misskeyApi('ap/show', {
uri,
});
+
promise.then(res => {
if (res.type === 'User') {
- mainRouter.replace(res.object.host ? `/@${res.object.username}@${res.object.host}` : `/@${res.object.username}`);
+ mainRouter.replace('/@:acct/:page?', {
+ params: {
+ acct: res.host != null ? `${res.object.username}@${res.object.host}` : res.object.username,
+ }
+ });
} else if (res.type === 'Note') {
- mainRouter.replace(`/notes/${res.object.id}`);
+ mainRouter.replace('/notes/:noteId/:initialTab?', {
+ params: {
+ noteId: res.object.id,
+ }
+ });
} else {
os.alert({
type: 'error',
@@ -63,7 +72,11 @@ function fetch() {
}
promise = misskeyApi('users/show', Misskey.acct.parse(uri));
promise.then(user => {
- mainRouter.replace(user.host ? `/@${user.username}@${user.host}` : `/@${user.username}`);
+ mainRouter.replace('/@:acct/:page?', {
+ params: {
+ acct: user.host != null ? `${user.username}@${user.host}` : user.username,
+ }
+ });
});
}
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index 8a9b9a9b08..9fe03ae981 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -154,7 +154,11 @@ async function save() {
pageId.value = created.id;
currentName.value = name.value.trim();
- mainRouter.replace(`/pages/edit/${pageId.value}`);
+ mainRouter.replace('/pages/edit/:initPageId', {
+ params: {
+ initPageId: pageId.value,
+ },
+ });
}
}
@@ -189,7 +193,11 @@ async function duplicate() {
pageId.value = created.id;
currentName.value = name.value.trim();
- mainRouter.push(`/pages/edit/${pageId.value}`);
+ mainRouter.push('/pages/edit/:initPageId', {
+ params: {
+ initPageId: pageId.value,
+ },
+ });
}
async function add() {
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index cd63e51fd5..5cb13a9c3f 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -267,7 +267,11 @@ function showMenu(ev: MouseEvent) {
menuItems.push({
icon: 'ti ti-pencil',
text: i18n.ts.edit,
- action: () => router.push(`/pages/edit/${page.value.id}`),
+ action: () => router.push('/pages/edit/:initPageId', {
+ params: {
+ initPageId: page.value!.id,
+ },
+ }),
});
if ($i.pinnedPageId === page.value.id) {
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
index e4d921b8d2..0ae374649d 100644
--- a/packages/frontend/src/pages/reversi/index.vue
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -168,7 +168,11 @@ function startGame(game: Misskey.entities.ReversiGameDetailed) {
playbackRate: 1,
});
- router.push(`/reversi/g/${game.id}`);
+ router.push('/reversi/g/:gameId', {
+ params: {
+ gameId: game.id,
+ },
+ });
}
async function matchHeatbeat() {
diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue
index f19c1e7efb..fb34d592a6 100644
--- a/packages/frontend/src/pages/search.note.vue
+++ b/packages/frontend/src/pages/search.note.vue
@@ -264,10 +264,18 @@ async function search() {
const res = await apLookup(searchParams.value.query);
if (res.type === 'User') {
- router.push(`/@${res.object.username}@${res.object.host}`);
+ router.push('/@:acct/:page?', {
+ params: {
+ acct: `${res.object.username}@${res.object.host}`,
+ },
+ });
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (res.type === 'Note') {
- router.push(`/notes/${res.object.id}`);
+ router.push('/notes/:noteId/:initialTab?', {
+ params: {
+ noteId: res.object.id,
+ },
+ });
}
return;
@@ -282,7 +290,7 @@ async function search() {
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
- router.push(`/${searchParams.value.query}`);
+ router.pushByPath(`/${searchParams.value.query}`);
return;
}
}
@@ -293,7 +301,11 @@ async function search() {
text: i18n.ts.openTagPageConfirm,
});
if (!confirm.canceled) {
- router.push(`/tags/${encodeURIComponent(searchParams.value.query.substring(1))}`);
+ router.push('/tags/:tag', {
+ params: {
+ tag: searchParams.value.query.substring(1),
+ },
+ });
return;
}
}
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index bd67d41a80..5110fca10c 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -77,10 +77,18 @@ async function search() {
const res = await promise;
if (res.type === 'User') {
- router.push(`/@${res.object.username}@${res.object.host}`);
+ router.push('/@:acct/:page?', {
+ params: {
+ acct: `${res.object.username}@${res.object.host}`,
+ },
+ });
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (res.type === 'Note') {
- router.push(`/notes/${res.object.id}`);
+ router.push('/notes/:noteId/:initialTab?', {
+ params: {
+ noteId: res.object.id,
+ },
+ });
}
return;
@@ -95,7 +103,7 @@ async function search() {
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
- router.push(`/${query}`);
+ router.pushByPath(`/${query}`);
return;
}
}
@@ -106,7 +114,11 @@ async function search() {
text: i18n.ts.openTagPageConfirm,
});
if (!confirm.canceled) {
- router.push(`/user-tags/${encodeURIComponent(query.substring(1))}`);
+ router.push('/user-tags/:tag', {
+ params: {
+ tag: query.substring(1),
+ },
+ });
return;
}
}
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index 877d2deb90..ee387fb20c 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -135,7 +135,7 @@ async function del(): Promise<void> {
webhookId: props.webhookId,
});
- router.push('/settings/webhook');
+ router.push('/settings/connect');
}
async function test(type: Misskey.entities.UserWebhook['on'][number]): Promise<void> {
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index f166495258..57a85a0be7 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -42,7 +42,11 @@ watch(() => props.listId, async () => {
}, { immediate: true });
function settings() {
- router.push(`/my/lists/${props.listId}`);
+ router.push('/my/lists/:listId', {
+ params: {
+ listId: props.listId,
+ }
+ });
}
const headerActions = computed(() => list.value ? [{
diff --git a/packages/frontend/src/router.definition.ts b/packages/frontend/src/router.definition.ts
index 5e0e6f7286..7edc5ed9b7 100644
--- a/packages/frontend/src/router.definition.ts
+++ b/packages/frontend/src/router.definition.ts
@@ -603,4 +603,4 @@ export const ROUTE_DEF = [{
}, {
path: '/:(*)',
component: page(() => import('@/pages/not-found.vue')),
-}] satisfies RouteDef[];
+}] as const satisfies RouteDef[];
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index 97ca63f50d..b1c1708915 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -20,7 +20,7 @@ export function createRouter(fullPath: string): Router {
export const mainRouter = createRouter(window.location.pathname + window.location.search + window.location.hash);
window.addEventListener('popstate', (event) => {
- mainRouter.replace(window.location.pathname + window.location.search + window.location.hash);
+ mainRouter.replaceByPath(window.location.pathname + window.location.search + window.location.hash);
});
mainRouter.addListener('push', ctx => {
diff --git a/packages/frontend/src/ui/_common_/sw-inject.ts b/packages/frontend/src/ui/_common_/sw-inject.ts
index 1459881ba1..63918fbe2f 100644
--- a/packages/frontend/src/ui/_common_/sw-inject.ts
+++ b/packages/frontend/src/ui/_common_/sw-inject.ts
@@ -43,7 +43,7 @@ export function swInject() {
if (mainRouter.currentRoute.value.path === ev.data.url) {
return window.scroll({ top: 0, behavior: 'smooth' });
}
- return mainRouter.push(ev.data.url);
+ return mainRouter.pushByPath(ev.data.url);
default:
return;
}
diff --git a/packages/frontend/src/utility/get-user-menu.ts b/packages/frontend/src/utility/get-user-menu.ts
index ad0864019b..d4407dadec 100644
--- a/packages/frontend/src/utility/get-user-menu.ts
+++ b/packages/frontend/src/utility/get-user-menu.ts
@@ -158,7 +158,11 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
icon: 'ti ti-user-exclamation',
text: i18n.ts.moderation,
action: () => {
- router.push(`/admin/user/${user.id}`);
+ router.push('/admin/user/:userId', {
+ params: {
+ userId: user.id,
+ },
+ });
},
}, { type: 'divider' });
}
@@ -216,7 +220,12 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
icon: 'ti ti-search',
text: i18n.ts.searchThisUsersNotes,
action: () => {
- router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`);
+ router.push('/search', {
+ query: {
+ username: user.username,
+ host: user.host ?? undefined,
+ },
+ });
},
});
}
diff --git a/packages/frontend/src/utility/lookup.ts b/packages/frontend/src/utility/lookup.ts
index 90611094fa..47d0db125d 100644
--- a/packages/frontend/src/utility/lookup.ts
+++ b/packages/frontend/src/utility/lookup.ts
@@ -19,12 +19,16 @@ export async function lookup(router?: Router) {
if (canceled || query.length <= 1) return;
if (query.startsWith('@') && !query.includes(' ')) {
- _router.push(`/${query}`);
+ _router.pushByPath(`/${query}`);
return;
}
if (query.startsWith('#')) {
- _router.push(`/tags/${encodeURIComponent(query.substring(1))}`);
+ _router.push('/tags/:tag', {
+ params: {
+ tag: query.substring(1),
+ }
+ });
return;
}
@@ -32,9 +36,17 @@ export async function lookup(router?: Router) {
const res = await apLookup(query);
if (res.type === 'User') {
- _router.push(`/@${res.object.username}@${res.object.host}`);
+ _router.push('/@:acct/:page?', {
+ params: {
+ acct: `${res.object.username}@${res.object.host}`,
+ },
+ });
} else if (res.type === 'Note') {
- _router.push(`/notes/${res.object.id}`);
+ _router.push('/notes/:noteId/:initialTab?', {
+ params: {
+ noteId: res.object.id,
+ },
+ });
}
return;