diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts index 12b84706bb..4b1ff1c3f3 100644 --- a/packages/frontend/src/preferences/manager.ts +++ b/packages/frontend/src/preferences/manager.ts @@ -75,7 +75,7 @@ export type PreferencesProfile = { id: string; version: string; type: 'main'; - modifiedAt: number; + modifiedAt: number; // 仕様が若干直感的ではない(syncされた値が降ってきたときは更新されないなど)ため、一応残してはいるが積極的な利用はしない方が無難 name: string; preferences: { [K in keyof PREF]: PrefRecord[]; @@ -181,6 +181,39 @@ function normalizePreferences(preferences: PossiblyNonNormalizedPreferencesProfi return data as PreferencesProfile['preferences']; } +// 各recordについて、modifiedAtが大きい方を採用する +export function mergeProfiles(a: PreferencesProfile, b: PreferencesProfile): PreferencesProfile { + const merged = { + ...a, + modifiedAt: Math.max(a.modifiedAt, b.modifiedAt), + preferences: {}, + } as PreferencesProfile; + + for (const _key in PREF_DEF) { + const key = _key as keyof PREF; + const aRecords = a.preferences[key]; + const bRecords = b.preferences[key]; + + const mergedRecords = [...aRecords]; + + for (const bRecord of bRecords) { + const existingIndex = mergedRecords.findIndex(([scope]) => isSameScope(scope, bRecord[0])); + if (existingIndex === -1) { + mergedRecords.push(bRecord); + } else { + const aRecord = mergedRecords[existingIndex]; + if ((bRecord[2].modifiedAt ?? 0) > (aRecord[2].modifiedAt ?? 0)) { + mergedRecords[existingIndex] = bRecord; + } + } + } + + (merged.preferences[key] as PrefRecord[]) = mergedRecords; + } + + return merged; +} + // eslint-disable-next-line @typescript-eslint/no-empty-object-type type PreferencesManagerEvents = { }; diff --git a/packages/frontend/src/preferences/utility.ts b/packages/frontend/src/preferences/utility.ts index c62e0cf13d..d0791271f0 100644 --- a/packages/frontend/src/preferences/utility.ts +++ b/packages/frontend/src/preferences/utility.ts @@ -4,6 +4,7 @@ */ import { ref, watch } from 'vue'; +import { mergeProfiles } from './manager.js'; import type { PreferencesProfile } from './manager.js'; import type { MenuItem } from '@/types/menu.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; @@ -180,14 +181,24 @@ export async function cloudBackup() { throw new Error('cannot auto backup for this profile'); } - // TODO: 同期有効時、既に新しいバージョンがバックアップされている場合は上書きしないようにする + let currentProfile = prefer.profile; - if (_DEV_) console.log('cloud backup', prefer.profile); + if (_DEV_) console.log('cloud backup', currentProfile); + + const backupedProfile = await misskeyApi('i/registry/get', { + scope: ['client', 'preferences', 'backups'], + key: prefer.profile.name, + }) as PreferencesProfile | null; + + // 古い設定で新しいバックアップを上書きしないようにマージ + if (backupedProfile != null) { + currentProfile = mergeProfiles(currentProfile, backupedProfile); + } await misskeyApi('i/registry/set', { scope: ['client', 'preferences', 'backups'], key: prefer.profile.name, - value: prefer.profile, + value: currentProfile, }); }