summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-06-02 15:44:00 +0000
committerHazelnoot <acomputerdog@gmail.com>2025-06-02 15:44:00 +0000
commit7bb4f9c9ccffabac203c564b6019e3d8341b9d31 (patch)
treed9423a013235043ddb2b1318252f53cd5a3b0fd4
parentmerge: Reduce overhead and DB error spam when a user changes their reaction (... (diff)
parentuse yes/no buttons for reloadAsk (diff)
downloadsharkey-7bb4f9c9ccffabac203c564b6019e3d8341b9d31.tar.gz
sharkey-7bb4f9c9ccffabac203c564b6019e3d8341b9d31.tar.bz2
sharkey-7bb4f9c9ccffabac203c564b6019e3d8341b9d31.zip
merge: Sync local storage to properties to preference profile (resolves #1075) (!1081)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1081 Closes #1075 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Marie <github@yuugi.dev>
-rw-r--r--packages/frontend/src/components/MkDonation.vue2
-rw-r--r--packages/frontend/src/components/MkPostForm.vue1
-rw-r--r--packages/frontend/src/local-storage.ts10
-rw-r--r--packages/frontend/src/pages/settings/custom-css.vue31
-rw-r--r--packages/frontend/src/pages/settings/preferences.vue82
-rw-r--r--packages/frontend/src/preferences/def.ts28
-rw-r--r--packages/frontend/src/utility/reload-ask.ts17
7 files changed, 115 insertions, 56 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<Keys, string>();
export const miLocalStorage = {
- getItem: (key: Keys): string | null => {
- return window.localStorage.getItem(key);
+ getItem: <T extends string = string>(key: Keys): T | null => {
+ return window.localStorage.getItem(key) as T | null;
},
- setItem: (key: Keys, value: string): void => {
+ setItem: <T extends string = string>(key: Keys, value: T): void => {
window.localStorage.setItem(key, value);
},
removeItem: (key: Keys): void => {
window.localStorage.removeItem(key);
},
- getItemAsJson: (key: Keys): any | undefined => {
+ getItemAsJson: <T = any>(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: <T = any>(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<string>({
+ 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
<SearchMarker :keywords="['font', 'size']">
<MkRadios v-model="fontSize">
<template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template>
- <option :value="null"><span style="font-size: 14px;">Aa</span></option>
+ <option value="0"><span style="font-size: 14px;">Aa</span></option>
<option value="1"><span style="font-size: 15px;">Aa</span></option>
<option value="2"><span style="font-size: 16px;">Aa</span></option>
<option value="3"><span style="font-size: 17px;">Aa</span></option>
@@ -795,7 +795,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<SearchMarker :keywords="['corner', 'radius']">
<MkRadios v-model="cornerRadius">
<template #label><SearchLabel>{{ i18n.ts.cornerRadius }}</SearchLabel></template>
- <option :value="null"><i class="sk-icons sk-shark sk-icons-lg" style="top: 2px;position: relative;"></i> Sharkey</option>
+ <option value="sharkey"><i class="sk-icons sk-shark sk-icons-lg" style="top: 2px;position: relative;"></i> Sharkey</option>
<option value="misskey"><i class="sk-icons sk-misskey sk-icons-lg" style="top: 2px;position: relative;"></i> Misskey</option>
</MkRadios>
</SearchMarker>
@@ -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<string>({
+ 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<boolean>({
+ 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;
diff --git a/packages/frontend/src/utility/reload-ask.ts b/packages/frontend/src/utility/reload-ask.ts
index 7c7ea113d4..f49de80231 100644
--- a/packages/frontend/src/utility/reload-ask.ts
+++ b/packages/frontend/src/utility/reload-ask.ts
@@ -12,6 +12,10 @@ let isReloadConfirming = false;
export async function reloadAsk(opts: {
unison?: boolean;
reason?: string;
+ type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
+ title?: string;
+ okText?: string;
+ cancelText?: string;
}) {
if (isReloadConfirming) {
return;
@@ -19,13 +23,12 @@ export async function reloadAsk(opts: {
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,
+ const { canceled } = await os.confirm({
+ type: opts.type ?? 'question',
+ title: opts.title ?? i18n.ts.reloadConfirm,
+ text: opts.reason ?? undefined,
+ okText: opts.okText ?? i18n.ts.yes,
+ cancelText: opts.cancelText ?? i18n.ts.no,
}).finally(() => {
isReloadConfirming = false;
});