Compare commits
19 Commits
73640f3515
...
bc8a9d54d2
Author | SHA1 | Date |
---|---|---|
anatawa12 | bc8a9d54d2 | |
かっこかり | 3bf63dd9c5 | |
かっこかり | ce95323e49 | |
Sayamame-beans | bb921297c7 | |
syuilo | 0265d913e5 | |
Kisaragi | 31daf10f87 | |
anatawa12 | d8941558c3 | |
anatawa12 | e77bcf5ff7 | |
anatawa12 | f9dd3aca78 | |
anatawa12 | fce656b5e9 | |
anatawa12 | 0ef7f14fa9 | |
anatawa12 | 4af9c90d12 | |
anatawa12 | 3eb1875804 | |
anatawa12 | f72c768407 | |
anatawa12 | b3561ce425 | |
anatawa12 | 1d6d90d62d | |
anatawa12 | d8844aed6f | |
anatawa12 | 28c8d318e1 | |
anatawa12 | 03c2919b69 |
|
@ -14,6 +14,7 @@
|
||||||
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
|
- Fix: 月の違う同じ日はセパレータが表示されないのを修正
|
||||||
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
|
- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正
|
||||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
|
||||||
|
- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
|
- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
|
||||||
|
@ -149,6 +150,7 @@
|
||||||
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
||||||
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
- Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059)
|
||||||
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
- Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正
|
||||||
|
- Fix: レートリミットのfactorが二回適用されて二乗の効果がある問題を修正 (#13997)
|
||||||
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
|
- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正
|
||||||
- Fix: 空文字列のリアクションはフォールバックされるように
|
- Fix: 空文字列のリアクションはフォールバックされるように
|
||||||
- Fix: リノートにリアクションできないように
|
- Fix: リノートにリアクションできないように
|
||||||
|
|
|
@ -3121,7 +3121,7 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"narrow": string;
|
"narrow": string;
|
||||||
/**
|
/**
|
||||||
* 設定はページリロード後に反映されます。今すぐリロードしますか?
|
* 設定はページリロード後に反映されます。
|
||||||
*/
|
*/
|
||||||
"reloadToApplySetting": string;
|
"reloadToApplySetting": string;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -778,7 +778,7 @@ left: "左"
|
||||||
center: "中央"
|
center: "中央"
|
||||||
wide: "広い"
|
wide: "広い"
|
||||||
narrow: "狭い"
|
narrow: "狭い"
|
||||||
reloadToApplySetting: "設定はページリロード後に反映されます。今すぐリロードしますか?"
|
reloadToApplySetting: "設定はページリロード後に反映されます。"
|
||||||
needReloadToApply: "反映には再起動が必要です。"
|
needReloadToApply: "反映には再起動が必要です。"
|
||||||
showTitlebar: "タイトルバーを表示する"
|
showTitlebar: "タイトルバーを表示する"
|
||||||
clearCache: "キャッシュをクリア"
|
clearCache: "キャッシュをクリア"
|
||||||
|
|
|
@ -123,11 +123,14 @@ export class AntennaService implements OnApplicationShutdown {
|
||||||
if (antenna.src === 'home') {
|
if (antenna.src === 'home') {
|
||||||
// TODO
|
// TODO
|
||||||
} else if (antenna.src === 'list') {
|
} else if (antenna.src === 'list') {
|
||||||
const listUsers = (await this.userListMembershipsRepository.findBy({
|
if (antenna.userListId == null) return false;
|
||||||
userListId: antenna.userListId!,
|
const exists = await this.userListMembershipsRepository.exists({
|
||||||
})).map(x => x.userId);
|
where: {
|
||||||
|
userListId: antenna.userListId,
|
||||||
if (!listUsers.includes(note.userId)) return false;
|
userId: note.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!exists) return false;
|
||||||
} else if (antenna.src === 'users') {
|
} else if (antenna.src === 'users') {
|
||||||
const accts = antenna.users.map(x => {
|
const accts = antenna.users.map(x => {
|
||||||
const { username, host } = Acct.parse(x);
|
const { username, host } = Acct.parse(x);
|
||||||
|
|
|
@ -326,19 +326,15 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
|
|
||||||
if (factor > 0) {
|
if (factor > 0) {
|
||||||
// Rate limit
|
// Rate limit
|
||||||
await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor).catch(err => {
|
const rateLimit = await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor);
|
||||||
if ('info' in err) {
|
if (rateLimit != null) {
|
||||||
// errはLimiter.LimiterInfoであることが期待される
|
throw new ApiError({
|
||||||
throw new ApiError({
|
message: 'Rate limit exceeded. Please try again later.',
|
||||||
message: 'Rate limit exceeded. Please try again later.',
|
code: 'RATE_LIMIT_EXCEEDED',
|
||||||
code: 'RATE_LIMIT_EXCEEDED',
|
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
||||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
httpStatusCode: 429,
|
||||||
httpStatusCode: 429,
|
}, rateLimit.info);
|
||||||
}, err.info);
|
}
|
||||||
} else {
|
|
||||||
throw new TypeError('information must be a rate-limiter information.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,14 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { IEndpointMeta } from './endpoints.js';
|
import type { IEndpointMeta } from './endpoints.js';
|
||||||
|
|
||||||
|
type RateLimitInfo = {
|
||||||
|
code: 'BRIEF_REQUEST_INTERVAL',
|
||||||
|
info: Limiter.LimiterInfo,
|
||||||
|
} | {
|
||||||
|
code: 'RATE_LIMIT_EXCEEDED',
|
||||||
|
info: Limiter.LimiterInfo,
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RateLimiterService {
|
export class RateLimiterService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
@ -31,77 +39,57 @@ export class RateLimiterService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string, factor = 1) {
|
private checkLimiter(options: Limiter.LimiterOption): Promise<Limiter.LimiterInfo> {
|
||||||
{
|
return new Promise<Limiter.LimiterInfo>((resolve, reject) => {
|
||||||
if (this.disabled) {
|
new Limiter(options).get((err, info) => {
|
||||||
return Promise.resolve();
|
if (err) {
|
||||||
}
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Short-term limit
|
@bindThis
|
||||||
const min = new Promise<void>((ok, reject) => {
|
public async limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string, factor = 1): Promise<RateLimitInfo | null> {
|
||||||
const minIntervalLimiter = new Limiter({
|
if (this.disabled) {
|
||||||
id: `${actor}:${limitation.key}:min`,
|
return null;
|
||||||
duration: limitation.minInterval! * factor,
|
}
|
||||||
max: 1,
|
|
||||||
db: this.redisClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
minIntervalLimiter.get((err, info) => {
|
// Short-term limit
|
||||||
if (err) {
|
if (limitation.minInterval != null) {
|
||||||
return reject({ code: 'ERR', info });
|
const info = await this.checkLimiter({
|
||||||
}
|
id: `${actor}:${limitation.key}:min`,
|
||||||
|
duration: limitation.minInterval * factor,
|
||||||
this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
|
max: 1,
|
||||||
|
db: this.redisClient,
|
||||||
if (info.remaining === 0) {
|
|
||||||
return reject({ code: 'BRIEF_REQUEST_INTERVAL', info });
|
|
||||||
} else {
|
|
||||||
if (hasLongTermLimit) {
|
|
||||||
return max.then(ok, reject);
|
|
||||||
} else {
|
|
||||||
return ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Long term limit
|
this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
|
||||||
const max = new Promise<void>((ok, reject) => {
|
|
||||||
const limiter = new Limiter({
|
|
||||||
id: `${actor}:${limitation.key}`,
|
|
||||||
duration: limitation.duration! * factor,
|
|
||||||
max: limitation.max! / factor,
|
|
||||||
db: this.redisClient,
|
|
||||||
});
|
|
||||||
|
|
||||||
limiter.get((err, info) => {
|
if (info.remaining === 0) {
|
||||||
if (err) {
|
// eslint-disable-next-line no-throw-literal
|
||||||
return reject({ code: 'ERR', info });
|
return { code: 'BRIEF_REQUEST_INTERVAL', info };
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
|
|
||||||
|
|
||||||
if (info.remaining === 0) {
|
|
||||||
return reject({ code: 'RATE_LIMIT_EXCEEDED', info });
|
|
||||||
} else {
|
|
||||||
return ok();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasShortTermLimit = typeof limitation.minInterval === 'number';
|
|
||||||
|
|
||||||
const hasLongTermLimit =
|
|
||||||
typeof limitation.duration === 'number' &&
|
|
||||||
typeof limitation.max === 'number';
|
|
||||||
|
|
||||||
if (hasShortTermLimit) {
|
|
||||||
return min;
|
|
||||||
} else if (hasLongTermLimit) {
|
|
||||||
return max;
|
|
||||||
} else {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Long term limit
|
||||||
|
if (limitation.duration != null && limitation.max != null) {
|
||||||
|
const info = await this.checkLimiter({
|
||||||
|
id: `${actor}:${limitation.key}`,
|
||||||
|
duration: limitation.duration,
|
||||||
|
max: limitation.max / factor,
|
||||||
|
db: this.redisClient,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
|
||||||
|
|
||||||
|
if (info.remaining === 0) {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
return { code: 'RATE_LIMIT_EXCEEDED', info };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,10 +73,9 @@ export class SigninApiService {
|
||||||
return { error };
|
return { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
// not more than 1 attempt per second and not more than 10 attempts per hour
|
// not more than 1 attempt per second and not more than 10 attempts per hour
|
||||||
await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
|
const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
|
||||||
} catch (err) {
|
if (rateLimit != null) {
|
||||||
reply.code(429);
|
reply.code(429);
|
||||||
return {
|
return {
|
||||||
error: {
|
error: {
|
||||||
|
|
|
@ -258,7 +258,7 @@ import { langs } from '@@/js/config.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
@ -270,16 +270,6 @@ const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||||
const dataSaver = ref(defaultStore.state.dataSaver);
|
const dataSaver = ref(defaultStore.state.dataSaver);
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
|
const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
|
||||||
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
|
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
|
||||||
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
|
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
|
||||||
|
@ -369,7 +359,7 @@ watch([
|
||||||
confirmWhenRevealingSensitiveMedia,
|
confirmWhenRevealingSensitiveMedia,
|
||||||
contextMenu,
|
contextMenu,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
|
||||||
|
|
|
@ -54,7 +54,7 @@ import MkContainer from '@/components/MkContainer.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { navbarItemDef } from '@/navbar.js';
|
import { navbarItemDef } from '@/navbar.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
|
||||||
|
@ -67,16 +67,6 @@ const items = ref(defaultStore.state.menu.map(x => ({
|
||||||
|
|
||||||
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addItem() {
|
async function addItem() {
|
||||||
const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
|
const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
|
||||||
const { canceled, result: item } = await os.select({
|
const { canceled, result: item } = await os.select({
|
||||||
|
@ -100,7 +90,7 @@ function removeItem(index: number) {
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
defaultStore.set('menu', items.value.map(x => x.type));
|
defaultStore.set('menu', items.value.map(x => x.type));
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
|
@ -111,7 +101,7 @@ function reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(menuDisplay, async () => {
|
watch(menuDisplay, async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -98,7 +98,7 @@ import { defaultStore } from '@/store.js';
|
||||||
import { signout, signinRequired } from '@/account.js';
|
import { signout, signinRequired } from '@/account.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
|
|
||||||
const $i = signinRequired();
|
const $i = signinRequired();
|
||||||
|
@ -132,16 +132,6 @@ async function deleteAccount() {
|
||||||
await signout();
|
await signout();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateRepliesAll(withReplies: boolean) {
|
async function updateRepliesAll(withReplies: boolean) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
@ -155,7 +145,7 @@ async function updateRepliesAll(withReplies: boolean) {
|
||||||
watch([
|
watch([
|
||||||
enableCondensedLineForAcct,
|
enableCondensedLineForAcct,
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -88,19 +88,9 @@ import { uniqueBy } from '@/scripts/array.js';
|
||||||
import { fetchThemes, getThemes } from '@/theme-store.js';
|
import { fetchThemes, getThemes } from '@/theme-store.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { unisonReload } from '@/scripts/unison-reload.js';
|
import { reloadAsk } from '@/scripts/reload-ask.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
async function reloadAsk() {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'info',
|
|
||||||
text: i18n.ts.reloadToApplySetting,
|
|
||||||
});
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
unisonReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
const installedThemes = ref(getThemes());
|
const installedThemes = ref(getThemes());
|
||||||
const builtinThemes = getBuiltinThemesRef();
|
const builtinThemes = getBuiltinThemesRef();
|
||||||
|
|
||||||
|
@ -148,13 +138,13 @@ watch(syncDeviceDarkMode, () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(wallpaper, () => {
|
watch(wallpaper, async () => {
|
||||||
if (wallpaper.value == null) {
|
if (wallpaper.value == null) {
|
||||||
miLocalStorage.removeItem('wallpaper');
|
miLocalStorage.removeItem('wallpaper');
|
||||||
} else {
|
} else {
|
||||||
miLocalStorage.setItem('wallpaper', wallpaper.value);
|
miLocalStorage.setItem('wallpaper', wallpaper.value);
|
||||||
}
|
}
|
||||||
reloadAsk();
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { unisonReload } from '@/scripts/unison-reload.js';
|
||||||
|
|
||||||
|
let isReloadConfirming = false;
|
||||||
|
|
||||||
|
export async function reloadAsk(opts: {
|
||||||
|
unison?: boolean;
|
||||||
|
reason?: string;
|
||||||
|
}) {
|
||||||
|
if (isReloadConfirming) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isReloadConfirming = true;
|
||||||
|
|
||||||
|
const { canceled } = await os.confirm(opts.reason == null ? {
|
||||||
|
type: 'info',
|
||||||
|
text: i18n.ts.reloadConfirm,
|
||||||
|
} : {
|
||||||
|
type: 'info',
|
||||||
|
title: i18n.ts.reloadConfirm,
|
||||||
|
text: opts.reason,
|
||||||
|
}).finally(() => {
|
||||||
|
isReloadConfirming = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
if (opts.unison) {
|
||||||
|
unisonReload();
|
||||||
|
} else {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue