From 1005d17313dca44cb42bb826f1c82028304d2cb5 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 3 Nov 2024 01:32:37 +0900 Subject: [PATCH 1/4] =?UTF-8?q?enhance:=20=E3=82=AF=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=82=A2=E3=83=B3=E3=83=88=E8=A8=AD=E5=AE=9A=E3=81=AE=E5=88=9D?= =?UTF-8?q?=E6=9C=9F=E5=80=A4=E3=82=92=E5=A4=89=E6=9B=B4=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=EF=BC=88=E7=B0=A1=E6=98=93?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 25 ++ locales/ja-JP.yml | 6 + .../1730552981368-ClientSettingOverrides.js | 16 ++ .../src/core/entities/MetaEntityService.ts | 1 + packages/backend/src/models/Meta.ts | 6 + .../backend/src/models/json-schema/meta.ts | 4 + .../src/server/api/endpoints/admin/meta.ts | 5 + .../server/api/endpoints/admin/update-meta.ts | 5 + packages/frontend/src/boot/common.ts | 23 +- packages/frontend/src/instance.ts | 21 +- .../pages/admin/client-setting-overrides.vue | 261 ++++++++++++++++++ .../frontend/src/pages/admin/settings.vue | 3 + packages/frontend/src/pizzax.ts | 41 ++- packages/frontend/src/router/definition.ts | 4 + packages/frontend/src/scripts/merge.ts | 8 +- packages/frontend/src/scripts/reload-ask.ts | 2 +- .../frontend/src/scripts/store-overrides.ts | 33 +++ packages/frontend/src/store.ts | 6 +- packages/misskey-js/src/autogen/types.ts | 3 + 19 files changed, 440 insertions(+), 33 deletions(-) create mode 100644 packages/backend/migration/1730552981368-ClientSettingOverrides.js create mode 100644 packages/frontend/src/pages/admin/client-setting-overrides.vue create mode 100644 packages/frontend/src/scripts/store-overrides.ts diff --git a/locales/index.d.ts b/locales/index.d.ts index 440f24ac84..66fe6615ff 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5218,6 +5218,31 @@ export interface Locale extends ILocale { * 利用可能なロール */ "availableRoles": string; + /** + * クライアント設定の初期値 + */ + "clientSettingOverrides": string; + /** + * 全ユーザーに対するクライアント設定の初期値を変更できます。 + * 知識のない方が変更すると全ユーザーがクライアントにアクセスできなくなる可能性があります。 + */ + "clientSettingOverridesWarn": string; + /** + * オーバーライドする + */ + "enableOverride": string; + /** + * オーバーライドする値 + */ + "overrideValue": string; + /** + * スイッチをオンにするとtrueとなります。 + */ + "onToTrue": string; + /** + * リセット + */ + "reset": string; "_accountSettings": { /** * コンテンツの表示にログインを必須にする diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5d8e1a5e72..10923ed414 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1300,6 +1300,12 @@ thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者により、表示に lockdown: "ロックダウン" pleaseSelectAccount: "アカウントを選択してください" availableRoles: "利用可能なロール" +clientSettingOverrides: "クライアント設定の初期値" +clientSettingOverridesWarn: "全ユーザーに対するクライアント設定の初期値を変更できます。\n知識のない方が変更すると全ユーザーがクライアントにアクセスできなくなる可能性があります。" +enableOverride: "オーバーライドする" +overrideValue: "オーバーライドする値" +onToTrue: "スイッチをオンにするとtrueとなります。" +reset: "リセット" _accountSettings: requireSigninToViewContents: "コンテンツの表示にログインを必須にする" diff --git a/packages/backend/migration/1730552981368-ClientSettingOverrides.js b/packages/backend/migration/1730552981368-ClientSettingOverrides.js new file mode 100644 index 0000000000..456d8305d5 --- /dev/null +++ b/packages/backend/migration/1730552981368-ClientSettingOverrides.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ClientSettingOverrides1730552981368 { + name = 'ClientSettingOverrides1730552981368' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "defaultClientSettingOverrides" character varying(8192)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "defaultClientSettingOverrides"`); + } +} diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 409dca3426..39db1ccf6c 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -151,6 +151,7 @@ export class MetaEntityService { const packDetailed: Packed<'MetaDetailed'> = { ...packed, + defaultClientSettingOverrides: instance.defaultClientSettingOverrides, cacheRemoteFiles: instance.cacheRemoteFiles, cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles, requireSetup: !await this.instanceActorService.realLocalUsersPresent(), diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index ad5e31ad6f..f61229cb01 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -409,6 +409,12 @@ export class MiMeta { }) public defaultDarkTheme: string | null; + @Column('varchar', { + length: 8192, + nullable: true, + }) + public defaultClientSettingOverrides: string | null; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index e3fd63464a..49b8f97272 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -315,6 +315,10 @@ export const packedMetaDetailedOnlySchema = { }, }, }, + defaultClientSettingOverrides: { + type: 'string', + optional: false, nullable: true, + }, proxyAccountName: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 64e3cc33bd..beae8473aa 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -420,6 +420,10 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + defaultClientSettingOverrides: { + type: 'string', + optional: false, nullable: true, + }, description: { type: 'string', optional: false, nullable: true, @@ -585,6 +589,7 @@ export default class extends Endpoint { // eslint- logoImageUrl: instance.logoImageUrl, defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, + defaultClientSettingOverrides: instance.defaultClientSettingOverrides, enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, translatorAvailable: instance.deeplAuthKey != null, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 38ef0d1de8..16d37a1d68 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -67,6 +67,7 @@ export const paramDef = { description: { type: 'string', nullable: true }, defaultLightTheme: { type: 'string', nullable: true }, defaultDarkTheme: { type: 'string', nullable: true }, + defaultClientSettingOverrides: { type: 'string', nullable: true }, cacheRemoteFiles: { type: 'boolean' }, cacheRemoteSensitiveFiles: { type: 'boolean' }, emailRequiredForSignup: { type: 'boolean' }, @@ -303,6 +304,10 @@ export default class extends Endpoint { // eslint- set.defaultDarkTheme = ps.defaultDarkTheme; } + if (ps.defaultClientSettingOverrides !== undefined) { + set.defaultClientSettingOverrides = ps.defaultClientSettingOverrides; + } + if (ps.cacheRemoteFiles !== undefined) { set.cacheRemoteFiles = ps.cacheRemoteFiles; } diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 90ae49ee59..0f2824a180 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -14,7 +14,7 @@ import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js'; import { updateI18n, i18n } from '@/i18n.js'; import { $i, refreshAccount, login } from '@/account.js'; import { defaultStore, ColdDeviceStorage } from '@/store.js'; -import { fetchInstance, instance } from '@/instance.js'; +import { initInstance, instance } from '@/instance.js'; import { deviceKind } from '@/scripts/device-kind.js'; import { reloadChannel } from '@/scripts/unison-reload.js'; import { getUrlWithoutLoginId } from '@/scripts/login-id.js'; @@ -115,15 +115,12 @@ export async function common(createVue: () => App) { const html = document.documentElement; html.setAttribute('lang', lang); //#endregion - + + await initInstance(); await defaultStore.ready; await deckStore.ready; - const fetchInstanceMetaPromise = fetchInstance(); - - fetchInstanceMetaPromise.then(() => { - miLocalStorage.setItem('v', instance.version); - }); + miLocalStorage.setItem('v', instance.version); //#region loginId const params = new URLSearchParams(location.search); @@ -177,13 +174,11 @@ export async function common(createVue: () => App) { }); //#endregion - fetchInstanceMetaPromise.then(() => { - if (defaultStore.state.themeInitial) { - if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON.parse(instance.defaultLightTheme)); - if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON.parse(instance.defaultDarkTheme)); - defaultStore.set('themeInitial', false); - } - }); + if (defaultStore.state.themeInitial) { + if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON.parse(instance.defaultLightTheme)); + if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON.parse(instance.defaultDarkTheme)); + defaultStore.set('themeInitial', false); + } watch(defaultStore.reactiveState.useBlurEffectForModal, v => { document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none'); diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index 71cb42b30c..9717c08f4d 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -16,7 +16,7 @@ const providedMetaEl = document.getElementById('misskey_meta'); let cachedMeta = miLocalStorage.getItem('instance') ? JSON.parse(miLocalStorage.getItem('instance')!) : null; let cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0; -const providedMeta = providedMetaEl && providedMetaEl.textContent ? JSON.parse(providedMetaEl.textContent) : null; +const providedMeta: Misskey.entities.MetaDetailed | null = providedMetaEl && providedMetaEl.textContent ? JSON.parse(providedMetaEl.textContent) : null; const providedAt = providedMetaEl && providedMetaEl.dataset.generatedAt ? parseInt(providedMetaEl.dataset.generatedAt) : 0; if (providedAt > cachedAt) { miLocalStorage.setItem('instance', JSON.stringify(providedMeta)); @@ -38,6 +38,19 @@ export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFA export const isEnabledUrlPreview = computed(() => instance.enableUrlPreview ?? true); +/** instanceの中身が入っていることを保証する */ +export async function initInstance() { + if (instance == null || Object.keys(instance).length === 0) { + if (providedMeta != null) { + for (const [k, v] of Object.entries(providedMeta)) { + instance[k] = v; + } + } else { + await fetchInstance(true); + } + } +} + export async function fetchInstance(force = false): Promise { if (!force) { const cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0; @@ -60,3 +73,9 @@ export async function fetchInstance(force = false): Promise + + + + + + diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index ea7603a45a..689b7aa165 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -249,6 +249,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.selectAccount }} + + {{ i18n.ts.clientSettingOverrides }} {{ i18n.ts.beta }} @@ -274,6 +276,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue'; import { useForm } from '@/scripts/use-form.js'; import MkFormFooter from '@/components/MkFormFooter.vue'; import MkRadios from '@/components/MkRadios.vue'; +import FormLink from '@/components/form/link.vue'; const meta = await misskeyApi('admin/meta'); diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index ac325e923f..b54203dc9d 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -13,7 +13,7 @@ import { get, set } from '@/scripts/idb-proxy.js'; import { defaultStore } from '@/store.js'; import { useStream } from '@/stream.js'; import { deepClone } from '@/scripts/clone.js'; -import { deepMerge } from '@/scripts/merge.js'; +import { deepMerge, type DeepPartial } from '@/scripts/merge.js'; type StateDef = Record { public readonly def: T; // TODO: これが実装されたらreadonlyにしたい: https://github.com/microsoft/TypeScript/issues/37487 + private readonly defaultState: State; public readonly state: State; public readonly reactiveState: ReactiveState; @@ -60,7 +61,7 @@ export class Storage { return promise; } - constructor(key: string, def: T) { + constructor(key: string, def: T, defaultOverrides?: DeepPartial>) { this.key = key; this.deviceStateKeyName = `pizzax::${key}`; this.deviceAccountStateKeyName = $i ? `pizzax::${key}::${$i.id}` : ''; @@ -69,25 +70,43 @@ export class Storage { this.pizzaxChannel = new BroadcastChannel(`pizzax::${key}`); + this.defaultState = {} as State; this.state = {} as State; this.reactiveState = {} as ReactiveState; for (const [k, v] of Object.entries(def) as [keyof T, T[keyof T]['default']][]) { - this.state[k] = v.default; - this.reactiveState[k] = ref(v.default); + let _defaultState = v.default; + if ( + defaultOverrides != null && + this.isPureObject(defaultOverrides) && + defaultOverrides[k] !== undefined // ←意図的にnullになっている可能性があるためundefined判定 + ) { + if (this.isPureObject(defaultOverrides[k]) && this.isPureObject(v.default)) { + _defaultState = deepMerge(defaultOverrides[k], v.default); + } else if (Array.isArray(defaultOverrides[k]) && Array.isArray(v.default)) { + _defaultState = Array.from(new Set([...defaultOverrides[k], ...v.default])); + } else { + _defaultState = defaultOverrides[k]; + } + if (_DEV_) console.log('defaultState', k, _defaultState); + } + + this.defaultState[k] = _defaultState; + this.state[k] = _defaultState; + this.reactiveState[k] = ref(_defaultState); } this.ready = this.init(); this.loaded = this.ready.then(() => this.load()); } - private isPureObject(value: unknown): value is Record { + private isPureObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } private mergeState(value: X, def: X): X { if (this.isPureObject(value) && this.isPureObject(def)) { - const merged = deepMerge(value, def); + const merged = deepMerge(value as DeepPartial, def); if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged); @@ -105,14 +124,14 @@ export class Storage { for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) { - this.reactiveState[k].value = this.state[k] = this.mergeState(deviceState[k], v.default); + this.reactiveState[k].value = this.state[k] = this.mergeState(deviceState[k], this.defaultState[k]); } else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) { - this.reactiveState[k].value = this.state[k] = this.mergeState(registryCache[k], v.default); + this.reactiveState[k].value = this.state[k] = this.mergeState(registryCache[k], this.defaultState[k]); } else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) { - this.reactiveState[k].value = this.state[k] = this.mergeState(deviceAccountState[k], v.default); + this.reactiveState[k].value = this.state[k] = this.mergeState(deviceAccountState[k], this.defaultState[k]); } else { - this.reactiveState[k].value = this.state[k] = v.default; - if (_DEV_) console.log('Use default value', k, v.default); + this.reactiveState[k].value = this.state[k] = this.defaultState[k]; + if (_DEV_) console.log('Use default value', k, this.defaultState[k]); } } diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index e98e0b59b1..ea2cfdc6b3 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -486,6 +486,10 @@ const routes: RouteDef[] = [{ path: '/system-webhook', name: 'system-webhook', component: page(() => import('@/pages/admin/system-webhook.vue')), + }, { + path: '/client-setting-overrides', + name: 'client-setting-overrides', + component: page(() => import('@/pages/admin/client-setting-overrides.vue')), }, { path: '/', component: page(() => import('@/pages/_empty_.vue')), diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts index 9794a300da..004b6d42a4 100644 --- a/packages/frontend/src/scripts/merge.ts +++ b/packages/frontend/src/scripts/merge.ts @@ -7,10 +7,10 @@ import { deepClone } from './clone.js'; import type { Cloneable } from './clone.js'; export type DeepPartial = { - [P in keyof T]?: T[P] extends Record ? DeepPartial : T[P]; + [P in keyof T]?: T[P] extends Record ? DeepPartial : T[P]; }; -function isPureObject(value: unknown): value is Record { +function isPureObject(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } @@ -18,14 +18,14 @@ function isPureObject(value: unknown): value is Record>(value: DeepPartial, def: X): X { +export function deepMerge>(value: DeepPartial, def: X): X { if (isPureObject(value) && isPureObject(def)) { const result = deepClone(value as Cloneable) as X; for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) { result[k] = v; } else if (isPureObject(v) && isPureObject(result[k])) { - const child = deepClone(result[k] as Cloneable) as DeepPartial>; + const child = deepClone(result[k] as Cloneable) as DeepPartial>; result[k] = deepMerge(child, v); } } diff --git a/packages/frontend/src/scripts/reload-ask.ts b/packages/frontend/src/scripts/reload-ask.ts index 733d91b85a..0e82cb06d8 100644 --- a/packages/frontend/src/scripts/reload-ask.ts +++ b/packages/frontend/src/scripts/reload-ask.ts @@ -12,7 +12,7 @@ let isReloadConfirming = false; export async function reloadAsk(opts: { unison?: boolean; reason?: string; -}) { +} = {}) { if (isReloadConfirming) { return; } diff --git a/packages/frontend/src/scripts/store-overrides.ts b/packages/frontend/src/scripts/store-overrides.ts new file mode 100644 index 0000000000..9dcba56e48 --- /dev/null +++ b/packages/frontend/src/scripts/store-overrides.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { initInstance, instance } from '@/instance.js'; + +export async function getDefaultStoreOverrides() { + await initInstance(); + if (instance.defaultClientSettingOverrides != null) { + try { + const clientSettingOverrides = JSON.parse(instance.defaultClientSettingOverrides); + const out = Object.fromEntries(Object.keys(clientSettingOverrides).filter(key => key.startsWith('defaultStore::')).map(key => [key.split('::')[1], clientSettingOverrides[key]])); + return out; + } catch (err) { + return null; + } + } + return null; +} + +export function getColdDeviceStorageOverrides() { + if (instance.defaultClientSettingOverrides != null) { + try { + const clientSettingOverrides = JSON.parse(instance.defaultClientSettingOverrides); + const out = Object.fromEntries(Object.keys(clientSettingOverrides).filter(key => key.startsWith('ColdDeviceStorage::')).map(key => [key.split('::')[1], clientSettingOverrides[key]])); + return out; + } catch (err) { + return null; + } + } + return null; +} diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 911a463636..78328992ab 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -12,6 +12,7 @@ import { miLocalStorage } from './local-storage.js'; import type { SoundType } from '@/scripts/sound.js'; import { Storage } from '@/pizzax.js'; import type { Ast } from '@syuilo/aiscript'; +import { getColdDeviceStorageOverrides, getDefaultStoreOverrides } from '@/scripts/store-overrides.js'; interface PostFormAction { title: string, @@ -502,7 +503,7 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore, }, -})); +}, await getDefaultStoreOverrides() ?? undefined)); // TODO: 他のタブと永続化されたstateを同期 @@ -548,7 +549,8 @@ export class ColdDeviceStorage { // (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある) const value = miLocalStorage.getItem(`${PREFIX}${key}`); if (value == null) { - return ColdDeviceStorage.default[key]; + const override = getColdDeviceStorageOverrides(); + return override != null ? override[key] ?? ColdDeviceStorage.default[key] : ColdDeviceStorage.default[key]; } else { return JSON.parse(value); } diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index a5333d4f93..48da4cec98 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5035,6 +5035,7 @@ export type components = { /** @default true */ miauth?: boolean; }; + defaultClientSettingOverrides: string | null; proxyAccountName: string | null; /** @example false */ requireSetup: boolean; @@ -5186,6 +5187,7 @@ export type operations = { deeplIsPro: boolean; defaultDarkTheme: string | null; defaultLightTheme: string | null; + defaultClientSettingOverrides: string | null; description: string | null; disableRegistration: boolean; impressumUrl: string | null; @@ -9496,6 +9498,7 @@ export type operations = { description?: string | null; defaultLightTheme?: string | null; defaultDarkTheme?: string | null; + defaultClientSettingOverrides?: string | null; cacheRemoteFiles?: boolean; cacheRemoteSensitiveFiles?: boolean; emailRequiredForSignup?: boolean; From c27811b49081b1728c34b071d9ac70e8cb49827e Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 3 Nov 2024 01:40:09 +0900 Subject: [PATCH 2/4] fix --- .../src/pages/admin/client-setting-overrides.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/admin/client-setting-overrides.vue b/packages/frontend/src/pages/admin/client-setting-overrides.vue index 16eab515f9..2a5f389827 100644 --- a/packages/frontend/src/pages/admin/client-setting-overrides.vue +++ b/packages/frontend/src/pages/admin/client-setting-overrides.vue @@ -194,10 +194,13 @@ async function save() { typeSafeObjectEntries(clientSettingOverrides.value) .filter(([key, def]) => ( def.enableOverride && - def.overrideValue !== def.defaultValue && - JSON.stringify(def.overrideValue) !== JSON.stringify(def.defaultValue) + def.overrideValue !== def.defaultValue && ( + (typeof def.defaultValue === 'string' && typeof def.overrideValue === 'string' && def.overrideValue !== def.defaultValue) || + (typeof def.defaultValue === 'object' && typeof def.overrideValue === 'string' && JSON.stringify(def.overrideValue) !== JSON.stringify(def.defaultValue)) || + (typeof def.defaultValue !== 'string' && typeof def.overrideValue === 'string' && def.overrideValue !== JSON.stringify(def.defaultValue)) + ) )) - .map(([key, def]) => [key, def.overrideValue]) + .map(([key, def]) => [key, typeof def.overrideValue === 'string' && typeof def.defaultValue !== 'string' ? JSON.parse(def.overrideValue) : def.overrideValue]) ); let defaultClientSettingOverrides: string | null = JSON.stringify(overrides); From 48e57989eb08b0b2d95fb138b6e6961b689a7ba8 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 3 Nov 2024 01:48:10 +0900 Subject: [PATCH 3/4] fix --- .../frontend/src/pages/admin/client-setting-overrides.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/frontend/src/pages/admin/client-setting-overrides.vue b/packages/frontend/src/pages/admin/client-setting-overrides.vue index 2a5f389827..6a7261b0d7 100644 --- a/packages/frontend/src/pages/admin/client-setting-overrides.vue +++ b/packages/frontend/src/pages/admin/client-setting-overrides.vue @@ -102,6 +102,11 @@ const notConfigurableDefaultStoreSettings = [ 'forceShowAds', 'additionalUnicodeEmojiIndexes', 'themeInitial', + + // 光過敏性対策のためあえて鯖管に設定させない + 'animation', + 'animatedMfm', + 'disableShowingAnimatedImages' ] satisfies (keyof typeof defaultStore.def)[]; const notConfigurableColdDeviceStorageSettings = [ From b6ca1a5e90122ae76be75ffa6c7d7f03fd5809ef Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:09:14 +0900 Subject: [PATCH 4/4] fix --- .../frontend/src/pages/admin/client-setting-overrides.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/admin/client-setting-overrides.vue b/packages/frontend/src/pages/admin/client-setting-overrides.vue index 6a7261b0d7..13e5fd8286 100644 --- a/packages/frontend/src/pages/admin/client-setting-overrides.vue +++ b/packages/frontend/src/pages/admin/client-setting-overrides.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -85,6 +85,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { reloadAsk } from '@/scripts/reload-ask.js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkTextarea from '@/components/MkTextarea.vue'; +import JSON5 from 'json5'; const query = ref(''); @@ -145,6 +146,7 @@ function typeSafeObjectEntries>(obj: T) { } function getClientSettingOverridesUIDefObj(def: unknown): ClientSettingOverridesUIDefObj { + const _def = typeof def === 'object' ? JSON.stringify(def, null, '\t') : def; return { formType: (() => { if (typeof def === 'boolean') { @@ -159,7 +161,7 @@ function getClientSettingOverridesUIDefObj(def: unknown): ClientSettingOverrides })() satisfies ClientSettingOverridesUIDefObj['formType'] as ClientSettingOverridesUIDefObj['formType'], enableOverride: false, defaultValue: def, - overrideValue: def, + overrideValue: _def, }; } @@ -205,7 +207,7 @@ async function save() { (typeof def.defaultValue !== 'string' && typeof def.overrideValue === 'string' && def.overrideValue !== JSON.stringify(def.defaultValue)) ) )) - .map(([key, def]) => [key, typeof def.overrideValue === 'string' && typeof def.defaultValue !== 'string' ? JSON.parse(def.overrideValue) : def.overrideValue]) + .map(([key, def]) => [key, typeof def.overrideValue === 'string' && typeof def.defaultValue !== 'string' ? JSON5.parse(def.overrideValue) : def.overrideValue]) ); let defaultClientSettingOverrides: string | null = JSON.stringify(overrides);