misskey/packages/frontend/src/nirax.ts

466 lines
11 KiB
TypeScript
Raw Normal View History

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
2022-06-21 05:18:06 +00:00
// NIRAX --- A lightweight router
import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3';
feat(frontend): ノート・ユーザータイムライン埋め込み (#13929) * fix * navhookをbootに移動 * サーバーサイドのbootも分けるように * 埋め込みページかどうかの判定は最初の一回だけに * tooltipは出せるように * fix design * 埋め込み独自のtooltipを削除 * ロジックの分岐が多かったMkNoteDetailedを分離 * fix indent * プレビュー用iframeにフォーカスが当たるのを修正 * popupの制御を出す側で行うように * パラメータが逆になっていたのを修正 * Update MkEmbedCodeGenDialog.vue * fix * eliminate misskey-js lint warns * fix * add appropriate attributes to embed html * enhance: サーバーサイドのembed系をさらに分離 * enhance: embed routerを分離(route定義をboot時に変更できるようにする改修を含む) * type * lint * fix indent * server-side styleを完全に分離 * Revert "refactor: 画面サイズのしきい値をconstにまとめる" This reverts commit 05ca36f400889456981e89489ae0ae242fa09b67. * fix * revert all changes in base.pug * embedドメインをまとめた * embedドメインをまとめた * prevent calling contextmenu in embed page by stopping at the caller * fix import * fix import * improve directory structure * fix import * register timeline ui as a container * wa- * rename * wa- * Update EmMediaList.vue * Update EmMediaList.vue * Update EmMediaList.vue * Update EmMediaImage.vue * Update EmNote.vue * revert mkmedialist changes * 戻し漏れ * wip * tweak embed media ui * revert original media components * Update boot.embed.js * rename * wip * Update MkNote.vue * wip * Update MkSubNoteContent.vue * Update EmNote.vue * Update packages/frontend/src/router/definition.ts * Revert "Update packages/frontend/src/router/definition.ts" This reverts commit 937ae44521cdb0f250796943b20142b65f8ed944. * refactor EmMediaImage * fix import * remove unused imports * Update router.ts * wip * Update boot.ts * wip * wip * wip * wip * Update EmNote.vue * Update EmNote.vue * Create EmA.vue * Create EmAvatar.vue * Update EmAvatar.vue * wip * wip * wip * Create EmImgWithBlurhash.vue * Update EmImgWithBlurhash.vue * Create EmPagination.vue * wip * Update boot.ts * wip * wip * wi@p * wip * wip * wiop * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update boot.ts * wip * Update MkMisskeyFlavoredMarkdown.ts * wip * wip * wip * wip * wip * Update post-message.ts * wip * Update EmNoteDetailed.vue * Update EmNoteDetailed.vue * Create instance.ts * Update EmNoteDetailed.vue * wip * Update EmNoteDetailed.vue * wip * wip * wip * Update pnpm-lock.yaml * wip * wip * wp * wip * Update ClientServerService.ts * wip * Update boot.ts * Update vite.config.local-dev.ts * Update vite.config.ts * Create index.html * wa- * wip * Update boot.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * Create EmLink.vue * Create EmMention.vue * Update EmMfm.ts * wip * wip * wip * wip * Update vite.config.ts * Update boot.ts * Update EmA.vue * うぃp * wip * wip * Create EmError.vue * wip * Update MkEmbedCodeGenDialog.vue * Update EmNote.vue * wip * wip * Update user-timeline.vue * Update check-spdx-license-id.yml * wip * wip * style(frontend-shared): lint fixes on build.js * fix(frontend-shared): include `*.{js,json}` files in js-built * wip * use alias * refactor * refactor * Update scroll.ts * refactor * refactor * refactor * wip * wip * wip * wip * Update roles.vue * Update branding.vue * wip * wip * wip * Update page.vue * wip * fix import * add missing css variables * 絵文字をtwemojiに変更 クライアントデフォルトにあわせるため * force empoll readonly * fix compiler error * fix broken imports * tweak button style * run api extractor * fix storybook theme preloads * fix storybook instance imports * Update preview.ts * Update preview.ts * Update preview.ts * Revert "Update preview.ts" This reverts commit 12bab1c6fbd3baf753515df760ff19d027b85155. * Revert "Update preview.ts" This reverts commit 5c0ce01dbdf2194ffe94aba950f747a9968f29c4. * Revert "Update preview.ts" This reverts commit f4863524d7e5ca0f25470808849c24a72bea000a. * Revert "fix storybook instance imports" This reverts commit ed8eabb246edf731d31adffbe3c77c539e53ae9e. * Revert "wip" This reverts commit d3c1926519878155193a1654f49141e515d49683. * Revert "Update page.vue" This reverts commit 27c7900b0c1ae296b56075e8a9c22585d9cd744b. * Revert "Update branding.vue" This reverts commit c08ccb65ba66774c3e2b3dcfc6153004b5c0aa16. * Revert "Update roles.vue" This reverts commit 1488b670660cb1803d17d8f5c78f2d79e59fa52d. * Revert "wip" This reverts commit aab1c769814b08c257cad3025422a0eea3bfba4f. * refactor: use common media proxy * fix imports * fix * fix: MediaProxyの初期化を保証する(storybook対策?) * enhance(frontend-embed): improve embedParams provide * fix(backend): MK_DEV_PREFER=backendのときにembed viteが読み込めないのを修正 * fix * embed-pageを共通化 * fix import * fix import * fix import * const.jsを共通化 (たぶんrevertしすぎた) * fix type error * fix duplicated import * fix lint * fix * コメントとして残す * sharedとembedをlint対象にする * lint * attempt to fix eslint (frontend-shared) * lint fixes --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
2024-09-09 11:57:36 +00:00
function safeURIDecode(str: string): string {
try {
return decodeURIComponent(str);
} catch {
return str;
}
}
interface RouteDefBase {
path: string;
query?: Record<string, string>;
loginRequired?: boolean;
name?: string;
hash?: string;
globalCacheKey?: string;
children?: RouteDef[];
}
interface RouteDefWithComponent extends RouteDefBase {
component: Component,
}
interface RouteDefWithRedirect extends RouteDefBase {
redirect: string | ((props: Map<string, string | boolean>) => string);
}
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
type ParsedPath = (string | {
name: string;
startsWith?: string;
wildcard?: boolean;
optional?: boolean;
})[];
export type RouterEvent = {
change: (ctx: {
beforePath: string;
path: string;
resolved: Resolved;
key: string;
}) => void;
replace: (ctx: {
path: string;
key: string;
}) => void;
push: (ctx: {
beforePath: string;
path: string;
route: RouteDef | null;
props: Map<string, string> | null;
key: string;
}) => void;
same: () => void;
}
export type Resolved = {
route: RouteDef;
props: Map<string, string | boolean>;
child?: Resolved;
redirected?: boolean;
/** @internal */
_parsedRoute: {
fullPath: string;
queryString: string | null;
hash: string | null;
};
};
function parsePath(path: string): ParsedPath {
const res = [] as ParsedPath;
path = path.substring(1);
for (const part of path.split('/')) {
if (part.includes(':')) {
const prefix = part.substring(0, part.indexOf(':'));
const placeholder = part.substring(part.indexOf(':') + 1);
const wildcard = placeholder.includes('(*)');
const optional = placeholder.endsWith('?');
res.push({
name: placeholder.replace('(*)', '').replace('?', ''),
startsWith: prefix !== '' ? prefix : undefined,
wildcard,
optional,
});
2022-06-23 16:26:15 +00:00
} else if (part.length !== 0) {
res.push(part);
}
}
return res;
}
export interface IRouter extends EventEmitter<RouterEvent> {
current: Resolved;
currentRef: ShallowRef<Resolved>;
currentRoute: ShallowRef<RouteDef>;
navHook: ((path: string, flag?: any) => boolean) | null;
/**
* eventListenerの定義後に必ず呼び出すこと
*/
init(): void;
resolve(path: string): Resolved | null;
getCurrentPath(): any;
getCurrentKey(): string;
push(path: string, flag?: any): void;
replace(path: string, key?: string | null): void;
/** @see EventEmitter */
eventNames(): Array<EventEmitter.EventNames<RouterEvent>>;
/** @see EventEmitter */
listeners<T extends EventEmitter.EventNames<RouterEvent>>(
event: T
): Array<EventEmitter.EventListener<RouterEvent, T>>;
/** @see EventEmitter */
listenerCount(
event: EventEmitter.EventNames<RouterEvent>
): number;
/** @see EventEmitter */
emit<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
...args: EventEmitter.EventArgs<RouterEvent, T>
): boolean;
/** @see EventEmitter */
on<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
addListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
once<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean | undefined
): this;
/** @see EventEmitter */
off<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean | undefined
): this;
/** @see EventEmitter */
removeAllListeners(
event?: EventEmitter.EventNames<RouterEvent>
): this;
}
export class Router extends EventEmitter<RouterEvent> implements IRouter {
private routes: RouteDef[];
public current: Resolved;
public currentRef: ShallowRef<Resolved>;
public currentRoute: ShallowRef<RouteDef>;
private currentPath: string;
private isLoggedIn: boolean;
private notFoundPageComponent: Component;
private currentKey = Date.now().toString();
private redirectCount = 0;
public navHook: ((path: string, flag?: any) => boolean) | null = null;
constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
super();
this.routes = routes;
this.current = this.resolve(currentPath)!;
this.currentRef = shallowRef(this.current);
this.currentRoute = shallowRef(this.current.route);
this.currentPath = currentPath;
this.isLoggedIn = isLoggedIn;
this.notFoundPageComponent = notFoundPageComponent;
}
public init() {
const res = this.navigate(this.currentPath, null, false);
this.emit('replace', {
path: res._parsedRoute.fullPath,
key: this.currentKey,
});
}
public resolve(path: string): Resolved | null {
const fullPath = path;
let queryString: string | null = null;
let hash: string | null = null;
if (path[0] === '/') path = path.substring(1);
if (path.includes('#')) {
hash = path.substring(path.indexOf('#') + 1);
path = path.substring(0, path.indexOf('#'));
}
if (path.includes('?')) {
queryString = path.substring(path.indexOf('?') + 1);
path = path.substring(0, path.indexOf('?'));
}
const _parsedRoute = {
fullPath,
queryString,
hash,
};
if (_DEV_) console.log('Routing: ', path, queryString);
function check(routes: RouteDef[], _parts: string[]): Resolved | null {
forEachRouteLoop:
for (const route of routes) {
let parts = [..._parts];
const props = new Map<string, string>();
pathMatchLoop:
for (const p of parsePath(route.path)) {
if (typeof p === 'string') {
if (p === parts[0]) {
parts.shift();
} else {
continue forEachRouteLoop;
}
} else {
if (parts[0] == null && !p.optional) {
continue forEachRouteLoop;
}
if (p.wildcard) {
if (parts.length !== 0) {
props.set(p.name, safeURIDecode(parts.join('/')));
parts = [];
}
break pathMatchLoop;
2022-06-21 05:12:39 +00:00
} else {
if (p.startsWith) {
if (parts[0] == null || !parts[0].startsWith(p.startsWith)) continue forEachRouteLoop;
props.set(p.name, safeURIDecode(parts[0].substring(p.startsWith.length)));
parts.shift();
} else {
if (parts[0]) {
props.set(p.name, safeURIDecode(parts[0]));
}
parts.shift();
2022-07-13 09:28:04 +00:00
}
2022-06-21 05:12:39 +00:00
}
}
}
if (parts.length === 0) {
if (route.children) {
const child = check(route.children, []);
if (child) {
return {
route,
props,
child,
_parsedRoute,
};
} else {
continue forEachRouteLoop;
}
}
if (route.hash != null && hash != null) {
props.set(route.hash, safeURIDecode(hash));
}
if (route.query != null && queryString != null) {
const queryObject = [...new URLSearchParams(queryString).entries()]
.reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {});
for (const q in route.query) {
const as = route.query[q];
if (queryObject[q]) {
props.set(as, safeURIDecode(queryObject[q]));
}
}
}
return {
route,
props,
_parsedRoute,
};
} else {
if (route.children) {
const child = check(route.children, parts);
if (child) {
return {
route,
props,
child,
_parsedRoute,
};
} else {
continue forEachRouteLoop;
}
} else {
continue forEachRouteLoop;
}
}
}
return null;
}
const _parts = path.split('/').filter(part => part.length !== 0);
return check(this.routes, _parts);
}
private navigate(path: string, key: string | null | undefined, emitChange = true, _redirected = false): Resolved {
const beforePath = this.currentPath;
this.currentPath = path;
const res = this.resolve(this.currentPath);
if (res == null) {
throw new Error('no route found for: ' + path);
}
if ('redirect' in res.route) {
let redirectPath: string;
if (typeof res.route.redirect === 'function') {
redirectPath = res.route.redirect(res.props);
} else {
redirectPath = res.route.redirect + (res._parsedRoute.queryString ? '?' + res._parsedRoute.queryString : '') + (res._parsedRoute.hash ? '#' + res._parsedRoute.hash : '');
}
if (_DEV_) console.log('Redirecting to: ', redirectPath);
if (_redirected && this.redirectCount++ > 10) {
throw new Error('redirect loop detected');
}
return this.navigate(redirectPath, null, emitChange, true);
}
if (res.route.loginRequired && !this.isLoggedIn) {
res.route.component = this.notFoundPageComponent;
res.props.set('showLoginPopup', true);
}
const isSamePath = beforePath === path;
if (isSamePath && key == null) key = this.currentKey;
this.current = res;
this.currentRef.value = res;
this.currentRoute.value = res.route;
this.currentKey = res.route.globalCacheKey ?? key ?? path;
if (emitChange && res.route.path !== '/:(*)') {
this.emit('change', {
beforePath,
path,
resolved: res,
key: this.currentKey,
});
}
this.redirectCount = 0;
return {
...res,
redirected: _redirected,
};
}
public getCurrentPath() {
return this.currentPath;
}
public getCurrentKey() {
return this.currentKey;
}
public push(path: string, flag?: any) {
2022-07-05 13:25:27 +00:00
const beforePath = this.currentPath;
if (path === beforePath) {
this.emit('same');
return;
}
2022-06-28 08:59:23 +00:00
if (this.navHook) {
const cancel = this.navHook(path, flag);
2022-06-28 08:59:23 +00:00
if (cancel) return;
}
const res = this.navigate(path, null);
if (res.route.path === '/:(*)') {
location.href = path;
} else {
this.emit('push', {
beforePath,
path: res._parsedRoute.fullPath,
route: res.route,
props: res.props,
key: this.currentKey,
});
}
}
public replace(path: string, key?: string | null) {
const res = this.navigate(path, key);
this.emit('replace', {
path: res._parsedRoute.fullPath,
key: this.currentKey,
});
}
}
refactor: frontendのcomponentsの型エラーを改善 (#12926) * add: safeFloatParserを追加 * fix: 欠けていた型を追加 * refactor: pageBlockTypesをjson-schemaに移植 * refactor: components/global内の型エラーが出ている箇所を修正 * lint: fix null check style * refactor: fix type error * refactor: fix some type errors * fix: 翻訳が抜けていた箇所を修正 * refactor: getJsonSchemaで正しいスキーマが返されるように修正 * fix: MkChartの型エラーとbytesオプションが機能していない問題を修正 * fix(misskey-js): `drive`->`folderUpdated`のpayloadの型が間違っていたのを修正 * refactor: fix some type errors * change: Captcha読み込み中の文言をLoadingに変更 * refactor(backend/misskey-js): MainEventの型を改善 * refactor: chartjs-plugin-gradientが二重でpluginに登録されていたのを修正 * update: misskey-js.api.md * refactor: fix some type errors * fix: backendのtypecheckが落ちていたのを修正 * update: misskey-js.api.md * add: json-schemaのnoteにpollの型定義を追加 * refactor: noteのjson-schemaの型を改善 * refactor: MkPoll * refactor: fix some type errors * change: UserLiteにisLockedを持たせるように * fix: notificationスキーマにroleが含まれていないのを修正 * Revert "change: UserLiteにisLockedを持たせるように" This reverts commit 1bb0c8e7a9b19a4e9f21bf7381712b98f27672a5. * fix: フォロー通知から鍵垢へのフォローを行うと処理中のまま止まってしまう問題を修正 * refactor: noteスキーマのvisibilityにenumを追加 * change: deepCloneのCloneableTypeにundefinedを追加 * refactor: fix some type errors * refactor: `allowEmpty: false`を使用していた箇所を`minLength: 1`に置き換え * enhance: API 'retension' のresponseの型を追加 * fix: Chart関連のtooltipが正しい位置に表示されない問題を修正 * refactor: fix some type errors * fix: 型情報が不足していたのを修正 * enhance: announcementスキーマにenumを追加 * enhance: ロールポリシーの型定義をRoleServiceからjson-schemaに移植 * refactor: policiesを`ref: RolePolicies`に統一 * fix: API `meta` のレスポンスの型にpoliciesが含まれていないのを修正 * refactor: fix some type errors * fix: backendのlintが落ちているのを修正 * fix: MkFoldableSectionの開閉時のanimationが適用されていない問題を修正 * fix: backendのtypecheckが落ちているのを修正 * update: run build-misskey-js-with-types * fix: MkDialogのmount時に文字数制限の判定が行われない問題を修正 * update: CHANGELOG.md * refactor: MkUserSelectDialogの型を改善 * fix: deepCloneでundefinedはcloneしないように (#9207) * change: frontendのcloneをbackend側にも反映 * update: CHANGELOG.md * fix: RoleServiceからPackを通して型RolePoliciesに依存させないように * Update packages/frontend/src/scripts/get-note-summary.ts * revert RoleService.ts changes * change: optional chaining -> non-null assertion * remove: unused import * fix: propsで渡されたuserがUserLiteの場合に意図しない動作になってしまうのを修正 * change: fix null check style * refactor: fix type error * change: fix null check style * Update packages/frontend/src/components/MkDrive.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * refactor: css moduleでglobalを使わないように * refactor: roleのiconUrlは必ず存在するものとして扱うように * enhance: MenuButtonのactiveにcomputedを受け付けられるように * Update packages/frontend/src/components/MkNotePreview.vue * Update MkWindow.vue * refactor: notification.noteは必ず存在するものとして扱うように * Update packages/frontend/src/components/MkNotification.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * fix: MkSignupDialogでdoneのemit時にresを含んでいなかったのを修正 * Update packages/frontend/src/scripts/clone.ts Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * refactor: 不要な返り値の型を削除 * refactor: 不要なnullチェックを削除 * update: misskey-js-autogen * update: clone.ts * refactor * Update MkNotification.vue * Update MkNotification.vue * :v: * Update MkNotification.vue * Update MkNotification.vue * Update MkNotification.vue * Update MkNotifications.vue * Update MkUserSetupDialog.Profile.vue * Update MkUserCardMini.vue * :v: * Update MkMenu.vue --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-30 10:53:53 +00:00
export function useScrollPositionManager(getScrollContainer: () => HTMLElement | null, router: IRouter) {
const scrollPosStore = new Map<string, number>();
onMounted(() => {
const scrollContainer = getScrollContainer();
refactor: frontendのcomponentsの型エラーを改善 (#12926) * add: safeFloatParserを追加 * fix: 欠けていた型を追加 * refactor: pageBlockTypesをjson-schemaに移植 * refactor: components/global内の型エラーが出ている箇所を修正 * lint: fix null check style * refactor: fix type error * refactor: fix some type errors * fix: 翻訳が抜けていた箇所を修正 * refactor: getJsonSchemaで正しいスキーマが返されるように修正 * fix: MkChartの型エラーとbytesオプションが機能していない問題を修正 * fix(misskey-js): `drive`->`folderUpdated`のpayloadの型が間違っていたのを修正 * refactor: fix some type errors * change: Captcha読み込み中の文言をLoadingに変更 * refactor(backend/misskey-js): MainEventの型を改善 * refactor: chartjs-plugin-gradientが二重でpluginに登録されていたのを修正 * update: misskey-js.api.md * refactor: fix some type errors * fix: backendのtypecheckが落ちていたのを修正 * update: misskey-js.api.md * add: json-schemaのnoteにpollの型定義を追加 * refactor: noteのjson-schemaの型を改善 * refactor: MkPoll * refactor: fix some type errors * change: UserLiteにisLockedを持たせるように * fix: notificationスキーマにroleが含まれていないのを修正 * Revert "change: UserLiteにisLockedを持たせるように" This reverts commit 1bb0c8e7a9b19a4e9f21bf7381712b98f27672a5. * fix: フォロー通知から鍵垢へのフォローを行うと処理中のまま止まってしまう問題を修正 * refactor: noteスキーマのvisibilityにenumを追加 * change: deepCloneのCloneableTypeにundefinedを追加 * refactor: fix some type errors * refactor: `allowEmpty: false`を使用していた箇所を`minLength: 1`に置き換え * enhance: API 'retension' のresponseの型を追加 * fix: Chart関連のtooltipが正しい位置に表示されない問題を修正 * refactor: fix some type errors * fix: 型情報が不足していたのを修正 * enhance: announcementスキーマにenumを追加 * enhance: ロールポリシーの型定義をRoleServiceからjson-schemaに移植 * refactor: policiesを`ref: RolePolicies`に統一 * fix: API `meta` のレスポンスの型にpoliciesが含まれていないのを修正 * refactor: fix some type errors * fix: backendのlintが落ちているのを修正 * fix: MkFoldableSectionの開閉時のanimationが適用されていない問題を修正 * fix: backendのtypecheckが落ちているのを修正 * update: run build-misskey-js-with-types * fix: MkDialogのmount時に文字数制限の判定が行われない問題を修正 * update: CHANGELOG.md * refactor: MkUserSelectDialogの型を改善 * fix: deepCloneでundefinedはcloneしないように (#9207) * change: frontendのcloneをbackend側にも反映 * update: CHANGELOG.md * fix: RoleServiceからPackを通して型RolePoliciesに依存させないように * Update packages/frontend/src/scripts/get-note-summary.ts * revert RoleService.ts changes * change: optional chaining -> non-null assertion * remove: unused import * fix: propsで渡されたuserがUserLiteの場合に意図しない動作になってしまうのを修正 * change: fix null check style * refactor: fix type error * change: fix null check style * Update packages/frontend/src/components/MkDrive.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * refactor: css moduleでglobalを使わないように * refactor: roleのiconUrlは必ず存在するものとして扱うように * enhance: MenuButtonのactiveにcomputedを受け付けられるように * Update packages/frontend/src/components/MkNotePreview.vue * Update MkWindow.vue * refactor: notification.noteは必ず存在するものとして扱うように * Update packages/frontend/src/components/MkNotification.vue Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * fix: MkSignupDialogでdoneのemit時にresを含んでいなかったのを修正 * Update packages/frontend/src/scripts/clone.ts Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * refactor: 不要な返り値の型を削除 * refactor: 不要なnullチェックを削除 * update: misskey-js-autogen * update: clone.ts * refactor * Update MkNotification.vue * Update MkNotification.vue * :v: * Update MkNotification.vue * Update MkNotification.vue * Update MkNotification.vue * Update MkNotifications.vue * Update MkUserSetupDialog.Profile.vue * Update MkUserCardMini.vue * :v: * Update MkMenu.vue --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2024-01-30 10:53:53 +00:00
if (scrollContainer == null) return;
scrollContainer.addEventListener('scroll', () => {
scrollPosStore.set(router.getCurrentKey(), scrollContainer.scrollTop);
}, { passive: true });
router.addListener('change', ctx => {
const scrollPos = scrollPosStore.get(ctx.key) ?? 0;
scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
if (scrollPos !== 0) {
window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
}, 100);
}
});
router.addListener('same', () => {
scrollContainer.scroll({ top: 0, behavior: 'smooth' });
});
});
}