From 3d3846ec8538cc205a851e8ae090315019afffe1 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 1 Jun 2025 13:15:12 -0400 Subject: [PATCH] synchronize localStorage properties to preference profile --- .../frontend/src/components/MkDonation.vue | 2 + .../frontend/src/components/MkPostForm.vue | 1 + packages/frontend/src/local-storage.ts | 10 +-- .../src/pages/settings/custom-css.vue | 31 +++---- .../src/pages/settings/preferences.vue | 82 ++++++++++++------- packages/frontend/src/preferences/def.ts | 28 +++++++ 6 files changed, 105 insertions(+), 49 deletions(-) diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue index 43d2002204..dfdfc0a871 100644 --- a/packages/frontend/src/components/MkDonation.vue +++ b/packages/frontend/src/components/MkDonation.vue @@ -53,6 +53,7 @@ import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { miLocalStorage } from '@/local-storage.js'; import { instance } from '@/instance.js'; +import { prefer } from '@/preferences.js'; const emit = defineEmits<{ (ev: 'closed'): void; @@ -66,6 +67,7 @@ function close() { } function neverShow() { + prefer.commit('neverShowDonationInfo', 'true'); miLocalStorage.setItem('neverShowDonationInfo', 'true'); close(); } diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 000ccf50bf..c1d78301de 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -557,6 +557,7 @@ async function toggleLocalOnly() { if (confirm.result === 'no') return; if (confirm.result === 'neverShow') { + prefer.commit('neverShowLocalOnlyInfo', 'true'); miLocalStorage.setItem('neverShowLocalOnlyInfo', 'true'); } } diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index 5c795b1f9d..f1d660a45b 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -48,23 +48,23 @@ export type Keys = ( //const safeSessionStorage = new Map(); export const miLocalStorage = { - getItem: (key: Keys): string | null => { - return window.localStorage.getItem(key); + getItem: (key: Keys): T | null => { + return window.localStorage.getItem(key) as T | null; }, - setItem: (key: Keys, value: string): void => { + setItem: (key: Keys, value: T): void => { window.localStorage.setItem(key, value); }, removeItem: (key: Keys): void => { window.localStorage.removeItem(key); }, - getItemAsJson: (key: Keys): any | undefined => { + getItemAsJson: (key: Keys): T | undefined => { const item = miLocalStorage.getItem(key); if (item === null) { return undefined; } return JSON.parse(item); }, - setItemAsJson: (key: Keys, value: any): void => { + setItemAsJson: (key: Keys, value: T): void => { miLocalStorage.setItem(key, JSON.stringify(value)); }, }; diff --git a/packages/frontend/src/pages/settings/custom-css.vue b/packages/frontend/src/pages/settings/custom-css.vue index 9b0e04860e..07b16d5c45 100644 --- a/packages/frontend/src/pages/settings/custom-css.vue +++ b/packages/frontend/src/pages/settings/custom-css.vue @@ -22,23 +22,24 @@ import { unisonReload } from '@/utility/unison-reload.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { miLocalStorage } from '@/local-storage.js'; +import { prefer } from '@/preferences.js'; +import { reloadAsk } from '@/utility/reload-ask'; -const localCustomCss = ref(miLocalStorage.getItem('customCss') ?? ''); +const customCssModel = prefer.model('customCss'); +const localCustomCss = computed({ + get() { + return customCssModel.value ?? miLocalStorage.getItem('customCss') ?? ''; + }, + set(newCustomCss) { + customCssModel.value = newCustomCss; + if (newCustomCss) { + miLocalStorage.setItem('customCss', newCustomCss); + } else { + miLocalStorage.removeItem('customCss'); + } -async function apply() { - miLocalStorage.setItem('customCss', localCustomCss.value); - - const { canceled } = await os.confirm({ - type: 'info', - text: i18n.ts.reloadToApplySetting, - }); - if (canceled) return; - - unisonReload(); -} - -watch(localCustomCss, async () => { - await apply(); + reloadAsk(true); + }, }); const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 8b89325db7..4e7568561b 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -683,7 +683,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -795,7 +795,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -974,7 +974,6 @@ import { worksOnInstance } from '@/utility/favicon-dot.js'; const $i = ensureSignin(); -const lang = ref(miLocalStorage.getItem('lang')); const dataSaver = ref(prefer.s.dataSaver); const overridedDeviceKind = prefer.model('overridedDeviceKind'); @@ -1034,9 +1033,6 @@ const contextMenu = prefer.model('contextMenu'); const menuStyle = prefer.model('menuStyle'); const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable'); -const fontSize = ref(miLocalStorage.getItem('fontSize')); -const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); - // Sharkey options const collapseNotesRepliedTo = prefer.model('collapseNotesRepliedTo'); const showTickerOnReplies = prefer.model('showTickerOnReplies'); @@ -1052,7 +1048,6 @@ const notificationClickable = prefer.model('notificationClickable'); const warnExternalUrl = prefer.model('warnExternalUrl'); const showVisibilitySelectorOnBoost = prefer.model('showVisibilitySelectorOnBoost'); const visibilityOnBoost = prefer.model('visibilityOnBoost'); -const cornerRadius = ref(miLocalStorage.getItem('cornerRadius')); const oneko = prefer.model('oneko'); const numberOfReplies = prefer.model('numberOfReplies'); const autoloadConversation = prefer.model('autoloadConversation'); @@ -1061,34 +1056,62 @@ const useCustomSearchEngine = computed(() => !Object.keys(searchEngineMap).inclu const defaultCW = ref($i.defaultCW); const defaultCWPriority = ref($i.defaultCWPriority); -watch(lang, () => { - miLocalStorage.setItem('lang', lang.value as string); - miLocalStorage.removeItem('locale'); - miLocalStorage.removeItem('localeVersion'); +const langModel = prefer.model('lang'); +const lang = computed({ + get() { + return langModel.value ?? miLocalStorage.getItem('lang') ?? 'en-US'; + }, + set(newLang) { + langModel.value = newLang; + miLocalStorage.setItem('lang', newLang); + miLocalStorage.removeItem('locale'); + miLocalStorage.removeItem('localeVersion'); + }, }); -watch(fontSize, () => { - if (fontSize.value == null) { - miLocalStorage.removeItem('fontSize'); - } else { - miLocalStorage.setItem('fontSize', fontSize.value); - } +const fontSizeModel = prefer.model('fontSize'); +const fontSize = computed<'0' | '1' | '2' | '3'>({ + get() { + return fontSizeModel.value ?? miLocalStorage.getItem('fontSize') ?? '0'; + }, + set(newFontSize) { + fontSizeModel.value = newFontSize; + if (newFontSize !== '0') { + miLocalStorage.setItem('fontSize', newFontSize); + } else { + miLocalStorage.removeItem('fontSize'); + } + }, }); -watch(useSystemFont, () => { - if (useSystemFont.value) { - miLocalStorage.setItem('useSystemFont', 't'); - } else { - miLocalStorage.removeItem('useSystemFont'); - } +const useSystemFontModel = prefer.model('useSystemFont'); +const useSystemFont = computed({ + get() { + return useSystemFontModel.value ?? (miLocalStorage.getItem('useSystemFont') != null); + }, + set(newUseSystemFont) { + useSystemFontModel.value = newUseSystemFont; + if (newUseSystemFont) { + miLocalStorage.setItem('useSystemFont', 't'); + } else { + miLocalStorage.removeItem('useSystemFont'); + } + }, }); -watch(cornerRadius, () => { - if (cornerRadius.value == null) { - miLocalStorage.removeItem('cornerRadius'); - } else { - miLocalStorage.setItem('cornerRadius', cornerRadius.value); - } +const cornerRadiusModel = prefer.model('cornerRadius'); +const cornerRadius = computed<'misskey' | 'sharkey'>({ + get() { + return cornerRadiusModel.value ?? miLocalStorage.getItem('cornerRadius') ?? 'sharkey'; + }, + set(newCornerRadius) { + cornerRadiusModel.value = newCornerRadius; + if (newCornerRadius === 'sharkey') { + miLocalStorage.removeItem('cornerRadius'); + } else { + miLocalStorage.setItem('cornerRadius', newCornerRadius); + } + }, }); watch([ @@ -1117,6 +1140,7 @@ watch([ contextMenu, fontSize, useSystemFont, + cornerRadius, makeEveryTextElementsSelectable, noteDesign, ], async () => { diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index a4d52c8acb..22059041d1 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -477,4 +477,32 @@ export const PREF_DEF = { default: true, }, //#endregion + + //#region hybrid options + // These exist in preferences, but may have a legacy value in local storage. + // Some parts of the system may still reference the legacy storage so both need to stay in sync! + // Null means "fall back to existing value from localStorage" + // For all of these preferences, "null" means fall back to existing value in localStorage. + fontSize: { + default: null as null | '0' | '1' | '2' | '3', + }, + useSystemFont: { + default: null as null | boolean, + }, + cornerRadius: { + default: null as null | 'misskey' | 'sharkey', + }, + lang: { + default: null as null | string, + }, + customCss: { + default: null as null | string, + }, + neverShowDonationInfo: { + default: null as null | 'true', + }, + neverShowLocalOnlyInfo: { + default: null as null | 'true', + }, + //#endregion } satisfies PreferencesDefinition;