Merge remote-tracking branch 'misskey-original/develop' into develop
This commit is contained in:
commit
ef9ea304f7
|
@ -160,14 +160,14 @@ id: 'aidx'
|
|||
# Job concurrency per worker
|
||||
#deliverJobConcurrency: 128
|
||||
#inboxJobConcurrency: 16
|
||||
#relashionshipJobConcurrency: 16
|
||||
# What's relashionshipJob?:
|
||||
#relationshipJobConcurrency: 16
|
||||
# What's relationshipJob?:
|
||||
# Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
|
||||
|
||||
# Job rate limiter
|
||||
#deliverJobPerSec: 128
|
||||
#inboxJobPerSec: 32
|
||||
#relashionshipJobPerSec: 64
|
||||
#relationshipJobPerSec: 64
|
||||
|
||||
# Job attempts
|
||||
#deliverJobMaxAttempts: 12
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
- Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更
|
||||
- Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正
|
||||
- Fix: properly handle cc followers
|
||||
- Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec
|
||||
|
||||
### Service Worker
|
||||
- Enhance: オフライン表示のデザインを改善・多言語対応
|
||||
|
|
30
cypress/e2e/router.cy.js
Normal file
30
cypress/e2e/router.cy.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
describe('Router transition', () => {
|
||||
describe('Redirect', () => {
|
||||
// サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う(使いまわした方が早い)
|
||||
before(() => {
|
||||
cy.resetState();
|
||||
|
||||
// インスタンス初期セットアップ
|
||||
cy.registerUser('admin', 'pass', true);
|
||||
|
||||
// ユーザー作成
|
||||
cy.registerUser('alice', 'alice1234');
|
||||
|
||||
cy.login('alice', 'alice1234');
|
||||
|
||||
// アカウント初期設定ウィザード
|
||||
// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
|
||||
cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 12000 }).click();
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy-modal-dialog-ok]').click();
|
||||
});
|
||||
|
||||
it('redirect to user profile', () => {
|
||||
// テストのためだけに用意されたリダイレクト用ルートに飛ぶ
|
||||
cy.visit('/redirect-test');
|
||||
|
||||
// プロフィールページのURLであることを確認する
|
||||
cy.url().should('include', '/@alice')
|
||||
});
|
||||
});
|
||||
});
|
|
@ -74,10 +74,10 @@ type Source = {
|
|||
|
||||
deliverJobConcurrency?: number;
|
||||
inboxJobConcurrency?: number;
|
||||
relashionshipJobConcurrency?: number;
|
||||
relationshipJobConcurrency?: number;
|
||||
deliverJobPerSec?: number;
|
||||
inboxJobPerSec?: number;
|
||||
relashionshipJobPerSec?: number;
|
||||
relationshipJobPerSec?: number;
|
||||
deliverJobMaxAttempts?: number;
|
||||
inboxJobMaxAttempts?: number;
|
||||
|
||||
|
@ -135,10 +135,10 @@ export type Config = {
|
|||
outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined;
|
||||
deliverJobConcurrency: number | undefined;
|
||||
inboxJobConcurrency: number | undefined;
|
||||
relashionshipJobConcurrency: number | undefined;
|
||||
relationshipJobConcurrency: number | undefined;
|
||||
deliverJobPerSec: number | undefined;
|
||||
inboxJobPerSec: number | undefined;
|
||||
relashionshipJobPerSec: number | undefined;
|
||||
relationshipJobPerSec: number | undefined;
|
||||
deliverJobMaxAttempts: number | undefined;
|
||||
inboxJobMaxAttempts: number | undefined;
|
||||
proxyRemoteFiles: boolean | undefined;
|
||||
|
@ -241,10 +241,10 @@ export function loadConfig(): Config {
|
|||
outgoingAddressFamily: config.outgoingAddressFamily,
|
||||
deliverJobConcurrency: config.deliverJobConcurrency,
|
||||
inboxJobConcurrency: config.inboxJobConcurrency,
|
||||
relashionshipJobConcurrency: config.relashionshipJobConcurrency,
|
||||
relationshipJobConcurrency: config.relationshipJobConcurrency,
|
||||
deliverJobPerSec: config.deliverJobPerSec,
|
||||
inboxJobPerSec: config.inboxJobPerSec,
|
||||
relashionshipJobPerSec: config.relashionshipJobPerSec,
|
||||
relationshipJobPerSec: config.relationshipJobPerSec,
|
||||
deliverJobMaxAttempts: config.deliverJobMaxAttempts,
|
||||
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
||||
proxyRemoteFiles: config.proxyRemoteFiles,
|
||||
|
|
|
@ -286,9 +286,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
|
||||
autorun: false,
|
||||
concurrency: this.config.relashionshipJobConcurrency ?? 16,
|
||||
concurrency: this.config.relationshipJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.relashionshipJobPerSec ?? 64,
|
||||
max: this.config.relationshipJobPerSec ?? 64,
|
||||
duration: 1000,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -93,6 +93,13 @@ windowRouter.addListener('push', ctx => {
|
|||
history.value.push({ path: ctx.path, key: ctx.key });
|
||||
});
|
||||
|
||||
windowRouter.addListener('replace', ctx => {
|
||||
history.value.pop();
|
||||
history.value.push({ path: ctx.path, key: ctx.key });
|
||||
});
|
||||
|
||||
windowRouter.init();
|
||||
|
||||
provide('router', windowRouter);
|
||||
provideMetadataReceiver((info) => {
|
||||
pageMetadata.value = info;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue';
|
||||
import type { RouteDef } from '@/nirax.js';
|
||||
import { IRouter, Router } from '@/nirax.js';
|
||||
import { $i, iAmModerator } from '@/account.js';
|
||||
import MkLoading from '@/pages/_loading_.vue';
|
||||
|
@ -16,7 +17,7 @@ const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
|
|||
errorComponent: MkError,
|
||||
});
|
||||
|
||||
const routes = [{
|
||||
const routes: RouteDef[] = [{
|
||||
path: '/@:initUser/pages/:initPageName/view-source',
|
||||
component: page(() => import('@/pages/page-editor/page-editor.vue')),
|
||||
}, {
|
||||
|
@ -333,8 +334,7 @@ const routes = [{
|
|||
component: page(() => import('@/pages/registry.vue')),
|
||||
}, {
|
||||
path: '/install-extentions',
|
||||
// Note: This path is kept for compatibility. It may be deleted.
|
||||
component: page(() => import('@/pages/install-extensions.vue')),
|
||||
redirect: '/install-extensions',
|
||||
loginRequired: true,
|
||||
}, {
|
||||
path: '/install-extensions',
|
||||
|
@ -557,6 +557,11 @@ const routes = [{
|
|||
path: '/',
|
||||
component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')),
|
||||
globalCacheKey: 'index',
|
||||
}, {
|
||||
// テスト用リダイレクト設定。ログイン中ユーザのプロフィールにリダイレクトする
|
||||
path: '/redirect-test',
|
||||
redirect: $i ? `@${$i.username}` : '/',
|
||||
loginRequired: true,
|
||||
}, {
|
||||
path: '/:(*)',
|
||||
component: page(() => import('@/pages/not-found.vue')),
|
||||
|
@ -575,8 +580,6 @@ export function setupRouter(app: App) {
|
|||
|
||||
const mainRouter = createRouterImpl(location.pathname + location.search + location.hash);
|
||||
|
||||
window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);
|
||||
|
||||
window.addEventListener('popstate', (event) => {
|
||||
mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
|
||||
});
|
||||
|
@ -585,5 +588,11 @@ export function setupRouter(app: App) {
|
|||
window.history.pushState({ key: ctx.key }, '', ctx.path);
|
||||
});
|
||||
|
||||
mainRouter.addListener('replace', ctx => {
|
||||
window.history.replaceState({ key: ctx.key }, '', ctx.path);
|
||||
});
|
||||
|
||||
mainRouter.init();
|
||||
|
||||
setMainRouter(mainRouter);
|
||||
}
|
||||
|
|
|
@ -9,16 +9,25 @@ import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
|
|||
import { EventEmitter } from 'eventemitter3';
|
||||
import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
|
||||
|
||||
export type RouteDef = {
|
||||
interface RouteDefBase {
|
||||
path: string;
|
||||
component: Component;
|
||||
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;
|
||||
|
@ -48,7 +57,19 @@ export type RouterEvent = {
|
|||
same: () => void;
|
||||
}
|
||||
|
||||
export type Resolved = { route: RouteDef; props: Map<string, string | boolean>; child?: Resolved; };
|
||||
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;
|
||||
|
@ -81,6 +102,11 @@ export interface IRouter extends EventEmitter<RouterEvent> {
|
|||
currentRoute: ShallowRef<RouteDef>;
|
||||
navHook: ((path: string, flag?: any) => boolean) | null;
|
||||
|
||||
/**
|
||||
* ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
|
||||
*/
|
||||
init(): void;
|
||||
|
||||
resolve(path: string): Resolved | null;
|
||||
|
||||
getCurrentPath(): any;
|
||||
|
@ -156,12 +182,13 @@ export interface IRouter extends EventEmitter<RouterEvent> {
|
|||
export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
||||
private routes: RouteDef[];
|
||||
public current: Resolved;
|
||||
public currentRef: ShallowRef<Resolved> = shallowRef();
|
||||
public currentRoute: ShallowRef<RouteDef> = shallowRef();
|
||||
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;
|
||||
|
||||
|
@ -169,13 +196,24 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
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;
|
||||
this.navigate(currentPath, null, false);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -188,6 +226,12 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
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 {
|
||||
|
@ -238,6 +282,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
route,
|
||||
props,
|
||||
child,
|
||||
_parsedRoute,
|
||||
};
|
||||
} else {
|
||||
continue forEachRouteLoop;
|
||||
|
@ -263,6 +308,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
return {
|
||||
route,
|
||||
props,
|
||||
_parsedRoute,
|
||||
};
|
||||
} else {
|
||||
if (route.children) {
|
||||
|
@ -272,6 +318,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
route,
|
||||
props,
|
||||
child,
|
||||
_parsedRoute,
|
||||
};
|
||||
} else {
|
||||
continue forEachRouteLoop;
|
||||
|
@ -290,7 +337,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
return check(this.routes, _parts);
|
||||
}
|
||||
|
||||
private navigate(path: string, key: string | null | undefined, emitChange = true) {
|
||||
private navigate(path: string, key: string | null | undefined, emitChange = true, _redirected = false): Resolved {
|
||||
const beforePath = this.currentPath;
|
||||
this.currentPath = path;
|
||||
|
||||
|
@ -300,6 +347,20 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
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);
|
||||
|
@ -321,7 +382,11 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
this.redirectCount = 0;
|
||||
return {
|
||||
...res,
|
||||
redirected: _redirected,
|
||||
};
|
||||
}
|
||||
|
||||
public getCurrentPath() {
|
||||
|
@ -345,7 +410,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
const res = this.navigate(path, null);
|
||||
this.emit('push', {
|
||||
beforePath,
|
||||
path,
|
||||
path: res._parsedRoute.fullPath,
|
||||
route: res.route,
|
||||
props: res.props,
|
||||
key: this.currentKey,
|
||||
|
@ -353,7 +418,11 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
}
|
||||
|
||||
public replace(path: string, key?: string | null) {
|
||||
this.navigate(path, key);
|
||||
const res = this.navigate(path, key);
|
||||
this.emit('replace', {
|
||||
path: res._parsedRoute.fullPath,
|
||||
key: this.currentKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue