diff --git a/app.vue b/app.vue index 70a8e63..6933776 100644 --- a/app.vue +++ b/app.vue @@ -1,7 +1,7 @@ - {{ customCss.value }} + {{ preferences.custom_css }} @@ -19,7 +19,6 @@ import ConfirmationModal from "./components/modals/confirm.vue"; import { Toaster } from "./components/ui/sonner"; import { TooltipProvider } from "./components/ui/tooltip"; import { overwriteGetLocale } from "./paraglide/runtime"; -import { type EnumSetting, SettingIds } from "./settings"; // Sin //import "~/styles/mcdonalds.css"; @@ -31,17 +30,16 @@ const origin = useRequestURL().searchParams.get("origin"); const appData = useAppData(); const instance = useInstance(); const description = useExtendedDescription(client); -const customCss = useSetting(SettingIds.CustomCSS); const route = useRoute(); // Theme switcher -const theme = useSetting(SettingIds.Theme) as Ref; const colorMode = useColorMode(); +const radius = useCssVar("--radius"); -watch(theme.value, () => { +watch(preferences.color_theme, (newVal) => { // Add theme-changing class to html to trigger transition document.documentElement.classList.add("theme-changing"); - colorMode.preference = theme.value.value; + colorMode.preference = newVal; setTimeout(() => { // Remove theme-changing class after transition @@ -49,6 +47,16 @@ watch(theme.value, () => { }, 1000); }); +watch( + preferences.border_radius, + (newVal) => { + radius.value = `${newVal}rem`; + }, + { + immediate: true, + }, +); + useSeoMeta({ titleTemplate: (titleChunk) => { return titleChunk ? `${titleChunk} · Versia` : "Versia"; @@ -111,13 +119,13 @@ html.theme-changing * { box-shadow 1s ease !important; } -.slide-down-enter-active, -.slide-down-leave-active { +.slide-up-enter-active, +.slide-up-leave-active { transition: transform 0.3s ease; } -.slide-down-enter-from, -.slide-down-leave-to { - transform: translateY(-100%); +.slide-up-enter-from, +.slide-up-leave-to { + transform: translateY(100%); } diff --git a/biome.json b/biome.json index d3a0a08..2ba3836 100644 --- a/biome.json +++ b/biome.json @@ -8,7 +8,8 @@ "rules": { "all": true, "suspicious": { - "noConsole": "off" + "noConsole": "off", + "noExplicitAny": "off" }, "performance": { "noBarrelFile": "off" @@ -20,7 +21,8 @@ "noUnusedVariables": "off", "noUnusedImports": "off", "noUndeclaredDependencies": "off", - "useImportExtensions": "off" + "useImportExtensions": "off", + "useJsxKeyInIterable": "off" }, "complexity": { "noExcessiveCognitiveComplexity": "off" diff --git a/bun.lock b/bun.lock index 78c0384..4984415 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@nuxtjs/color-mode": "3.5.2", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.4", + "@tanstack/vue-table": "^8.21.3", "@tiptap/extension-highlight": "^2.11.7", "@tiptap/extension-image": "^2.11.7", "@tiptap/extension-link": "^2.11.7", @@ -47,7 +48,9 @@ "tw-animate-css": "^1.2.8", "vaul-vue": "^0.4.1", "vee-validate": "^4.15.0", + "virtua": "^0.40.4", "vue": "^3.5.13", + "vue-draggable-plus": "^0.6.0", "vue-router": "^4.5.1", "vue-sonner": "^1.3.2", "zod": "^3.24.3", @@ -620,8 +623,12 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.1.4", "", { "dependencies": { "@tailwindcss/node": "4.1.4", "@tailwindcss/oxide": "4.1.4", "tailwindcss": "4.1.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-4UQeMrONbvrsXKXXp/uxmdEN5JIJ9RkH7YVzs6AMxC/KC1+Np7WZBaNIco7TEjlkthqxZbt8pU/ipD+hKjm80A=="], + "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.5", "", {}, "sha512-gMLNylxhJdUlfRR1G3U9rtuwUh2IjdrrniJIDcekVJN3/3i+bluvdMi3+eodnxzJq5nKnxnigo9h0lIpaqV6HQ=="], + "@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="], + "@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.5", "", { "dependencies": { "@tanstack/virtual-core": "3.13.5" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-1hhUA6CUjmKc5JDyKLcYOV6mI631FaKKxXh77Ja4UtIy6EOofYaLPk8vVgvK6vLMUSfHR2vI3ZpPY9ibyX60SA=="], "@tiptap/core": ["@tiptap/core@2.11.7", "", { "peerDependencies": { "@tiptap/pm": "^2.7.0" } }, "sha512-zN+NFFxLsxNEL8Qioc+DL6b8+Tt2bmRbXH22Gk6F6nD30x83eaUSFlSv3wqvgyCq3I1i1NO394So+Agmayx6rQ=="], @@ -714,6 +721,8 @@ "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], + "@types/sortablejs": ["@types/sortablejs@1.15.8", "", {}, "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg=="], + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], "@types/video.js": ["@types/video.js@7.3.58", "", {}, "sha512-1CQjuSrgbv1/dhmcfQ83eVyYbvGyqhTvb2Opxr0QCV+iJ4J6/J+XWQ3Om59WiwCd1MN3rDUHasx5XRrpUtewYQ=="], @@ -2064,6 +2073,8 @@ "videojs-vtt.js": ["videojs-vtt.js@0.15.5", "", { "dependencies": { "global": "^4.3.1" } }, "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ=="], + "virtua": ["virtua@0.40.4", "", { "peerDependencies": { "react": ">=16.14.0", "react-dom": ">=16.14.0", "solid-js": ">=1.0", "svelte": ">=5.0", "vue": ">=3.2" }, "optionalPeers": ["react", "react-dom", "solid-js", "svelte", "vue"] }, "sha512-eV55eOm2b5Lzc9upivqIcAFPgfBrfcVrppW9T4vhTH+QAbaxfw5ypq25apkG83T5FuiFEoQYnefix1fQyx/GXQ=="], + "vite": ["vite@6.2.6", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw=="], "vite-dev-rpc": ["vite-dev-rpc@1.0.7", "", { "dependencies": { "birpc": "^2.0.19", "vite-hot-client": "^2.0.4" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1" } }, "sha512-FxSTEofDbUi2XXujCA+hdzCDkXFG1PXktMjSk1efq9Qb5lOYaaM9zNSvKvPPF7645Bak79kSp1PTooMW2wktcA=="], @@ -2090,6 +2101,8 @@ "vue-devtools-stub": ["vue-devtools-stub@0.1.0", "", {}, "sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ=="], + "vue-draggable-plus": ["vue-draggable-plus@0.6.0", "", { "dependencies": { "@types/sortablejs": "^1.15.8" } }, "sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw=="], + "vue-router": ["vue-router@4.5.1", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.2.0" } }, "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw=="], "vue-sonner": ["vue-sonner@1.3.2", "", {}, "sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A=="], diff --git a/components/composer/composer.vue b/components/composer/composer.vue index 65bab80..c38cfe4 100644 --- a/components/composer/composer.vue +++ b/components/composer/composer.vue @@ -159,7 +159,6 @@ import { SelectTrigger, } from "~/components/ui/select"; import * as m from "~/paraglide/messages.js"; -import { SettingIds } from "~/settings"; import EditorContent from "../editor/content.vue"; import { Button } from "../ui/button"; import { DialogFooter } from "../ui/dialog"; @@ -169,13 +168,11 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import Files from "./files.vue"; const { Control_Enter, Command_Enter } = useMagicKeys(); -const ctrlEnterSend = useSetting(SettingIds.CtrlEnterToSend); -const defaultVisibility = useSetting(SettingIds.DefaultVisibility); const { play } = useAudio(); const fileInput = ref(null); watch([Control_Enter, Command_Enter], () => { - if (sending.value || !ctrlEnterSend.value.value) { + if (sending.value || !preferences.ctrl_enter_send.value) { return; } @@ -220,9 +217,10 @@ const state = reactive({ sensitive: relation?.type === "edit" ? relation.note.sensitive : false, contentWarning: relation?.type === "edit" ? relation.note.spoiler_text : "", contentType: "text/html" as "text/html" | "text/plain", - visibility: (relation?.type === "edit" - ? relation.note.visibility - : (defaultVisibility.value.value ?? "public")) as Status["visibility"], + visibility: + relation?.type === "edit" + ? relation.note.visibility + : preferences.default_visibility.value, files: (relation?.type === "edit" ? relation.note.media_attachments.map((a) => ({ apiId: a.id, diff --git a/components/form/switch.vue b/components/form/switch.vue new file mode 100644 index 0000000..20ab220 --- /dev/null +++ b/components/form/switch.vue @@ -0,0 +1,34 @@ + + + + + + {{ title }} + + + {{ description }} + + + + + + + + + + + diff --git a/components/form/text.vue b/components/form/text.vue new file mode 100644 index 0000000..eff9e81 --- /dev/null +++ b/components/form/text.vue @@ -0,0 +1,29 @@ + + + + {{ title }} + + + + + + {{ description }} + + + + + + diff --git a/components/modals/confirm-inline.vue b/components/modals/confirm-inline.vue index cab014a..e168c94 100644 --- a/components/modals/confirm-inline.vue +++ b/components/modals/confirm-inline.vue @@ -19,9 +19,9 @@ defineProps<{ modalOptions: ConfirmModalOptions; }>(); -defineEmits<{ - confirm: (result: ConfirmModalResult) => void; - cancel: () => void; +const emit = defineEmits<{ + confirm: [result: ConfirmModalResult]; + cancel: []; }>(); const inputValue = ref(""); @@ -55,10 +55,10 @@ const inputValue = ref(""); - $emit('cancel')"> + emit('cancel')"> {{ modalOptions.cancelText }} - $emit('confirm', { + emit('confirm', { confirmed: true, value: inputValue, })"> @@ -67,4 +67,4 @@ const inputValue = ref(""); - \ No newline at end of file + diff --git a/components/notes/actions.vue b/components/notes/actions.vue index f047677..9cae48b 100644 --- a/components/notes/actions.vue +++ b/components/notes/actions.vue @@ -21,7 +21,6 @@ import { Ellipsis, Heart, Quote, Repeat, Reply } from "lucide-vue-next"; import { toast } from "vue-sonner"; import * as m from "~/paraglide/messages.js"; import { getLocale } from "~/paraglide/runtime"; -import { SettingIds } from "~/settings"; import { confirmModalService } from "../modals/composable"; import ActionButton from "./action-button.vue"; import Menu from "./menu.vue"; @@ -48,11 +47,8 @@ const emit = defineEmits<{ }>(); const { play } = useAudio(); -const confirmLikes = useSetting(SettingIds.ConfirmLike); -const confirmReblogs = useSetting(SettingIds.ConfirmReblog); - const like = async () => { - if (confirmLikes.value.value) { + if (preferences.confirm_actions.value.includes("like")) { const confirmation = await confirmModalService.confirm({ title: m.slimy_least_ray_aid(), message: m.stale_new_ray_jolt(), @@ -74,7 +70,7 @@ const like = async () => { }; const unlike = async () => { - if (confirmLikes.value.value) { + if (preferences.confirm_actions.value.includes("like")) { const confirmation = await confirmModalService.confirm({ title: m.odd_strong_halibut_prosper(), message: m.slow_blue_parrot_savor(), @@ -95,7 +91,7 @@ const unlike = async () => { }; const reblog = async () => { - if (confirmReblogs.value.value) { + if (preferences.confirm_actions.value.includes("reblog")) { const confirmation = await confirmModalService.confirm({ title: m.best_mellow_llama_surge(), message: m.salty_plain_mallard_gaze(), @@ -116,7 +112,7 @@ const reblog = async () => { }; const unreblog = async () => { - if (confirmReblogs.value.value) { + if (preferences.confirm_actions.value.includes("reblog")) { const confirmation = await confirmModalService.confirm({ title: m.main_fancy_octopus_loop(), message: m.odd_alive_swan_express(), diff --git a/components/notes/content.vue b/components/notes/content.vue index 5ddb12a..503d9b9 100644 --- a/components/notes/content.vue +++ b/components/notes/content.vue @@ -1,11 +1,11 @@ - + - + - + @@ -14,7 +14,6 @@ diff --git a/components/notes/header.vue b/components/notes/header.vue index a248d6a..b4e6bfb 100644 --- a/components/notes/header.vue +++ b/components/notes/header.vue @@ -1,7 +1,7 @@ { - if (!enableHoverCard.value) { + if (!preferences.popup_avatar_hover) { popupOpen = false; } }" :open-delay="2000"> @@ -51,7 +51,6 @@ import type { } from "@vueuse/core"; import { AtSign, Globe, Lock, LockOpen } from "lucide-vue-next"; import { getLocale } from "~/paraglide/runtime"; -import { SettingIds } from "~/settings"; import Avatar from "../profiles/avatar.vue"; import SmallCard from "../profiles/small-card.vue"; import { @@ -59,7 +58,6 @@ import { HoverCardContent, HoverCardTrigger, } from "../ui/hover-card"; -import CopyableText from "./copyable-text.vue"; const { createdAt, noteUrl, author, authorUrl } = defineProps<{ cornerAvatar?: string; @@ -94,7 +92,6 @@ const fullTime = new Intl.DateTimeFormat(getLocale(), { dateStyle: "medium", timeStyle: "short", }).format(createdAt); -const enableHoverCard = useSetting(SettingIds.PopupAvatarHover); const popupOpen = ref(false); const visibilities = { diff --git a/components/notes/menu.vue b/components/notes/menu.vue index 306ec95..8be2eee 100644 --- a/components/notes/menu.vue +++ b/components/notes/menu.vue @@ -21,7 +21,6 @@ import { import { toast } from "vue-sonner"; import { confirmModalService } from "~/components/modals/composable.ts"; import * as m from "~/paraglide/messages.js"; -import { SettingIds } from "~/settings"; const { authorId, noteId } = defineProps<{ apiNoteString: string; @@ -41,8 +40,6 @@ const { copy } = useClipboard(); const loggedIn = !!identity.value; const authorIsMe = loggedIn && authorId === identity.value?.account.id; -const confirmDeletes = useSetting(SettingIds.ConfirmDelete); - const copyText = (text: string) => { copy(text); toast.success(m.flat_nice_worm_dream()); @@ -57,7 +54,7 @@ const blockUser = async (userId: string) => { }; const _delete = async () => { - if (confirmDeletes.value.value) { + if (preferences.confirm_actions.value.includes("delete")) { const confirmation = await confirmModalService.confirm({ title: m.calm_icy_weasel_twirl(), message: m.gray_fun_toucan_slide(), diff --git a/components/preferences/category.vue b/components/preferences/category.vue new file mode 100644 index 0000000..56aa2e1 --- /dev/null +++ b/components/preferences/category.vue @@ -0,0 +1,45 @@ + + + + {{ name }} + + + + + + + + + + + + + + + + diff --git a/components/preferences/code.vue b/components/preferences/code.vue deleted file mode 100644 index 5b142ab..0000000 --- a/components/preferences/code.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - {{ setting.title() }} - - - {{ setting.description() }} - - - - - - - - - - - { - setting.value = String(v); - } - " - /> - - - - - - diff --git a/components/preferences/developer.vue b/components/preferences/developer.vue new file mode 100644 index 0000000..5f49f72 --- /dev/null +++ b/components/preferences/developer.vue @@ -0,0 +1,60 @@ + + + + + + {{ key }} + + {{ value }} + + + + + + + + + diff --git a/components/preferences/dialog.vue b/components/preferences/dialog.vue new file mode 100644 index 0000000..92a182e --- /dev/null +++ b/components/preferences/dialog.vue @@ -0,0 +1,166 @@ + + + + + + + + + + Preferences + + + Make changes to your preferences here. + + + + + {{ page }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ pkg.description }} + + + + + + + Developers + + + + + + + + + Dependencies + + + + {{ dep }}@{{ version }} + + + + + + + + + + diff --git a/components/preferences/emojis/batch-dropdown.vue b/components/preferences/emojis/batch-dropdown.vue new file mode 100644 index 0000000..8217ac6 --- /dev/null +++ b/components/preferences/emojis/batch-dropdown.vue @@ -0,0 +1,72 @@ + + + + + + + + + {{ m.tense_quick_cod_favor() }} + + + + + + diff --git a/components/preferences/emojis/category.vue b/components/preferences/emojis/category.vue deleted file mode 100644 index 7c3be20..0000000 --- a/components/preferences/emojis/category.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - {{ name }} - - - - - - - - - - - - - - diff --git a/components/preferences/emojis/emoji.vue b/components/preferences/emojis/dropdown.vue similarity index 65% rename from components/preferences/emojis/emoji.vue rename to components/preferences/emojis/dropdown.vue index 51271ed..ec69beb 100644 --- a/components/preferences/emojis/emoji.vue +++ b/components/preferences/emojis/dropdown.vue @@ -1,39 +1,11 @@ - - - - - - - {{ emoji.shortcode }} - - - {{ - emoji.global - ? m.real_tame_moose_greet() - : m.witty_heroic_trout_cry() - }} - - - - - - - - - - - + + + + + + {{ m.cuddly_such_swallow_hush() }} @@ -52,20 +24,11 @@ diff --git a/components/preferences/emojis/table.vue b/components/preferences/emojis/table.vue new file mode 100644 index 0000000..0d1ff83 --- /dev/null +++ b/components/preferences/emojis/table.vue @@ -0,0 +1,361 @@ + + + + + + + + + + + + + + + Columns + + + + + { + column.toggleVisibility(!!value) + }"> + {{ column.id }} + + + + + + + + + + + + + + + + + + + + + + + + {{ JSON.stringify(row.original) }} + + + + + + + + No results. + + + + + + + + + {{ table.getFilteredSelectedRowModel().rows.length }} of + {{ table.getFilteredRowModel().rows.length }} row(s) selected. + + + + Previous + + + Next + + + + + diff --git a/components/preferences/emojis/uploader.vue b/components/preferences/emojis/uploader.vue index f2eb68d..18404ad 100644 --- a/components/preferences/emojis/uploader.vue +++ b/components/preferences/emojis/uploader.vue @@ -10,7 +10,7 @@ {{ m.frail_great_marten_pet() }} - + - - - - {{ m.pink_sharp_carp_work() }} - - - {{ m.dark_pretty_hyena_link() }} - - - - - - - + + + @@ -178,6 +164,7 @@ import { RolePermission } from "@versia/client/types"; import { useForm } from "vee-validate"; import { toast } from "vue-sonner"; import { z } from "zod"; +import FormSwitch from "~/components/form/switch.vue"; import { Button } from "~/components/ui/button"; import { Card, diff --git a/components/preferences/index.vue b/components/preferences/index.vue new file mode 100644 index 0000000..82b83d4 --- /dev/null +++ b/components/preferences/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/components/preferences/page.vue b/components/preferences/page.vue new file mode 100644 index 0000000..048299c --- /dev/null +++ b/components/preferences/page.vue @@ -0,0 +1,15 @@ + + + + {{ title }} + + + + + + + diff --git a/components/preferences/preferences.ts b/components/preferences/preferences.ts new file mode 100644 index 0000000..22bea5c --- /dev/null +++ b/components/preferences/preferences.ts @@ -0,0 +1,150 @@ +import * as m from "~/paraglide/messages.js"; +import { + BooleanPreference, + CodePreference, + MultiSelectPreference, + NumberPreference, + SelectPreference, + UrlPreference, +} from "./types"; + +export const preferences = { + render_mfm: new BooleanPreference({ + name: m.quaint_clear_boar_attend(), + description: m.aloof_helpful_larva_spur(), + defaultValue: true, + category: "Behaviour/Notes", + }), + default_visibility: new SelectPreference< + "public" | "unlisted" | "private" | "direct" + >({ + name: m.loud_tense_kitten_exhale(), + description: m.vivid_last_crocodile_offer(), + defaultValue: "public", + options: { + public: m.lost_trick_dog_grace(), + unlisted: m.funny_slow_jannes_walk(), + private: m.grassy_empty_raven_startle(), + direct: m.pretty_bold_baboon_wave(), + }, + category: "Behaviour/Posting", + }), + language: new SelectPreference<"en" | "fr">({ + name: m.pretty_born_jackal_dial(), + description: m.tired_happy_lobster_pet(), + defaultValue: "en", + options: { + en: m.keen_aware_goldfish_thrive( + {}, + { + locale: "en", + }, + ), + fr: m.vivid_mellow_sawfish_approve( + {}, + { + locale: "fr", + }, + ), + }, + category: "Behaviour/Globals", + }), + border_radius: new NumberPreference({ + name: "Border radius", + description: + "Global border radius that all elements inheritt from (rem units).", + defaultValue: 0.625, + step: 0.025, + min: 0, + max: 2, + category: "Appearance/Globals", + }), + custom_css: new CodePreference({ + name: m.smart_awake_dachshund_view(), + description: m.loved_topical_rat_coax(), + defaultValue: "", + language: "css", + category: "Appearance/Globals", + }), + color_theme: new SelectPreference<"dark" | "light" | "system">({ + name: m.hour_elegant_mink_grip(), + defaultValue: "system", + options: { + dark: m.wise_neat_ox_buzz(), + light: m.each_strong_snail_aid(), + system: m.helpful_raw_seal_nurture(), + }, + category: "Appearance/Globals", + }), + custom_emojis: new BooleanPreference({ + name: m.loud_raw_sheep_imagine(), + description: m.inclusive_pink_tuna_enjoy(), + defaultValue: true, + category: "Behaviour/Notes", + }), + show_content_warning: new BooleanPreference({ + name: m.fair_swift_elephant_hunt(), + description: m.gray_minor_bee_endure(), + defaultValue: true, + category: "Behaviour/Notes", + }), + popup_avatar_hover: new BooleanPreference({ + name: m.north_nimble_turkey_transform(), + description: m.bold_moving_fly_savor(), + defaultValue: false, + category: "Behaviour/Timelines", + }), + infinite_scroll: new BooleanPreference({ + name: m.sleek_this_earthworm_hug(), + description: m.plane_dark_salmon_pout(), + defaultValue: true, + category: "Behaviour/Timelines", + }), + confirm_actions: new MultiSelectPreference< + "delete" | "follow" | "like" | "reblog" + >({ + name: "Confirm actions", + description: "Confirm actions before performing them.", + defaultValue: ["delete"], + options: { + delete: m.trite_salty_eel_race(), + follow: m.jolly_empty_bullock_mend(), + like: m.patchy_basic_alligator_inspire(), + reblog: m.honest_great_rooster_taste(), + }, + category: "Behaviour/Notes", + }), + ctrl_enter_send: new BooleanPreference({ + name: m.equal_blue_zebra_launch(), + description: m.heavy_pink_meerkat_affirm(), + defaultValue: true, + category: "Behaviour/Posting", + }), + emoji_theme: new SelectPreference< + "native" | "twemoji" | "noto" | "fluent" | "fluent-flat" + >({ + name: m.weak_bad_martin_glow(), + description: m.warm_round_dove_skip(), + defaultValue: "native", + options: { + native: m.slimy_sound_termite_hug(), + twemoji: m.new_brave_maggot_relish(), + noto: m.shy_clear_spider_cook(), + fluent: m.many_tasty_midge_zoom(), + "fluent-flat": m.less_early_lionfish_honor(), + }, + category: "Appearance/Globals", + }), + background_url: new UrlPreference({ + name: m.stock_large_marten_comfort(), + description: m.mean_weird_donkey_stab(), + defaultValue: "", + category: "Appearance/Globals", + }), + display_notifications_sidebar: new BooleanPreference({ + name: m.tired_jumpy_rook_slurp(), + description: m.wide_new_robin_empower(), + defaultValue: true, + category: "Appearance/Globals", + }), +} as const; diff --git a/components/preferences/profile.ts b/components/preferences/profile.ts new file mode 100644 index 0000000..7d9d94e --- /dev/null +++ b/components/preferences/profile.ts @@ -0,0 +1,63 @@ +import { toTypedSchema } from "@vee-validate/zod"; +import { z } from "zod"; +import * as m from "~/paraglide/messages.js"; + +const characterRegex = new RegExp(/^[a-z0-9_-]+$/); + +export const formSchema = (identity: Identity) => + toTypedSchema( + z.strictObject({ + banner: z + .instanceof(File) + .refine( + (v) => + v.size <= + (identity.instance.configuration.accounts + .header_size_limit ?? Number.POSITIVE_INFINITY), + m.civil_icy_ant_mend({ + size: identity.instance.configuration.accounts + .header_size_limit, + }), + ) + .optional(), + avatar: z + .instanceof(File) + .refine( + (v) => + v.size <= + (identity.instance.configuration.accounts + .avatar_size_limit ?? Number.POSITIVE_INFINITY), + m.zippy_caring_raven_edit({ + size: identity.instance.configuration.accounts + .avatar_size_limit, + }), + ) + .or(z.string().url()) + .optional(), + name: z + .string() + .max( + identity.instance.configuration.accounts + .max_displayname_characters, + ), + username: z + .string() + .regex(characterRegex, m.still_upper_otter_dine()) + .max( + identity.instance.configuration.accounts + .max_username_characters, + ), + bio: z + .string() + .max( + identity.instance.configuration.accounts + .max_note_characters, + ), + bot: z.boolean().default(false), + locked: z.boolean().default(false), + discoverable: z.boolean().default(true), + fields: z.array( + z.strictObject({ name: z.string(), value: z.string() }), + ), + }), + ); diff --git a/components/preferences/profile.vue b/components/preferences/profile.vue new file mode 100644 index 0000000..873fce0 --- /dev/null +++ b/components/preferences/profile.vue @@ -0,0 +1,185 @@ + + + + + + Unsaved changes + + Click "apply" to save your changes. + + Apply + + + + + + + + + + + + setValue(file)" + @submit-url="(url) => setValue(url)" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/preferences/profile/editor.vue b/components/preferences/profile/editor.vue deleted file mode 100644 index 83b2e16..0000000 --- a/components/preferences/profile/editor.vue +++ /dev/null @@ -1,419 +0,0 @@ - - - - - - - {{ m.bright_late_osprey_renew() }} - - - - - - {{ m.great_level_lamb_sway() }} - - - - - - - - - {{ m.safe_icy_bulldog_quell() }} - - - setValue(file)" - @submit-url="(url) => setValue(url)" - /> - - - - - - - - - {{ m.mild_known_mallard_jolt() }} - - - - - - {{ m.lime_dry_skunk_loop() }} - - - - - - - - - {{ m.neat_silly_dog_prosper() }} - - - - - - {{ m.petty_plane_tadpole_earn() }} - - - - - - - - - {{ m.next_caring_ladybug_hack() }} - - - - - - {{ m.stale_just_anaconda_earn() }} - - - - - - - - - {{ m.aqua_mealy_toucan_pride() }} - - - - - - - - { - handleChange([ - ...value.slice(0, index), - { name: e, value: field.value }, - ...value.slice(index + 1), - ]); - } - " - /> - { - handleChange([ - ...value.slice(0, index), - { name: field.name, value: e }, - ...value.slice(index + 1), - ]); - } - " - /> - - - {{ m.front_north_eel_gulp() }} - - - - - - - - - - - - {{ m.gaudy_each_opossum_play() }} - - - {{ m.grassy_acidic_gadfly_cure() }} - - - - - - - - - - - - - - {{ m.dirty_moving_shark_emerge() }} - - - {{ m.bright_fun_mouse_boil() }} - - - - - - - - - - - - - - {{ m.red_vivid_cuckoo_spark() }} - - - {{ m.plain_zany_donkey_dart() }} - - - - - - - - - - - - - diff --git a/components/preferences/profile/fields.vue b/components/preferences/profile/fields.vue new file mode 100644 index 0000000..bc01e42 --- /dev/null +++ b/components/preferences/profile/fields.vue @@ -0,0 +1,104 @@ + + + + {{ title }} + + + + + + + + + + + + updateKey(index, String(e)) + " /> + updateValue(index, String(e)) + " /> + + + + + + + + + + + diff --git a/components/preferences/profile/image-uploader.vue b/components/preferences/profile/image-uploader.vue index 3b76674..eeeae18 100644 --- a/components/preferences/profile/image-uploader.vue +++ b/components/preferences/profile/image-uploader.vue @@ -21,10 +21,10 @@ {{ m.suave_broad_albatross_drop() }} - + @@ -222,7 +222,7 @@ const emailToGravatar = async (email: string) => { const open = ref(false); const gravatarUrl = ref(undefined); -const { handleSubmit, isSubmitting, values } = useForm({ +const { handleSubmit, isSubmitting } = useForm({ validationSchema: schema, }); diff --git a/components/preferences/select.vue b/components/preferences/select.vue deleted file mode 100644 index 9eef4a2..0000000 --- a/components/preferences/select.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - - {{ setting.title() }} - - - {{ setting.description() }} - - - - { setting.value = v }"> - - - - - - {{ option.label() }} - - - - - - - - diff --git a/components/preferences/stats.vue b/components/preferences/stats.vue new file mode 100644 index 0000000..4d5b03d --- /dev/null +++ b/components/preferences/stats.vue @@ -0,0 +1,37 @@ + + + + + + {{ key }} + + {{ value }} + + + + + + + + + diff --git a/components/preferences/string.vue b/components/preferences/string.vue deleted file mode 100644 index 30c34f8..0000000 --- a/components/preferences/string.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - {{ setting.title() }} - - - {{ setting.description() }} - - - - { setting.value = String(v) }" /> - - - - - diff --git a/components/preferences/switch.vue b/components/preferences/switch.vue deleted file mode 100644 index 1c8da35..0000000 --- a/components/preferences/switch.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - {{ setting.title() }} - - - {{ setting.description() }} - - - - { setting.value = v }" /> - - - - - diff --git a/components/preferences/types.ts b/components/preferences/types.ts new file mode 100644 index 0000000..0a29873 --- /dev/null +++ b/components/preferences/types.ts @@ -0,0 +1,71 @@ +export interface PreferenceOptions { + name: string; + description?: string; + category?: string; + defaultValue: ValueType; +} + +export abstract class Preference { + public abstract options: PreferenceOptions; +} + +export class TextPreference extends Preference { + constructor(public options: PreferenceOptions) { + super(); + } +} + +export class NumberPreference extends Preference { + constructor( + public options: PreferenceOptions & { + integer?: boolean; + step?: number; + min?: number; + max?: number; + }, + ) { + super(); + } +} + +export class BooleanPreference extends Preference { + constructor(public options: PreferenceOptions) { + super(); + } +} + +export class SelectPreference extends Preference { + constructor( + public options: PreferenceOptions & { + options: Record; + }, + ) { + super(); + } +} + +export class CodePreference extends Preference { + constructor( + public options: PreferenceOptions & { + language?: "css"; + }, + ) { + super(); + } +} + +export class MultiSelectPreference extends Preference { + constructor( + public options: PreferenceOptions & { + options: Record; + }, + ) { + super(); + } +} + +export class UrlPreference extends Preference { + constructor(public options: PreferenceOptions) { + super(); + } +} diff --git a/components/preferences/types/base.vue b/components/preferences/types/base.vue new file mode 100644 index 0000000..b8c519e --- /dev/null +++ b/components/preferences/types/base.vue @@ -0,0 +1,43 @@ + + + + {{ pref.options.name }} + {{ + pref.options.description }} + + + + + + + + + diff --git a/components/preferences/types/boolean.vue b/components/preferences/types/boolean.vue new file mode 100644 index 0000000..21cd7f7 --- /dev/null +++ b/components/preferences/types/boolean.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/components/preferences/types/code.vue b/components/preferences/types/code.vue new file mode 100644 index 0000000..fc9da3e --- /dev/null +++ b/components/preferences/types/code.vue @@ -0,0 +1,36 @@ + + + + + + + Open code + + + + + + + + + + + + + diff --git a/components/preferences/types/multiselect.vue b/components/preferences/types/multiselect.vue new file mode 100644 index 0000000..9c3e541 --- /dev/null +++ b/components/preferences/types/multiselect.vue @@ -0,0 +1,41 @@ + + + + + + Pick + + + + { + if (checked) { + setValue([...value, option]); + } else { + setValue(value.filter((v: any) => v !== option)); + } + }"> + {{ title }} + + + + + + + diff --git a/components/preferences/types/number.vue b/components/preferences/types/number.vue new file mode 100644 index 0000000..df60ce4 --- /dev/null +++ b/components/preferences/types/number.vue @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/preferences/types/select.vue b/components/preferences/types/select.vue new file mode 100644 index 0000000..b6278b7 --- /dev/null +++ b/components/preferences/types/select.vue @@ -0,0 +1,35 @@ + + + + + + + + + + {{ title }} + + + + + + + + diff --git a/components/preferences/types/text.vue b/components/preferences/types/text.vue new file mode 100644 index 0000000..5c1608c --- /dev/null +++ b/components/preferences/types/text.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/components/preferences/types/url.vue b/components/preferences/types/url.vue new file mode 100644 index 0000000..655cab1 --- /dev/null +++ b/components/preferences/types/url.vue @@ -0,0 +1,36 @@ + + + + + + + Edit URL + + + + + + + + + + + + + diff --git a/components/profiles/avatar.vue b/components/profiles/avatar.vue index 3ece60e..5e52ab9 100644 --- a/components/profiles/avatar.vue +++ b/components/profiles/avatar.vue @@ -1,5 +1,5 @@ - + {{ getInitials(name) }} @@ -8,7 +8,6 @@ diff --git a/components/profiles/profile.vue b/components/profiles/profile.vue index 5505edc..ad80bcb 100644 --- a/components/profiles/profile.vue +++ b/components/profiles/profile.vue @@ -74,7 +74,6 @@ import { Button } from "~/components/ui/button"; import { Card, CardContent, CardFooter, CardTitle } from "~/components/ui/card"; import { Separator } from "~/components/ui/separator"; import * as m from "~/paraglide/messages.js"; -import { SettingIds } from "~/settings"; import { confirmModalService } from "../modals/composable"; import ProfileActions from "./profile-actions.vue"; import ProfileBadges from "./profile-badges.vue"; @@ -91,10 +90,8 @@ const { relationship, isLoading } = useRelationship(client, account.id); const isMe = identity.value?.account.id === account.id; const [username, instance] = account.acct.split("@"); -const confirmFollows = useSetting(SettingIds.ConfirmFollow); - const follow = async () => { - if (confirmFollows.value.value) { + if (preferences.confirm_actions.value.includes("follow")) { const confirmation = await confirmModalService.confirm({ title: m.many_fair_capybara_imagine(), message: m.mellow_yummy_jannes_cuddle({ @@ -118,7 +115,7 @@ const follow = async () => { }; const unfollow = async () => { - if (confirmFollows.value.value) { + if (preferences.confirm_actions.value.includes("follow")) { const confirmation = await confirmModalService.confirm({ title: m.funny_aloof_swan_loop(), message: m.white_best_dolphin_catch({ diff --git a/components/profiles/tiny-card.vue b/components/profiles/tiny-card.vue index b1d186f..6c44fcd 100644 --- a/components/profiles/tiny-card.vue +++ b/components/profiles/tiny-card.vue @@ -1,7 +1,7 @@ diff --git a/components/sidebars/footer/footer-actions.vue b/components/sidebars/footer/footer-actions.vue index 52408d5..5dc3a58 100644 --- a/components/sidebars/footer/footer-actions.vue +++ b/components/sidebars/footer/footer-actions.vue @@ -1,5 +1,11 @@ diff --git a/components/sidebars/sidebar.ts b/components/sidebars/sidebar.ts index a3dda96..1587e17 100644 --- a/components/sidebars/sidebar.ts +++ b/components/sidebars/sidebar.ts @@ -1,44 +1,9 @@ -import { - BedSingle, - Bell, - Globe, - House, - MapIcon, - Settings2, -} from "lucide-vue-next"; +import { BedSingle, Bell, Globe, House, MapIcon } from "lucide-vue-next"; import * as m from "~/paraglide/messages.js"; import type { SidebarConfig } from "~/types/sidebar"; export const sidebarConfig: SidebarConfig = { - navMain: [ - { - title: m.patchy_seemly_hound_grace(), - url: "/preferences", - icon: Settings2, - items: [ - { - title: m.factual_arable_jurgen_endure(), - url: "/preferences/account", - }, - { - title: m.tough_clean_wolf_gleam(), - url: "/preferences/appearance", - }, - { - title: m.legal_best_tadpole_rise(), - url: "/preferences/behaviour", - }, - { - title: m.novel_trite_sloth_adapt(), - url: "/preferences/emojis", - }, - { - title: m.safe_green_mink_cook(), - url: "/preferences/roles", - }, - ], - }, - ], + navMain: [], other: [ { title: m.bland_chunky_sparrow_propel(), diff --git a/components/sidebars/sidebar.vue b/components/sidebars/sidebar.vue index 2eeeb00..84b8d83 100644 --- a/components/sidebars/sidebar.vue +++ b/components/sidebars/sidebar.vue @@ -1,13 +1,8 @@ + + + + + + diff --git a/components/ui/number-field/NumberFieldContent.vue b/components/ui/number-field/NumberFieldContent.vue new file mode 100644 index 0000000..bc5f9a4 --- /dev/null +++ b/components/ui/number-field/NumberFieldContent.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/components/ui/number-field/NumberFieldDecrement.vue b/components/ui/number-field/NumberFieldDecrement.vue new file mode 100644 index 0000000..c0c2282 --- /dev/null +++ b/components/ui/number-field/NumberFieldDecrement.vue @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/components/ui/number-field/NumberFieldIncrement.vue b/components/ui/number-field/NumberFieldIncrement.vue new file mode 100644 index 0000000..710fe71 --- /dev/null +++ b/components/ui/number-field/NumberFieldIncrement.vue @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/components/ui/number-field/NumberFieldInput.vue b/components/ui/number-field/NumberFieldInput.vue new file mode 100644 index 0000000..045994f --- /dev/null +++ b/components/ui/number-field/NumberFieldInput.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/components/ui/number-field/index.ts b/components/ui/number-field/index.ts new file mode 100644 index 0000000..2d61fbc --- /dev/null +++ b/components/ui/number-field/index.ts @@ -0,0 +1,5 @@ +export { default as NumberField } from "./NumberField.vue"; +export { default as NumberFieldContent } from "./NumberFieldContent.vue"; +export { default as NumberFieldDecrement } from "./NumberFieldDecrement.vue"; +export { default as NumberFieldIncrement } from "./NumberFieldIncrement.vue"; +export { default as NumberFieldInput } from "./NumberFieldInput.vue"; diff --git a/components/ui/switch/Switch.vue b/components/ui/switch/Switch.vue index 8a18033..e42d7e7 100644 --- a/components/ui/switch/Switch.vue +++ b/components/ui/switch/Switch.vue @@ -29,7 +29,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits); data-slot="switch" v-bind="forwarded" :class="cn( - 'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', + 'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] hover:cursor-pointer disabled:cursor-not-allowed disabled:opacity-50', props.class, )" > diff --git a/components/ui/table/Table.vue b/components/ui/table/Table.vue new file mode 100644 index 0000000..418423d --- /dev/null +++ b/components/ui/table/Table.vue @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/components/ui/table/TableBody.vue b/components/ui/table/TableBody.vue new file mode 100644 index 0000000..41d6b9c --- /dev/null +++ b/components/ui/table/TableBody.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/components/ui/table/TableCaption.vue b/components/ui/table/TableCaption.vue new file mode 100644 index 0000000..064dfb5 --- /dev/null +++ b/components/ui/table/TableCaption.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/components/ui/table/TableCell.vue b/components/ui/table/TableCell.vue new file mode 100644 index 0000000..6898cda --- /dev/null +++ b/components/ui/table/TableCell.vue @@ -0,0 +1,22 @@ + + + + + + + diff --git a/components/ui/table/TableEmpty.vue b/components/ui/table/TableEmpty.vue new file mode 100644 index 0000000..a822f44 --- /dev/null +++ b/components/ui/table/TableEmpty.vue @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/components/ui/table/TableFooter.vue b/components/ui/table/TableFooter.vue new file mode 100644 index 0000000..d5ee81d --- /dev/null +++ b/components/ui/table/TableFooter.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/components/ui/table/TableHead.vue b/components/ui/table/TableHead.vue new file mode 100644 index 0000000..c791e60 --- /dev/null +++ b/components/ui/table/TableHead.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/components/ui/table/TableHeader.vue b/components/ui/table/TableHeader.vue new file mode 100644 index 0000000..400daa1 --- /dev/null +++ b/components/ui/table/TableHeader.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/components/ui/table/TableRow.vue b/components/ui/table/TableRow.vue new file mode 100644 index 0000000..221d70a --- /dev/null +++ b/components/ui/table/TableRow.vue @@ -0,0 +1,17 @@ + + + + + + + diff --git a/components/ui/table/index.ts b/components/ui/table/index.ts new file mode 100644 index 0000000..0afab4c --- /dev/null +++ b/components/ui/table/index.ts @@ -0,0 +1,9 @@ +export { default as Table } from "./Table.vue"; +export { default as TableBody } from "./TableBody.vue"; +export { default as TableCaption } from "./TableCaption.vue"; +export { default as TableCell } from "./TableCell.vue"; +export { default as TableEmpty } from "./TableEmpty.vue"; +export { default as TableFooter } from "./TableFooter.vue"; +export { default as TableHead } from "./TableHead.vue"; +export { default as TableHeader } from "./TableHeader.vue"; +export { default as TableRow } from "./TableRow.vue"; diff --git a/components/ui/table/utils.ts b/components/ui/table/utils.ts new file mode 100644 index 0000000..045e0dd --- /dev/null +++ b/components/ui/table/utils.ts @@ -0,0 +1,12 @@ +import type { Updater } from "@tanstack/vue-table"; +import type { Ref } from "vue"; + +export function valueUpdater>( + updaterOrValue: T, + ref: Ref, +) { + ref.value = + typeof updaterOrValue === "function" + ? updaterOrValue(ref.value) + : updaterOrValue; +} diff --git a/components/ui/tabs/TabsList.vue b/components/ui/tabs/TabsList.vue index 7021820..1ebe4b7 100644 --- a/components/ui/tabs/TabsList.vue +++ b/components/ui/tabs/TabsList.vue @@ -19,7 +19,7 @@ const delegatedProps = computed(() => { data-slot="tabs-list" v-bind="delegatedProps" :class="cn( - 'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]', + 'bg-muted text-muted-foreground inline-flex w-fit items-center justify-center rounded-lg p-1 overflow-x-auto', props.class, )" > diff --git a/components/ui/tabs/TabsTrigger.vue b/components/ui/tabs/TabsTrigger.vue index 356b40b..f3bb35a 100644 --- a/components/ui/tabs/TabsTrigger.vue +++ b/components/ui/tabs/TabsTrigger.vue @@ -21,7 +21,7 @@ const forwardedProps = useForwardProps(delegatedProps); data-slot="tabs-trigger" v-bind="forwardedProps" :class="cn( - `data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4`, + `data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4`, props.class, )" > diff --git a/composables/Config.ts b/composables/Config.ts index 3d8966a..fee9092 100644 --- a/composables/Config.ts +++ b/composables/Config.ts @@ -71,6 +71,7 @@ export const useConfig = () => { "jessew@social.lysand.org", "jessew@beta.versia.social", "jessew@versia.social", + "jessew@vs.cpluspatch.com", "aprl@social.lysand.org", "aprl@beta.versia.social", "aprl@versia.social", diff --git a/composables/EventBus.ts b/composables/EventBus.ts index 5b3030f..7a804a9 100644 --- a/composables/EventBus.ts +++ b/composables/EventBus.ts @@ -23,6 +23,7 @@ type ApplicationEvents = { "account:update": Account; "attachment:view": Attachment; "identity:change": Identity; + "preferences:open": undefined; error: { code: string; title: string; diff --git a/composables/Language.ts b/composables/Language.ts index c07fe7b..a5aff69 100644 --- a/composables/Language.ts +++ b/composables/Language.ts @@ -1,7 +1,3 @@ -import { SettingIds } from "~/settings"; - export const useLanguage = () => { - const lang = useSetting(SettingIds.Language); - - return computed(() => lang.value.value as "en" | "fr" | "en-pt"); + return computed(() => preferences.language.value); }; diff --git a/composables/Preference.ts b/composables/Preference.ts new file mode 100644 index 0000000..118a824 --- /dev/null +++ b/composables/Preference.ts @@ -0,0 +1,56 @@ +import { StorageSerializers } from "@vueuse/core"; +import { preferences as prefs } from "~/components/preferences/preferences"; + +type SerializedPreferences = { + [K in keyof typeof prefs]: (typeof prefs)[K]["options"]["defaultValue"]; +}; + +const usePreferences = (): { + [K in keyof typeof prefs]: WritableComputedRef< + (typeof prefs)[K]["options"]["defaultValue"] + >; +} => { + const localStorage = useLocalStorage( + "versia:preferences", + Object.fromEntries( + Object.entries(prefs).map(([key, value]) => [ + key, + value.options.defaultValue, + ]), + ) as SerializedPreferences, + { + serializer: { + read(raw) { + return StorageSerializers.object.read(raw); + }, + write(value) { + return StorageSerializers.object.write(value); + }, + }, + }, + ); + + return Object.fromEntries( + Object.entries(prefs).map(([key, value]) => [ + key, + computed({ + get() { + return ( + localStorage.value[key as keyof typeof prefs] ?? + value.options.defaultValue + ); + }, + set(newValue) { + // @ts-expect-error Key is marked as readonly in the type + localStorage.value[key] = newValue; + }, + }), + ]), + ) as { + [K in keyof typeof prefs]: WritableComputedRef< + (typeof prefs)[K]["options"]["defaultValue"] + >; + }; +}; + +export const preferences = usePreferences(); diff --git a/composables/Settings.ts b/composables/Settings.ts deleted file mode 100644 index 24b230a..0000000 --- a/composables/Settings.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { StorageSerializers } from "@vueuse/core"; -import { - type SettingIds, - type Settings, - mergeSettings, - settings as settingsJson, -} from "~/settings"; - -const useSettings = () => { - return useLocalStorage("versia:settings", settingsJson(), { - serializer: { - read(raw) { - const json = StorageSerializers.object.read(raw); - - return mergeSettings(json); - }, - write(value) { - const json = Object.fromEntries( - Object.entries(value).map(([key, value]) => [ - key, - value.value, - ]), - ); - - // flatMap object values to .value - return StorageSerializers.object.write(json); - }, - }, - }); -}; - -export const settings = useSettings(); - -export const useSetting = ( - id: Id, -): Ref => { - const setting = ref(settings.value[id]) as Ref; - - watch(settings, (newSettings) => { - setting.value = newSettings[id]; - }); - - watch(setting, (newSetting) => { - settings.value = { - ...settings.value, - [id]: newSetting, - }; - }); - - return setting; -}; diff --git a/layouts/app.vue b/layouts/app.vue index 405cad3..b77b8de 100644 --- a/layouts/app.vue +++ b/layouts/app.vue @@ -8,6 +8,7 @@ + @@ -15,12 +16,11 @@ import ComposerDialog from "~/components/composer/dialog.vue"; import AuthRequired from "~/components/errors/AuthRequired.vue"; import MobileNavbar from "~/components/navigation/mobile-navbar.vue"; +import Preferences from "~/components/preferences/index.vue"; import AppSidebar from "~/components/sidebars/sidebar.vue"; import { SidebarProvider } from "~/components/ui/sidebar"; -import { SettingIds } from "~/settings"; const colorMode = useColorMode(); -const themeSetting = useSetting(SettingIds.Theme); const { n, d } = useMagicKeys(); const activeElement = useActiveElement(); const notUsingInput = computed( @@ -42,10 +42,10 @@ watch([n, notUsingInput, d], async () => { // Swap theme from dark to light or vice versa if (colorMode.value === "dark") { colorMode.preference = "light"; - themeSetting.value.value = "light"; + preferences.color_theme.value = "light"; } else { colorMode.preference = "dark"; - themeSetting.value.value = "dark"; + preferences.color_theme.value = "dark"; } } }); diff --git a/messages/en-pt.json b/messages/en-pt.json index 6da5a2e..f27e869 100644 --- a/messages/en-pt.json +++ b/messages/en-pt.json @@ -275,7 +275,6 @@ "wise_neat_ox_buzz": "Dark as the depths", "each_strong_snail_aid": "Bright as sail canvas", "helpful_raw_seal_nurture": "As the sea decides", - "male_stout_florian_feast": "Ship's appearance.", "hour_elegant_mink_grip": "Ship's look", "loud_raw_sheep_imagine": "Render deck decorations", "inclusive_pink_tuna_enjoy": "Render deck decorations. Requires resetting yer sails to apply.", diff --git a/messages/en.json b/messages/en.json index 8d27190..f1150ce 100644 --- a/messages/en.json +++ b/messages/en.json @@ -275,8 +275,7 @@ "wise_neat_ox_buzz": "Dark", "each_strong_snail_aid": "Light", "helpful_raw_seal_nurture": "System", - "male_stout_florian_feast": "UI theme.", - "hour_elegant_mink_grip": "Theme", + "hour_elegant_mink_grip": "Color theme", "loud_raw_sheep_imagine": "Render custom emojis", "inclusive_pink_tuna_enjoy": "Render custom emojis. Requires a page reload to apply.", "fair_swift_elephant_hunt": "Blur sensitive content", @@ -352,5 +351,7 @@ "sunny_small_warbler_express": "URL is valid", "teal_late_grebe_blend": "URL is invalid", "sharp_alive_anteater_fade": "Which instance?", - "noble_misty_rook_slide": "Put your instance's domain name here." + "noble_misty_rook_slide": "Put your instance's domain name here.", + "next_hour_jurgen_sprout": "Are you sure you want to delete {amount} emojis?", + "equal_only_crow_file": "Deleting {amount} emojis..." } diff --git a/messages/fr.json b/messages/fr.json index 1457cbd..9bd8668 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -257,8 +257,7 @@ "wise_neat_ox_buzz": "Sombre", "each_strong_snail_aid": "Clair", "helpful_raw_seal_nurture": "Système", - "male_stout_florian_feast": "Thème de l'interface.", - "hour_elegant_mink_grip": "Thème", + "hour_elegant_mink_grip": "Thème de couleurs", "loud_raw_sheep_imagine": "Afficher les émojis personnalisés", "inclusive_pink_tuna_enjoy": "Afficher les émojis personnalisés. Nécessite un rechargement de la page.", "fair_swift_elephant_hunt": "Flouter les contenus sensibles", @@ -333,5 +332,6 @@ "sunny_small_warbler_express": "L'URL est valide", "teal_late_grebe_blend": "L'URL n'est pas valide", "sharp_alive_anteater_fade": "Quelle instance ?", - "noble_misty_rook_slide": "Mettez le nom de domaine de votre instance ici." + "noble_misty_rook_slide": "Mettez le nom de domaine de votre instance ici.", + "next_hour_jurgen_sprout": "" } diff --git a/nix/package.nix b/nix/package.nix index 6141446..d20ad9f 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -10,14 +10,14 @@ packageJson = builtins.fromJSON (builtins.readFile ../package.json); in stdenv.mkDerivation (finalAttrs: { - pname = packageJson.name; + pname = "versia-fe"; version = packageJson.version; src = ../.; pnpmDeps = pnpm.fetchDeps { inherit (finalAttrs) pname version src; - hash = "sha256-Z8eZiCJ3wfk/RyMnqmbk9UhJbnfYHv1k9tusNwoOgB0="; + hash = "sha256-WYZDL8ankh/S2DrQMU9PRA0z8uWS7QO/nPp/i61mrVY="; }; nativeBuildInputs = [ diff --git a/package.json b/package.json index 06bec3d..3d01c88 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "versia-fe", - "version": "0.1.0", + "name": "@versia/frontend", + "version": "0.8.0-alpha", "private": true, - "description": " Versia Server frontend, designed with Nuxt.", + "description": "Beautiful, powerful and responsive web client for Versia Server.", "type": "module", "license": "AGPL-3.0", "author": { @@ -36,6 +36,7 @@ "@nuxtjs/color-mode": "3.5.2", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.4", + "@tanstack/vue-table": "^8.21.3", "@tiptap/extension-highlight": "^2.11.7", "@tiptap/extension-image": "^2.11.7", "@tiptap/extension-link": "^2.11.7", @@ -75,7 +76,9 @@ "tw-animate-css": "^1.2.8", "vaul-vue": "^0.4.1", "vee-validate": "^4.15.0", + "virtua": "^0.40.4", "vue": "^3.5.13", + "vue-draggable-plus": "^0.6.0", "vue-router": "^4.5.1", "vue-sonner": "^1.3.2", "zod": "^3.24.3" diff --git a/pages/preferences/[page].vue b/pages/preferences/[page].vue deleted file mode 100644 index 836f23b..0000000 --- a/pages/preferences/[page].vue +++ /dev/null @@ -1,64 +0,0 @@ - - - - - {{ page }} - - - - - - - - - - - - - diff --git a/pages/preferences/account.vue b/pages/preferences/account.vue deleted file mode 100644 index f22212d..0000000 --- a/pages/preferences/account.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - Unsaved changes - - Click "apply" to save your changes. - - - Apply - - - - - - diff --git a/pages/preferences/emojis.vue b/pages/preferences/emojis.vue deleted file mode 100644 index c8cb113..0000000 --- a/pages/preferences/emojis.vue +++ /dev/null @@ -1,106 +0,0 @@ - - - - - {{ m.suave_smart_mantis_climb() }} - - - Upload - - - - - - - - - - {{ m.actual_steep_llama_rest() }} - - {{ m.lucky_suave_myna_adore() }} - - - - - - - diff --git a/pages/preferences/index.vue b/pages/preferences/index.vue deleted file mode 100644 index 53be26f..0000000 --- a/pages/preferences/index.vue +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/plugins/EmojiRenderer.ts b/plugins/EmojiRenderer.ts index 7a77154..e4ea251 100644 --- a/plugins/EmojiRenderer.ts +++ b/plugins/EmojiRenderer.ts @@ -1,5 +1,4 @@ import type { Emoji } from "@versia/client/types"; -import { SettingIds } from "~/settings"; const emojisRegex = /\p{RI}\p{RI}|\p{Emoji}(\p{EMod}|\uFE0F\u20E3?|[\u{E0020}-\u{E007E}]+\u{E007F})?(\u200D(\p{RI}\p{RI}|\p{Emoji}(\p{EMod}|\uFE0F\u20E3?|[\u{E0020}-\u{E007E}]+\u{E007F})?))*/gu; @@ -8,11 +7,8 @@ const incorrectEmojisRegex = /^[#*0-9©®]$/; export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.directive("render-emojis", { beforeMount(el, binding) { - const shouldRenderEmoji = useSetting(SettingIds.CustomEmojis); - const emojiFont = useSetting(SettingIds.EmojiTheme); - // Replace emoji shortcodes with images - if (shouldRenderEmoji.value.value) { + if (preferences.custom_emojis.value) { el.innerHTML = el.innerHTML.replace( /:([a-zA-Z0-9_-]+):/g, (match, emoji) => { @@ -35,13 +31,13 @@ export default defineNuxtPlugin((nuxtApp) => { ); } - if (emojiFont.value.value !== "native") { + if (preferences.emoji_theme.value !== "native") { el.innerHTML = el.innerHTML.replace(emojisRegex, (match) => { if (incorrectEmojisRegex.test(match)) { return match; } - return ``; + return ``; }); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d12a38f..4d8d540 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@tailwindcss/vite': specifier: ^4.1.4 version: 4.1.4(vite@6.2.6(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.1)) + '@tanstack/vue-table': + specifier: ^8.21.3 + version: 8.21.3(vue@3.5.13(typescript@5.8.3)) '@tiptap/extension-highlight': specifier: ^2.11.7 version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) @@ -137,9 +140,15 @@ importers: vee-validate: specifier: ^4.15.0 version: 4.15.0(vue@3.5.13(typescript@5.8.3)) + virtua: + specifier: ^0.40.4 + version: 0.40.4(vue@3.5.13(typescript@5.8.3)) vue: specifier: ^3.5.13 version: 3.5.13(typescript@5.8.3) + vue-draggable-plus: + specifier: ^0.6.0 + version: 0.6.0(@types/sortablejs@1.15.8) vue-router: specifier: ^4.5.1 version: 4.5.1(vue@3.5.13(typescript@5.8.3)) @@ -1627,9 +1636,19 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + '@tanstack/virtual-core@3.13.6': resolution: {integrity: sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==} + '@tanstack/vue-table@8.21.3': + resolution: {integrity: sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==} + engines: {node: '>=12'} + peerDependencies: + vue: '>=3.2' + '@tanstack/vue-virtual@3.13.6': resolution: {integrity: sha512-GYdZ3SJBQPzgxhuCE2fvpiH46qzHiVx5XzBSdtESgiqh4poj8UgckjGWYEhxaBbcVt1oLzh1m3Ql4TyH32TOzQ==} peerDependencies: @@ -1857,6 +1876,9 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/sortablejs@1.15.8': + resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -4843,6 +4865,26 @@ packages: videojs-vtt.js@0.15.5: resolution: {integrity: sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==} + virtua@0.40.4: + resolution: {integrity: sha512-eV55eOm2b5Lzc9upivqIcAFPgfBrfcVrppW9T4vhTH+QAbaxfw5ypq25apkG83T5FuiFEoQYnefix1fQyx/GXQ==} + peerDependencies: + react: '>=16.14.0' + react-dom: '>=16.14.0' + solid-js: '>=1.0' + svelte: '>=5.0' + vue: '>=3.2' + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vue: + optional: true + vite-dev-rpc@1.0.7: resolution: {integrity: sha512-FxSTEofDbUi2XXujCA+hdzCDkXFG1PXktMjSk1efq9Qb5lOYaaM9zNSvKvPPF7645Bak79kSp1PTooMW2wktcA==} peerDependencies: @@ -4985,6 +5027,15 @@ packages: vue-devtools-stub@0.1.0: resolution: {integrity: sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==} + vue-draggable-plus@0.6.0: + resolution: {integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw==} + peerDependencies: + '@types/sortablejs': ^1.15.0 + '@vue/composition-api': '*' + peerDependenciesMeta: + '@vue/composition-api': + optional: true + vue-router@4.5.1: resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==} peerDependencies: @@ -6847,8 +6898,15 @@ snapshots: tailwindcss: 4.1.4 vite: 6.2.6(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.1) + '@tanstack/table-core@8.21.3': {} + '@tanstack/virtual-core@3.13.6': {} + '@tanstack/vue-table@8.21.3(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@tanstack/table-core': 8.21.3 + vue: 3.5.13(typescript@5.8.3) + '@tanstack/vue-virtual@3.13.6(vue@3.5.13(typescript@5.8.3))': dependencies: '@tanstack/virtual-core': 3.13.6 @@ -7081,6 +7139,8 @@ snapshots: '@types/resolve@1.20.2': {} + '@types/sortablejs@1.15.8': {} + '@types/trusted-types@2.0.7': {} '@types/video.js@7.3.58': {} @@ -10583,6 +10643,10 @@ snapshots: dependencies: global: 4.4.0 + virtua@0.40.4(vue@3.5.13(typescript@5.8.3)): + optionalDependencies: + vue: 3.5.13(typescript@5.8.3) + vite-dev-rpc@1.0.7(vite@6.2.6(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.1)): dependencies: birpc: 2.3.0 @@ -10697,6 +10761,10 @@ snapshots: vue-devtools-stub@0.1.0: {} + vue-draggable-plus@0.6.0(@types/sortablejs@1.15.8): + dependencies: + '@types/sortablejs': 1.15.8 + vue-router@4.5.1(vue@3.5.13(typescript@5.8.3)): dependencies: '@vue/devtools-api': 6.6.4 diff --git a/settings.ts b/settings.ts deleted file mode 100644 index 7a33228..0000000 --- a/settings.ts +++ /dev/null @@ -1,339 +0,0 @@ -import * as m from "~/paraglide/messages.js"; - -export enum SettingType { - String = "string", - Boolean = "boolean", - Enum = "enum", - Float = "float", - Integer = "integer", - Code = "code", -} - -export type Setting = { - title: () => string; - description: () => string; - notImplemented?: boolean; - type: SettingType; - value: unknown; - page: SettingPages; -}; - -export type StringSetting = Setting & { - type: SettingType.String; - value: string; -}; - -export type BooleanSetting = Setting & { - type: SettingType.Boolean; - value: boolean; -}; - -export type EnumSetting = Setting & { - type: SettingType.Enum; - value: string; - options: { - value: string; - label: () => string; - icon?: 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 { - Account = "account", - Emojis = "emojis", - Behaviour = "behaviour", - Appearance = "appearance", -} - -export enum SettingIds { - Language = "language", - Mfm = "mfm", - CustomCSS = "custom-css", - Theme = "theme", - CustomEmojis = "custom-emojis", - ShowContentWarning = "show-content-warning", - PopupAvatarHover = "popup-avatar-hover", - InfiniteScroll = "infinite-scroll", - ConfirmDelete = "confirm-delete", - ConfirmFollow = "confirm-follow", - ConfirmReblog = "confirm-reblog", - ConfirmLike = "confirm-favourite", - CtrlEnterToSend = "ctrl-enter-to-send", - EmojiTheme = "emoji-theme", - BackgroundURL = "background-url", - NotificationsSidebar = "notifications-sidebar", - AvatarShape = "avatar-shape", - DefaultVisibility = "default-visibility", -} - -export const settings = (): Record => { - return { - [SettingIds.Mfm]: { - title: m.quaint_clear_boar_attend, - description: m.aloof_helpful_larva_spur, - type: SettingType.Boolean, - value: false, - page: SettingPages.Behaviour, - notImplemented: true, - } as BooleanSetting, - [SettingIds.DefaultVisibility]: { - title: m.loud_tense_kitten_exhale, - description: m.vivid_last_crocodile_offer, - type: SettingType.Enum, - value: "public", - options: [ - { - value: "public", - label: m.lost_trick_dog_grace, - }, - { - value: "unlisted", - label: m.funny_slow_jannes_walk, - }, - { - value: "private", - label: m.grassy_empty_raven_startle, - }, - { - value: "direct", - label: m.pretty_bold_baboon_wave, - }, - ], - page: SettingPages.Behaviour, - } as EnumSetting, - [SettingIds.Language]: { - title: m.pretty_born_jackal_dial, - description: m.tired_happy_lobster_pet, - type: SettingType.Enum, - value: "en", - options: [ - { - value: "en", - label: () => - m.keen_aware_goldfish_thrive( - {}, - { - locale: "en", - }, - ), - }, - { - value: "fr", - label: () => - m.vivid_mellow_sawfish_approve( - {}, - { - locale: "fr", - }, - ), - }, - { - value: "en-pt", - label: () => m.these_awful_ape_reside(), - }, - ], - page: SettingPages.Behaviour, - } as EnumSetting, - [SettingIds.AvatarShape]: { - title: m.fit_cool_bulldog_dine, - description: m.agent_misty_firefox_arise, - type: SettingType.Enum, - value: "square", - options: [ - { - value: "circle", - label: m.polite_awful_ladybug_greet, - }, - { - value: "square", - label: m.sad_each_cowfish_lock, - }, - ], - page: SettingPages.Appearance, - } as EnumSetting, - [SettingIds.CustomCSS]: { - title: m.smart_awake_dachshund_view, - description: m.loved_topical_rat_coax, - type: SettingType.Code, - value: "", - language: "css", - page: SettingPages.Appearance, - } as CodeSetting, - [SettingIds.Theme]: { - title: m.hour_elegant_mink_grip, - description: m.male_stout_florian_feast, - type: SettingType.Enum, - value: "dark", - options: [ - { - value: "dark", - label: m.wise_neat_ox_buzz, - }, - { - value: "light", - label: m.each_strong_snail_aid, - }, - { - value: "system", - label: m.helpful_raw_seal_nurture, - }, - ], - page: SettingPages.Appearance, - } as EnumSetting, - [SettingIds.CustomEmojis]: { - title: m.loud_raw_sheep_imagine, - description: m.inclusive_pink_tuna_enjoy, - type: SettingType.Boolean, - value: true, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.ShowContentWarning]: { - title: m.fair_swift_elephant_hunt, - description: m.gray_minor_bee_endure, - type: SettingType.Boolean, - value: true, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.PopupAvatarHover]: { - title: m.north_nimble_turkey_transform, - description: m.bold_moving_fly_savor, - type: SettingType.Boolean, - value: true, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.InfiniteScroll]: { - title: m.sleek_this_earthworm_hug, - description: m.plane_dark_salmon_pout, - type: SettingType.Boolean, - value: true, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.ConfirmDelete]: { - title: m.trite_salty_eel_race, - description: m.helpful_early_worm_laugh, - type: SettingType.Boolean, - value: true, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.ConfirmFollow]: { - title: m.jolly_empty_bullock_mend, - description: m.calm_male_wombat_relish, - type: SettingType.Boolean, - value: false, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.ConfirmReblog]: { - title: m.honest_great_rooster_taste, - description: m.wacky_inner_osprey_intend, - type: SettingType.Boolean, - value: false, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.ConfirmLike]: { - title: m.patchy_basic_alligator_inspire, - description: m.antsy_weak_raven_treat, - type: SettingType.Boolean, - value: false, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.CtrlEnterToSend]: { - title: m.equal_blue_zebra_launch, - description: m.heavy_pink_meerkat_affirm, - type: SettingType.Boolean, - value: true, - page: SettingPages.Behaviour, - } as BooleanSetting, - [SettingIds.EmojiTheme]: { - title: m.weak_bad_martin_glow, - description: m.warm_round_dove_skip, - type: SettingType.Enum, - value: "native", - options: [ - { - value: "native", - label: m.slimy_sound_termite_hug, - }, - { - value: "twemoji", - label: m.new_brave_maggot_relish, - }, - { - value: "noto", - label: m.shy_clear_spider_cook, - }, - { - value: "fluent", - label: m.many_tasty_midge_zoom, - }, - { - value: "fluent-flat", - label: m.less_early_lionfish_honor, - }, - ], - page: SettingPages.Appearance, - } as EnumSetting, - [SettingIds.BackgroundURL]: { - title: m.stock_large_marten_comfort, - description: m.mean_weird_donkey_stab, - type: SettingType.String, - value: "", - page: SettingPages.Appearance, - } as StringSetting, - [SettingIds.NotificationsSidebar]: { - title: m.tired_jumpy_rook_slurp, - description: m.wide_new_robin_empower, - type: SettingType.Boolean, - value: true, - page: SettingPages.Appearance, - } as BooleanSetting, - }; -}; - -export const getSettingsForPage = (page: SettingPages): Partial => { - 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, -): Settings => { - const finalSettings = settings(); - - for (const [key, value] of Object.entries(settingsToMerge)) { - if (key in settings()) { - finalSettings[key as SettingIds].value = value; - } - } - - return finalSettings; -}; -export type Settings = ReturnType;
+ {{ pkg.description }} +
+ {{ dep }}@{{ version }} +