diff --git a/src/App.vue b/src/App.vue index 79fa63a46..175ba1dbf 100644 --- a/src/App.vue +++ b/src/App.vue @@ -92,7 +92,7 @@ watch(userEmailConfirm, (userEmailConfirm) => { router.push({name: 'user.login'}) }, { immediate: true }) -setLanguage() +setLanguage(authStore.settings.language) useColorScheme() diff --git a/src/components/quick-actions/quick-actions.vue b/src/components/quick-actions/quick-actions.vue index f5606a181..a6a5d47ce 100644 --- a/src/components/quick-actions/quick-actions.vue +++ b/src/components/quick-actions/quick-actions.vue @@ -71,10 +71,10 @@ import {useBaseStore} from '@/stores/base' import {useProjectStore} from '@/stores/projects' import {useLabelStore} from '@/stores/labels' import {useTaskStore} from '@/stores/tasks' +import {useAuthStore} from '@/stores/auth' import {getHistory} from '@/modules/projectHistory' import {parseTaskText, PrefixMode, PREFIXES} from '@/modules/parseTaskText' -import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode' import {success} from '@/message' import type {ITeam} from '@/modelTypes/ITeam' @@ -88,6 +88,7 @@ const baseStore = useBaseStore() const projectStore = useProjectStore() const labelStore = useLabelStore() const taskStore = useTaskStore() +const authStore = useAuthStore() type DoAction = { type: ACTION_TYPE } & Type @@ -242,7 +243,7 @@ const hintText = computed(() => { } } const prefixes = - PREFIXES[getQuickAddMagicMode()] ?? PREFIXES[PrefixMode.Default] + PREFIXES[authStore.settings.frontendSettings.quickAddMagicMode] ?? PREFIXES[PrefixMode.Default] return t('quickActions.hint', prefixes) }) @@ -255,7 +256,7 @@ const availableCmds = computed(() => { return cmds }) -const parsedQuery = computed(() => parseTaskText(query.value, getQuickAddMagicMode())) +const parsedQuery = computed(() => parseTaskText(query.value, authStore.settings.frontendSettings.quickAddMagicMode)) const searchMode = computed(() => { if (query.value === '') { diff --git a/src/components/tasks/add-task.vue b/src/components/tasks/add-task.vue index 4c18f10b2..d377b20e8 100644 --- a/src/components/tasks/add-task.vue +++ b/src/components/tasks/add-task.vue @@ -116,12 +116,12 @@ async function addTask() { // This allows us to find the tasks with the title they had before being parsed // by quick add magic. const createdTasks: { [key: ITask['title']]: ITask } = {} - const tasksToCreate = parseSubtasksViaIndention(newTaskTitle.value) + const tasksToCreate = parseSubtasksViaIndention(newTaskTitle.value, authStore.settings.frontendSettings.quickAddMagicMode) // We ensure all labels exist prior to passing them down to the create task method // In the store it will only ever see one task at a time so there's no way to reliably // check if a new label was created before (because everything happens async). - const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title) ?? []) + const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title, authStore.settings.frontendSettings.quickAddMagicMode) ?? []) await taskStore.ensureLabelsExist(allLabels.flat()) const newTasks = tasksToCreate.map(async ({title, project}) => { diff --git a/src/components/tasks/partials/quick-add-magic.vue b/src/components/tasks/partials/quick-add-magic.vue index 39582e0c3..30818a0ff 100644 --- a/src/components/tasks/partials/quick-add-magic.vue +++ b/src/components/tasks/partials/quick-add-magic.vue @@ -99,11 +99,13 @@ import {ref, computed} from 'vue' import BaseButton from '@/components/base/BaseButton.vue' -import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode' import {PREFIXES} from '@/modules/parseTaskText' +import {useAuthStore} from '@/stores/auth' + +const authStore = useAuthStore() const visible = ref(false) -const mode = ref(getQuickAddMagicMode()) +const mode = computed(() => authStore.settings.frontendSettings.quickAddMagicMode) defineProps<{ highlightHintIcon: boolean, diff --git a/src/composables/useColorScheme.ts b/src/composables/useColorScheme.ts index e15948dd3..7b5f58aa1 100644 --- a/src/composables/useColorScheme.ts +++ b/src/composables/useColorScheme.ts @@ -1,8 +1,7 @@ import {computed, watch, readonly} from 'vue' -import {useStorage, createSharedComposable, usePreferredColorScheme, tryOnMounted} from '@vueuse/core' +import {createSharedComposable, usePreferredColorScheme, tryOnMounted} from '@vueuse/core' import type {BasicColorSchema} from '@vueuse/core' - -const STORAGE_KEY = 'color-scheme' +import {useAuthStore} from '@/stores/auth' const DEFAULT_COLOR_SCHEME_SETTING: BasicColorSchema = 'light' @@ -17,7 +16,8 @@ const CLASS_LIGHT = 'light' // - value is synced via `createSharedComposable` // https://github.com/vueuse/vueuse/blob/main/packages/core/useDark/index.ts export const useColorScheme = createSharedComposable(() => { - const store = useStorage(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING) + const authStore = useAuthStore() + const store = computed(() => authStore.settings.frontendSettings.colorSchema) const preferredColorScheme = usePreferredColorScheme() diff --git a/src/helpers/parseSubtasksViaIndention.test.ts b/src/helpers/parseSubtasksViaIndention.test.ts index d50c8bb2a..55017f039 100644 --- a/src/helpers/parseSubtasksViaIndention.test.ts +++ b/src/helpers/parseSubtasksViaIndention.test.ts @@ -1,5 +1,6 @@ -import {describe, it, expect} from 'vitest' +import {describe, expect, it} from 'vitest' import {parseSubtasksViaIndention} from '@/helpers/parseSubtasksViaIndention' +import {PrefixMode} from '@/modules/parseTaskText' describe('Parse Subtasks via Relation', () => { it('Should not return a parent for a single task', () => { @@ -10,7 +11,7 @@ describe('Parse Subtasks via Relation', () => { }) it('Should not return a parent for multiple tasks without indention', () => { const tasks = parseSubtasksViaIndention(`task one -task two`) +task two`, PrefixMode.Default) expect(tasks).to.have.length(2) expect(tasks[0].parent).toBeNull() @@ -18,7 +19,7 @@ task two`) }) it('Should return a parent for two tasks with indention', () => { const tasks = parseSubtasksViaIndention(`parent task - sub task`) + sub task`, PrefixMode.Default) expect(tasks).to.have.length(2) expect(tasks[0].parent).toBeNull() @@ -29,7 +30,7 @@ task two`) it('Should return a parent for multiple subtasks', () => { const tasks = parseSubtasksViaIndention(`parent task sub task one - sub task two`) + sub task two`, PrefixMode.Default) expect(tasks).to.have.length(3) expect(tasks[0].parent).toBeNull() @@ -42,7 +43,7 @@ task two`) it('Should work with multiple indention levels', () => { const tasks = parseSubtasksViaIndention(`parent task sub task - sub sub task`) + sub sub task`, PrefixMode.Default) expect(tasks).to.have.length(3) expect(tasks[0].parent).toBeNull() @@ -56,7 +57,7 @@ task two`) const tasks = parseSubtasksViaIndention(`parent task sub task sub sub task one - sub sub task two`) + sub sub task two`, PrefixMode.Default) expect(tasks).to.have.length(4) expect(tasks[0].parent).toBeNull() @@ -73,7 +74,7 @@ task two`) sub task sub sub task one sub sub sub task - sub sub task two`) + sub sub task two`, PrefixMode.Default) expect(tasks).to.have.length(5) expect(tasks[0].parent).toBeNull() @@ -90,7 +91,7 @@ task two`) it('Should return a parent for multiple subtasks with special stuff', () => { const tasks = parseSubtasksViaIndention(`* parent task * sub task one - sub task two`) + sub task two`, PrefixMode.Default) expect(tasks).to.have.length(3) expect(tasks[0].parent).toBeNull() @@ -101,7 +102,7 @@ task two`) expect(tasks[2].parent).to.eq('parent task') }) it('Should not break when the first line is indented', () => { - const tasks = parseSubtasksViaIndention(' single task') + const tasks = parseSubtasksViaIndention(' single task', PrefixMode.Default) expect(tasks).to.have.length(1) expect(tasks[0].parent).toBeNull() @@ -110,7 +111,7 @@ task two`) const tasks = parseSubtasksViaIndention( `parent task +list sub task 1 - sub task 2`) + sub task 2`, PrefixMode.Default) expect(tasks).to.have.length(3) expect(tasks[0].project).to.eq('list') diff --git a/src/helpers/parseSubtasksViaIndention.ts b/src/helpers/parseSubtasksViaIndention.ts index 807301d5b..1956e671f 100644 --- a/src/helpers/parseSubtasksViaIndention.ts +++ b/src/helpers/parseSubtasksViaIndention.ts @@ -1,4 +1,4 @@ -import {getProjectFromPrefix} from '@/modules/parseTaskText' +import {getProjectFromPrefix, PrefixMode} from '@/modules/parseTaskText' export interface TaskWithParent { title: string, @@ -16,7 +16,7 @@ const spaceRegex = /^ */ * @param taskTitles should be multiple lines of task tiles with indention to declare their parent/subtask * relation between each other. */ -export function parseSubtasksViaIndention(taskTitles: string): TaskWithParent[] { +export function parseSubtasksViaIndention(taskTitles: string, prefixMode: PrefixMode): TaskWithParent[] { const titles = taskTitles.split(/[\r\n]+/) return titles.map((title, index) => { @@ -26,7 +26,7 @@ export function parseSubtasksViaIndention(taskTitles: string): TaskWithParent[] project: null, } - task.project = getProjectFromPrefix(task.title) + task.project = getProjectFromPrefix(task.title, prefixMode) if (index === 0) { return task @@ -49,7 +49,7 @@ export function parseSubtasksViaIndention(taskTitles: string): TaskWithParent[] task.parent = task.parent.replace(spaceRegex, '') if (task.project === null) { // This allows to specify a project once for the parent task and inherit it to all subtasks - task.project = getProjectFromPrefix(task.parent) + task.project = getProjectFromPrefix(task.parent, prefixMode) } } diff --git a/src/helpers/playPop.ts b/src/helpers/playPop.ts index 3f4e50c35..91b951e7f 100644 --- a/src/helpers/playPop.ts +++ b/src/helpers/playPop.ts @@ -2,15 +2,6 @@ import popSoundFile from '@/assets/audio/pop.mp3' export const playSoundWhenDoneKey = 'playSoundWhenTaskDone' -export function playPop() { - const enabled = localStorage.getItem(playSoundWhenDoneKey) === 'true' - if (!enabled) { - return - } - - playPopSound() -} - export function playPopSound() { const popSound = new Audio(popSoundFile) popSound.play() diff --git a/src/helpers/quickAddMagicMode.ts b/src/helpers/quickAddMagicMode.ts deleted file mode 100644 index 8ee23b270..000000000 --- a/src/helpers/quickAddMagicMode.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {PrefixMode} from '@/modules/parseTaskText' - -const key = 'quickAddMagicMode' - -export const setQuickAddMagicMode = (mode: PrefixMode) => { - localStorage.setItem(key, mode) -} - -export const getQuickAddMagicMode = (): PrefixMode => { - const mode = localStorage.getItem(key) - - switch (mode) { - case null: - case PrefixMode.Default: - return PrefixMode.Default - case PrefixMode.Todoist: - return PrefixMode.Todoist - } - - return PrefixMode.Disabled -} diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 77ebf216a..2d2c1cddb 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -32,7 +32,7 @@ export const i18n = createI18n({ } as Record, }) -export async function setLanguage(lang: SupportedLocale = getCurrentLanguage()): Promise { +export async function setLanguage(lang: SupportedLocale): Promise { if (!lang) { throw new Error() } @@ -53,12 +53,7 @@ export async function setLanguage(lang: SupportedLocale = getCurrentLanguage()): return lang } -export function getCurrentLanguage(): SupportedLocale { - const savedLanguage = localStorage.getItem('language') as SupportedLocale | null - if (savedLanguage !== null) { - return savedLanguage - } - +export function getBrowserLanguage(): SupportedLocale { const browserLanguage = navigator.language const language = Object.keys(SUPPORTED_LOCALES).find(langKey => { @@ -67,8 +62,3 @@ export function getCurrentLanguage(): SupportedLocale { return language || DEFAULT_LANGUAGE } - -export async function saveLanguage(lang: SupportedLocale) { - localStorage.setItem('language', lang) - await setLanguage() -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 3b2389e65..586839b86 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,7 +16,7 @@ import Notifications from '@kyvg/vue3-notification' import './registerServiceWorker' // i18n -import {i18n, setLanguage} from './i18n' +import {getBrowserLanguage, i18n, setLanguage} from './i18n' declare global { interface Window { @@ -56,7 +56,8 @@ import Card from '@/components/misc/card.vue' // We're loading the language before creating the app so that it won't fail to load when the user's // language file is not yet loaded. -setLanguage().then(() => { +const browserLanguage = getBrowserLanguage() +setLanguage(browserLanguage).then(() => { const app = createApp(App) app.use(Notifications) diff --git a/src/modelTypes/IUserSettings.ts b/src/modelTypes/IUserSettings.ts index 8703c48af..9e3b3fb4b 100644 --- a/src/modelTypes/IUserSettings.ts +++ b/src/modelTypes/IUserSettings.ts @@ -1,6 +1,15 @@ import type {IAbstract} from './IAbstract' import type {IProject} from './IProject' +import type {PrefixMode} from '@/modules/parseTaskText' +import type {BasicColorSchema} from '@vueuse/core' +import type {SupportedLocale} from '@/i18n' + +export interface IFrontendSettings { + playSoundWhenDone: boolean + quickAddMagicMode: PrefixMode + colorSchema: BasicColorSchema +} export interface IUserSettings extends IAbstract { name: string @@ -12,5 +21,6 @@ export interface IUserSettings extends IAbstract { defaultProjectId: undefined | IProject['id'] weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6 timezone: string - language: string + language: SupportedLocale + frontendSettings: IFrontendSettings } \ No newline at end of file diff --git a/src/models/userSettings.ts b/src/models/userSettings.ts index e45e2041b..397963dff 100644 --- a/src/models/userSettings.ts +++ b/src/models/userSettings.ts @@ -1,7 +1,8 @@ import AbstractModel from './abstractModel' -import type {IUserSettings} from '@/modelTypes/IUserSettings' -import {getCurrentLanguage} from '@/i18n' +import type {IFrontendSettings, IUserSettings} from '@/modelTypes/IUserSettings' +import {getBrowserLanguage} from '@/i18n' +import {PrefixMode} from '@/modules/parseTaskText' export default class UserSettingsModel extends AbstractModel implements IUserSettings { name = '' @@ -13,7 +14,12 @@ export default class UserSettingsModel extends AbstractModel impl defaultProjectId = undefined weekStart = 0 as IUserSettings['weekStart'] timezone = '' - language = getCurrentLanguage() + language = getBrowserLanguage() + frontendSettings: IFrontendSettings = { + playSoundWhenDone: true, + quickAddMagicMode: PrefixMode.Default, + colorSchema: 'auto', + } constructor(data: Partial = {}) { super() diff --git a/src/modules/parseTaskText.ts b/src/modules/parseTaskText.ts index 0b4044d1c..9f3251bdd 100644 --- a/src/modules/parseTaskText.ts +++ b/src/modules/parseTaskText.ts @@ -1,7 +1,6 @@ import {parseDate} from '../helpers/time/parseDate' import {PRIORITIES} from '@/constants/priorities' import {REPEAT_TYPES, type IRepeatAfter, type IRepeatType} from '@/types/IRepeatAfter' -import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode' const VIKUNJA_PREFIXES: Prefixes = { label: '*', @@ -72,10 +71,10 @@ export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMod return result } - result.labels = getLabelsFromPrefix(text, prefixes.label) ?? [] + result.labels = getLabelsFromPrefix(text, prefixesMode) ?? [] result.text = cleanupItemText(result.text, result.labels, prefixes.label) - result.project = getProjectFromPrefix(result.text, prefixes.project) + result.project = getProjectFromPrefix(result.text, prefixesMode) result.text = result.project !== null ? cleanupItemText(result.text, [result.project], prefixes.project) : result.text result.priority = getPriority(result.text, prefixes.priority) @@ -131,27 +130,21 @@ const getItemsFromPrefix = (text: string, prefix: string): string[] => { return Array.from(new Set(items)) } -export const getProjectFromPrefix = (text: string, projectPrefix: string | null = null): string | null => { - if (projectPrefix === null) { - const prefixes = PREFIXES[getQuickAddMagicMode()] - if (prefixes === undefined) { - return null - } - projectPrefix = prefixes.project +export const getProjectFromPrefix = (text: string, prefixMode: PrefixMode): string | null => { + const projectPrefix = PREFIXES[prefixMode]?.project + if(typeof projectPrefix === 'undefined') { + return null } const projects: string[] = getItemsFromPrefix(text, projectPrefix) return projects.length > 0 ? projects[0] : null } -export const getLabelsFromPrefix = (text: string, projectPrefix: string | null = null): string[] | null => { - if (projectPrefix === null) { - const prefixes = PREFIXES[getQuickAddMagicMode()] - if (prefixes === undefined) { - return null - } - projectPrefix = prefixes.label +export const getLabelsFromPrefix = (text: string, prefixMode: PrefixMode): string[] | null => { + const labelsPrefix = PREFIXES[prefixMode]?.label + if(typeof labelsPrefix === 'undefined') { + return null } - return getItemsFromPrefix(text, projectPrefix) + return getItemsFromPrefix(text, labelsPrefix) } const getPriority = (text: string, prefix: string): number | null => { diff --git a/src/stores/auth.ts b/src/stores/auth.ts index c56adb953..ade6e0908 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -1,10 +1,10 @@ import {computed, readonly, ref} from 'vue' -import {defineStore, acceptHMRUpdate} from 'pinia' +import {acceptHMRUpdate, defineStore} from 'pinia' -import {HTTPFactory, AuthenticatedHTTPFactory} from '@/helpers/fetcher' -import {i18n, getCurrentLanguage, saveLanguage, setLanguage} from '@/i18n' +import {AuthenticatedHTTPFactory, HTTPFactory} from '@/helpers/fetcher' +import {getBrowserLanguage, i18n, setLanguage} from '@/i18n' import {objectToSnakeCase} from '@/helpers/case' -import UserModel, { getAvatarUrl, getDisplayName } from '@/models/user' +import UserModel, {getAvatarUrl, getDisplayName} from '@/models/user' import UserSettingsService from '@/services/userSettings' import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth' import {setModuleLoading} from '@/stores/helper' @@ -16,6 +16,7 @@ import router from '@/router' import {useConfigStore} from '@/stores/config' import UserSettingsModel from '@/models/userSettings' import {MILLISECONDS_A_SECOND} from '@/constants/date' +import {PrefixMode} from '@/modules/parseTaskText' function redirectToProviderIfNothingElseIsEnabled() { const {auth} = useConfigStore() @@ -68,13 +69,13 @@ export const useAuthStore = defineStore('auth', () => { isLoadingGeneralSettings.value = isLoading } - function setUser(newUser: IUser | null) { + function setUser(newUser: IUser | null, saveSettings = true) { info.value = newUser if (newUser !== null) { reloadAvatar() - if (newUser.settings) { - settings.value = new UserSettingsModel(newUser.settings) + if (saveSettings && newUser.settings) { + loadSettings(newUser.settings) } isLinkShareAuth.value = newUser.id < 0 @@ -82,12 +83,26 @@ export const useAuthStore = defineStore('auth', () => { } function setUserSettings(newSettings: IUserSettings) { - settings.value = new UserSettingsModel(newSettings) + loadSettings(newSettings) info.value = new UserModel({ ...info.value !== null ? info.value : {}, name: newSettings.name, }) } + + function loadSettings(newSettings: IUserSettings) { + settings.value = new UserSettingsModel({ + ...newSettings, + frontendSettings: { + // Need to set default settings here in case the user does not have any saved in the api already + playSoundWhenDone: true, + quickAddMagicMode: PrefixMode.Default, + colorSchema: 'auto', + ...newSettings.frontendSettings, + }, + }) + // console.log('settings from auth store', {...settings.value.frontendSettings}) + } function setAuthenticated(newAuthenticated: boolean) { authenticated.value = newAuthenticated @@ -218,7 +233,8 @@ export const useAuthStore = defineStore('auth', () => { const info = new UserModel(JSON.parse(atob(base64))) const ts = Math.round((new Date()).getTime() / MILLISECONDS_A_SECOND) isAuthenticated = info.exp >= ts - setUser(info) + // Settings should only be loaded from the api request, not via the jwt + setUser(info, false) if (isAuthenticated) { await refreshUserInfo() @@ -268,7 +284,7 @@ export const useAuthStore = defineStore('auth', () => { await saveUserSettings({ settings: { ...settings.value, - language: getCurrentLanguage(), + language: settings.value.language ? settings.value.language : getBrowserLanguage(), }, showMessage: false, }) @@ -316,10 +332,9 @@ export const useAuthStore = defineStore('auth', () => { const cancel = setModuleLoading(setIsLoadingGeneralSettings) try { const updateSettingsPromise = userSettingsService.update(settings) - const saveLanguagePromise = saveLanguage(settings.language) - await updateSettingsPromise setUserSettings({...settings}) - await saveLanguagePromise + await setLanguage(settings.language) + await updateSettingsPromise if (showMessage) { success({message: i18n.global.t('user.settings.general.savedSuccess')}) } diff --git a/src/stores/tasks.ts b/src/stores/tasks.ts index 40e7f1bd1..3166e353f 100644 --- a/src/stores/tasks.ts +++ b/src/stores/tasks.ts @@ -6,8 +6,7 @@ import TaskService from '@/services/task' import TaskAssigneeService from '@/services/taskAssignee' import LabelTaskService from '@/services/labelTask' -import {playPop} from '@/helpers/playPop' -import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode' +import {playPopSound} from '@/helpers/playPop' import {cleanupItemText, parseTaskText, PREFIXES} from '@/modules/parseTaskText' import TaskAssigneeModel from '@/models/taskAssignee' @@ -29,6 +28,7 @@ import {useAttachmentStore} from '@/stores/attachments' import {useKanbanStore} from '@/stores/kanban' import {useBaseStore} from '@/stores/base' import ProjectUserService from '@/services/projectUsers' +import {useAuthStore} from '@/stores/auth' interface MatchedAssignee extends IUser { match: string, @@ -106,6 +106,7 @@ export const useTaskStore = defineStore('task', () => { const attachmentStore = useAttachmentStore() const labelStore = useLabelStore() const projectStore = useProjectStore() + const authStore = useAuthStore() const tasks = ref<{ [id: ITask['id']]: ITask }>({}) // TODO: or is this ITask[] const isLoading = ref(false) @@ -142,8 +143,8 @@ export const useTaskStore = defineStore('task', () => { try { const updatedTask = await taskService.update(task) kanbanStore.setTaskInBucket(updatedTask) - if (task.done) { - playPop() + if (task.done && useAuthStore().settings.frontendSettings.playSoundWhenDone) { + playPopSound() } return updatedTask } finally { @@ -398,7 +399,7 @@ export const useTaskStore = defineStore('task', () => { Partial, ) { const cancel = setModuleLoading(setIsLoading) - const quickAddMagicMode = getQuickAddMagicMode() + const quickAddMagicMode = authStore.settings.frontendSettings.quickAddMagicMode const parsedTask = parseTaskText(title, quickAddMagicMode) const foundProjectId = await findProjectId({ diff --git a/src/views/user/settings/General.vue b/src/views/user/settings/General.vue index 87624daaf..8822e8e92 100644 --- a/src/views/user/settings/General.vue +++ b/src/views/user/settings/General.vue @@ -57,7 +57,7 @@
@@ -97,7 +97,7 @@ {{ $t('user.settings.quickAddMagic.title') }}
- @@ -111,7 +111,7 @@ {{ $t('user.settings.appearance.title') }}
-