mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-07 00:48:20 +01:00
refactor: ♻️ Rewrite settings backend
This commit is contained in:
parent
80b1fc87f7
commit
78e4fa0f04
|
|
@ -36,14 +36,14 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Switch } from "@ark-ui/vue";
|
import { Switch } from "@ark-ui/vue";
|
||||||
import { type Setting, type SettingIds, SettingType } from "~/settings";
|
import { type SettingIds, SettingType } from "~/settings";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
setting: Setting;
|
id: SettingIds;
|
||||||
}>();
|
}>();
|
||||||
const checked = ref(!!props.setting.value);
|
|
||||||
|
|
||||||
const setting = useSetting(props.setting.id as SettingIds);
|
const setting = useSetting(props.id);
|
||||||
|
const checked = ref(setting.value.value as boolean);
|
||||||
|
|
||||||
watch(checked, (c) => {
|
watch(checked, (c) => {
|
||||||
setting.value.value = c;
|
setting.value.value = c;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<HoverCard.Root :positioning="{
|
<HoverCard.Root :positioning="{
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
|
strategy: 'fixed',
|
||||||
}" v-if="isEnabled.value">
|
}" v-if="isEnabled.value">
|
||||||
<HoverCard.Trigger :as-child="true">
|
<HoverCard.Trigger :as-child="true">
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Account, Emoji } from "@lysand-org/client/types";
|
import type { Account, Emoji } from "@lysand-org/client/types";
|
||||||
import { renderToString } from "vue/server-renderer";
|
import { renderToString } from "vue/server-renderer";
|
||||||
import { SettingIds, type Settings, getSettingById } from "~/settings";
|
import { SettingIds, type Settings } from "~/settings";
|
||||||
import MentionComponent from "../components/social-elements/notes/mention.vue";
|
import MentionComponent from "../components/social-elements/notes/mention.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -13,7 +13,7 @@ export const useParsedContent = (
|
||||||
content: MaybeRef<string>,
|
content: MaybeRef<string>,
|
||||||
emojis: MaybeRef<Emoji[]>,
|
emojis: MaybeRef<Emoji[]>,
|
||||||
mentions: MaybeRef<Account[]> = ref([]),
|
mentions: MaybeRef<Account[]> = ref([]),
|
||||||
settings: MaybeRef<Settings> = ref([]),
|
settings: MaybeRef<Settings | null> = ref(null),
|
||||||
): Ref<string | null> => {
|
): Ref<string | null> => {
|
||||||
const result = ref(null as string | null);
|
const result = ref(null as string | null);
|
||||||
|
|
||||||
|
|
@ -27,10 +27,8 @@ export const useParsedContent = (
|
||||||
const contentHtml = document.createElement("div");
|
const contentHtml = document.createElement("div");
|
||||||
contentHtml.innerHTML = toValue(content);
|
contentHtml.innerHTML = toValue(content);
|
||||||
|
|
||||||
const shouldRenderEmoji = getSettingById(
|
const shouldRenderEmoji =
|
||||||
toValue(settings),
|
toValue(settings)?.[SettingIds.CustomEmojis].value;
|
||||||
SettingIds.CustomEmojis,
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
// Replace emoji shortcodes with images
|
// Replace emoji shortcodes with images
|
||||||
if (shouldRenderEmoji) {
|
if (shouldRenderEmoji) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {
|
||||||
type Setting,
|
type Setting,
|
||||||
type SettingIds,
|
type SettingIds,
|
||||||
type Settings,
|
type Settings,
|
||||||
parseFromJson,
|
mergeSettings,
|
||||||
settings as settingsJson,
|
settings as settingsJson,
|
||||||
} from "~/settings";
|
} from "~/settings";
|
||||||
|
|
||||||
|
|
@ -13,18 +13,17 @@ const useSettings = () => {
|
||||||
read(raw) {
|
read(raw) {
|
||||||
const json = StorageSerializers.object.read(raw);
|
const json = StorageSerializers.object.read(raw);
|
||||||
|
|
||||||
return parseFromJson(json);
|
return mergeSettings(json);
|
||||||
},
|
},
|
||||||
write(value) {
|
write(value) {
|
||||||
// key/value, with key being id and value being the value
|
const json = Object.fromEntries(
|
||||||
const json = value.reduce(
|
Object.entries(value).map(([key, value]) => [
|
||||||
(acc, setting) => {
|
key,
|
||||||
acc[setting.id] = setting.value;
|
value.value,
|
||||||
return acc;
|
]),
|
||||||
},
|
|
||||||
{} as Record<string, unknown>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// flatMap object values to .value
|
||||||
return StorageSerializers.object.write(json);
|
return StorageSerializers.object.write(json);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -33,24 +32,21 @@ const useSettings = () => {
|
||||||
|
|
||||||
export const settings = useSettings();
|
export const settings = useSettings();
|
||||||
|
|
||||||
export const useSetting = <T extends Setting = Setting>(id: SettingIds) => {
|
export const useSetting = <Id extends SettingIds>(
|
||||||
const setting: Ref<T> = ref<T>(
|
id: Id,
|
||||||
settings.value.find((s) => s.id === id) as T,
|
): Ref<Settings[Id]> => {
|
||||||
) as unknown as Ref<T>;
|
const setting = ref(settings.value[id]) as Ref<Settings[Id]>;
|
||||||
|
|
||||||
watch(settings, (newSettings) => {
|
watch(settings, (newSettings) => {
|
||||||
setting.value = newSettings.find((s) => s.id === id) as T;
|
setting.value = newSettings[id];
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(setting, (newSetting) => {
|
watch(setting, (newSetting) => {
|
||||||
settings.value = settings.value.map((s) =>
|
settings.value = {
|
||||||
s.id === id ? newSetting : s,
|
...settings.value,
|
||||||
) as Settings;
|
[id]: newSetting,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return setting;
|
return setting;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSetting = <T extends Setting = Setting>(id: SettingIds) => {
|
|
||||||
return settingsJson.find((s) => s.id === id) as T;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<SettingsSidebar>
|
<SettingsSidebar>
|
||||||
<template #behaviour>
|
<template #behaviour>
|
||||||
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
|
<Renderer :id="id" v-for="id of (Object.keys(getSettingsForPage(
|
||||||
settings,
|
|
||||||
SettingPages.Behaviour,
|
SettingPages.Behaviour,
|
||||||
)" :key="setting.id" />
|
)) as SettingIds[])" :key="id" />
|
||||||
</template>
|
</template>
|
||||||
<template #appearance>
|
<template #appearance>
|
||||||
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
|
<Renderer :id="id" v-for="id of (Object.keys(getSettingsForPage(
|
||||||
settings,
|
|
||||||
SettingPages.Appearance,
|
SettingPages.Appearance,
|
||||||
)" :key="setting.id" />
|
)) as SettingIds[])" :key="id" />
|
||||||
</template>
|
</template>
|
||||||
<template #advanced>
|
<template #advanced>
|
||||||
<Renderer :setting="setting" v-for="setting of getSettingsForPath(
|
<Renderer :id="id" v-for="id of (Object.keys(getSettingsForPage(
|
||||||
settings,
|
|
||||||
SettingPages.Advanced,
|
SettingPages.Advanced,
|
||||||
)" :key="setting.id" />
|
)) as SettingIds[])" :key="id" />
|
||||||
</template>
|
</template>
|
||||||
<template #account>
|
<template #account>
|
||||||
<ProfileEditor />
|
<ProfileEditor />
|
||||||
|
|
@ -28,7 +25,7 @@
|
||||||
import ProfileEditor from "~/components/settings/profile-editor.vue";
|
import ProfileEditor from "~/components/settings/profile-editor.vue";
|
||||||
import Renderer from "~/components/settings/renderer.vue";
|
import Renderer from "~/components/settings/renderer.vue";
|
||||||
import SettingsSidebar from "~/components/sidebars/settings-sidebar.vue";
|
import SettingsSidebar from "~/components/sidebars/settings-sidebar.vue";
|
||||||
import { SettingPages, getSettingsForPath } from "~/settings";
|
import { SettingIds, SettingPages, getSettingsForPage } from "~/settings";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: "app",
|
layout: "app",
|
||||||
|
|
|
||||||
185
settings.ts
185
settings.ts
|
|
@ -7,27 +7,51 @@ export enum SettingType {
|
||||||
Code = "code",
|
Code = "code",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Setting<T = SettingType> = {
|
export type Setting = {
|
||||||
id: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
notImplemented?: boolean;
|
notImplemented?: boolean;
|
||||||
type: T;
|
type: SettingType;
|
||||||
value: T extends SettingType.String | SettingType.Code
|
value: unknown;
|
||||||
? string
|
page: SettingPages;
|
||||||
: T extends SettingType.Boolean
|
};
|
||||||
? boolean
|
|
||||||
: T extends SettingType.Float | SettingType.Integer
|
export type StringSetting = Setting & {
|
||||||
? number
|
type: SettingType.String;
|
||||||
: T extends SettingType.Enum
|
value: string;
|
||||||
? string
|
};
|
||||||
: never;
|
|
||||||
min?: T extends SettingType.Float | SettingType.Integer ? number : never;
|
export type BooleanSetting = Setting & {
|
||||||
max?: T extends SettingType.Float | SettingType.Integer ? number : never;
|
type: SettingType.Boolean;
|
||||||
step?: T extends SettingType.Float | SettingType.Integer ? number : never;
|
value: boolean;
|
||||||
options?: T extends SettingType.Enum ? string[] : never;
|
};
|
||||||
language?: T extends SettingType.Code ? string : never;
|
|
||||||
path: SettingPages;
|
export type EnumSetting = Setting & {
|
||||||
|
type: SettingType.Enum;
|
||||||
|
value: string;
|
||||||
|
options: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FloatSetting = Setting & {
|
||||||
|
type: SettingType.Float;
|
||||||
|
value: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
step: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IntegerSetting = Setting & {
|
||||||
|
type: SettingType.Integer;
|
||||||
|
value: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
step: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CodeSetting = Setting & {
|
||||||
|
type: SettingType.Code;
|
||||||
|
value: string;
|
||||||
|
language: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum SettingPages {
|
export enum SettingPages {
|
||||||
|
|
@ -37,27 +61,6 @@ export enum SettingPages {
|
||||||
Appearance = "appearance",
|
Appearance = "appearance",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSettingsForPath = (
|
|
||||||
settingsToFilterIn: Settings,
|
|
||||||
path: SettingPages,
|
|
||||||
) => settingsToFilterIn.filter((setting) => setting.path === path);
|
|
||||||
|
|
||||||
export const getSettingById = (settingsToFilterIn: Settings, id: SettingIds) =>
|
|
||||||
settingsToFilterIn.find((setting) => setting.id === id);
|
|
||||||
|
|
||||||
export const parseFromJson = (json: Record<string, unknown>) => {
|
|
||||||
const finalSettings = structuredClone(settings);
|
|
||||||
|
|
||||||
// Override the default values with the values from the JSON except for the user value
|
|
||||||
for (const setting of finalSettings) {
|
|
||||||
if (setting.id in json) {
|
|
||||||
setting.value = json[setting.id] as (typeof setting)["value"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalSettings;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum SettingIds {
|
export enum SettingIds {
|
||||||
Mfm = "mfm",
|
Mfm = "mfm",
|
||||||
CustomCSS = "custom-css",
|
CustomCSS = "custom-css",
|
||||||
|
|
@ -72,103 +75,115 @@ export enum SettingIds {
|
||||||
ConfirmFavourite = "confirm-favourite",
|
ConfirmFavourite = "confirm-favourite",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const settings = [
|
export const settings: Record<SettingIds, Setting> = {
|
||||||
{
|
[SettingIds.Mfm]: {
|
||||||
id: SettingIds.Mfm,
|
|
||||||
title: "Render MFM",
|
title: "Render MFM",
|
||||||
description: "Render Misskey-Flavoured Markdown",
|
description: "Render Misskey-Flavoured Markdown",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
notImplemented: true,
|
notImplemented: true,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
{
|
[SettingIds.CustomCSS]: {
|
||||||
id: SettingIds.CustomCSS,
|
|
||||||
title: "Custom CSS",
|
title: "Custom CSS",
|
||||||
description: "Custom CSS for the UI",
|
description: "Custom CSS for the UI",
|
||||||
type: SettingType.Code,
|
type: SettingType.Code,
|
||||||
value: "",
|
value: "",
|
||||||
language: "css",
|
language: "css",
|
||||||
path: SettingPages.Appearance,
|
page: SettingPages.Appearance,
|
||||||
} as Setting<SettingType.Code>,
|
} as CodeSetting,
|
||||||
{
|
[SettingIds.Theme]: {
|
||||||
id: SettingIds.Theme,
|
|
||||||
title: "Theme",
|
title: "Theme",
|
||||||
description: "UI theme",
|
description: "UI theme",
|
||||||
type: SettingType.Enum,
|
type: SettingType.Enum,
|
||||||
value: "dark",
|
value: "dark",
|
||||||
options: ["light", "dark"],
|
options: ["light", "dark"],
|
||||||
path: SettingPages.Appearance,
|
page: SettingPages.Appearance,
|
||||||
} as Setting<SettingType.Enum>,
|
} as EnumSetting,
|
||||||
{
|
[SettingIds.CustomEmojis]: {
|
||||||
id: SettingIds.CustomEmojis,
|
|
||||||
title: "Render Custom Emojis",
|
title: "Render Custom Emojis",
|
||||||
description: "Render custom emojis",
|
description: "Render custom emojis",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: true,
|
value: true,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
{
|
[SettingIds.ShowContentWarning]: {
|
||||||
id: SettingIds.ShowContentWarning,
|
|
||||||
title: "Show Content Warning",
|
title: "Show Content Warning",
|
||||||
description: "Show content warnings on notes marked sensitive/spoiler",
|
description: "Show content warnings on notes marked sensitive/spoiler",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: true,
|
value: true,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
{
|
[SettingIds.PopupAvatarHover]: {
|
||||||
id: SettingIds.PopupAvatarHover,
|
|
||||||
title: "Popup Profile Hover",
|
title: "Popup Profile Hover",
|
||||||
description: "Show profile popup when hovering over a user's avatar",
|
description: "Show profile popup when hovering over a user's avatar",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: true,
|
value: true,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
{
|
[SettingIds.InfiniteScroll]: {
|
||||||
id: SettingIds.InfiniteScroll,
|
|
||||||
title: "Infinite Scroll",
|
title: "Infinite Scroll",
|
||||||
description:
|
description:
|
||||||
"Automatically load more notes when reaching the bottom of the page",
|
"Automatically load more notes when reaching the bottom of the page",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: true,
|
value: true,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
{
|
[SettingIds.ConfirmDelete]: {
|
||||||
id: SettingIds.ConfirmDelete,
|
|
||||||
title: "Confirm Delete",
|
title: "Confirm Delete",
|
||||||
description: "Confirm before deleting a note",
|
description: "Confirm before deleting a note",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
notImplemented: true,
|
notImplemented: true,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
{
|
[SettingIds.ConfirmFollow]: {
|
||||||
id: SettingIds.ConfirmFollow,
|
|
||||||
title: "Confirm Follow",
|
title: "Confirm Follow",
|
||||||
description: "Confirm before following/unfollowing a user",
|
description: "Confirm before following/unfollowing a user",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
notImplemented: true,
|
notImplemented: true,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
{
|
[SettingIds.ConfirmReblog]: {
|
||||||
id: SettingIds.ConfirmReblog,
|
|
||||||
title: "Confirm Reblog",
|
title: "Confirm Reblog",
|
||||||
description: "Confirm before reblogging a note",
|
description: "Confirm before reblogging a note",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
notImplemented: true,
|
notImplemented: true,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
{
|
[SettingIds.ConfirmFavourite]: {
|
||||||
id: SettingIds.ConfirmFavourite,
|
|
||||||
title: "Confirm Favourite",
|
title: "Confirm Favourite",
|
||||||
description: "Confirm before favouriting a note",
|
description: "Confirm before favouriting a note",
|
||||||
type: SettingType.Boolean,
|
type: SettingType.Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
path: SettingPages.Behaviour,
|
page: SettingPages.Behaviour,
|
||||||
notImplemented: true,
|
notImplemented: true,
|
||||||
} as Setting<SettingType.Boolean>,
|
} as BooleanSetting,
|
||||||
];
|
};
|
||||||
|
|
||||||
|
export const getSettingsForPage = (page: SettingPages): Partial<Settings> => {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(settings).filter(([, setting]) => setting.page === page),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge a partly defined Settings object with the default settings
|
||||||
|
* Useful when there is an update to the settings in the backend
|
||||||
|
*/
|
||||||
|
export const mergeSettings = (
|
||||||
|
settingsToMerge: Record<SettingIds, Setting["value"]>,
|
||||||
|
): Settings => {
|
||||||
|
const finalSettings = structuredClone(settings);
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(settingsToMerge)) {
|
||||||
|
if (key in settings) {
|
||||||
|
finalSettings[key as SettingIds].value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalSettings;
|
||||||
|
};
|
||||||
export type Settings = typeof settings;
|
export type Settings = typeof settings;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue