From 7f4202afd73105b850498b55ad66922b3505f0e3 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Sun, 14 Dec 2025 16:39:25 -0800 Subject: feat: Add unified reader settings with local overrides (#2230) * Add initial impl * fix some format inconsistencies, add indicator in user settings when local is out of sync * Fix sliders in user settings, unify constants and formatting * address CodeRabbit suggestions * add mobile implementation * address coderabbit nitpicks * fix responsiveness of the reader settings popover * Move more of the web UI strings to i18n * update translations for more coverage * remove duplicate logic/definitions * fix android font family * add shared reading setting hook between web and mobile * unify reader settings context for both web and mobile * remove unused export * address coderabbit suggestions * fix tests --- apps/web/components/settings/ReaderSettings.tsx | 288 ++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 apps/web/components/settings/ReaderSettings.tsx (limited to 'apps/web/components/settings') diff --git a/apps/web/components/settings/ReaderSettings.tsx b/apps/web/components/settings/ReaderSettings.tsx new file mode 100644 index 00000000..ce4017c7 --- /dev/null +++ b/apps/web/components/settings/ReaderSettings.tsx @@ -0,0 +1,288 @@ +"use client"; + +import { useState } from "react"; +import { useClientConfig } from "@/lib/clientConfig"; +import { useTranslation } from "@/lib/i18n/client"; +import { useReaderSettings } from "@/lib/readerSettings"; +import { AlertTriangle, BookOpen, Laptop, RotateCcw } from "lucide-react"; + +import { + formatFontSize, + formatLineHeight, + READER_DEFAULTS, + READER_FONT_FAMILIES, + READER_SETTING_CONSTRAINTS, +} from "@karakeep/shared/types/readers"; + +import { Alert, AlertDescription } from "../ui/alert"; +import { Button } from "../ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "../ui/card"; +import { Label } from "../ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; +import { Slider } from "../ui/slider"; +import { toast } from "../ui/use-toast"; + +export default function ReaderSettings() { + const { t } = useTranslation(); + const clientConfig = useClientConfig(); + const { + settings, + serverSettings, + localOverrides, + hasLocalOverrides, + clearServerDefaults, + clearLocalOverrides, + updateServerSetting, + } = useReaderSettings(); + + // Local state for slider dragging (null = not dragging, use server value) + const [draggingFontSize, setDraggingFontSize] = useState(null); + const [draggingLineHeight, setDraggingLineHeight] = useState( + null, + ); + + const hasServerSettings = + serverSettings.fontSize !== null || + serverSettings.lineHeight !== null || + serverSettings.fontFamily !== null; + + const handleClearDefaults = () => { + clearServerDefaults(); + toast({ description: t("settings.info.reader_settings.defaults_cleared") }); + }; + + const handleClearLocalOverrides = () => { + clearLocalOverrides(); + toast({ + description: t("settings.info.reader_settings.local_overrides_cleared"), + }); + }; + + // Format local override for display + const formatLocalOverride = ( + key: "fontSize" | "lineHeight" | "fontFamily", + ) => { + const value = localOverrides[key]; + if (value === undefined) return null; + if (key === "fontSize") return formatFontSize(value as number); + if (key === "lineHeight") return formatLineHeight(value as number); + if (key === "fontFamily") { + switch (value) { + case "serif": + return t("settings.info.reader_settings.serif"); + case "sans": + return t("settings.info.reader_settings.sans"); + case "mono": + return t("settings.info.reader_settings.mono"); + } + } + return String(value); + }; + + return ( + + + + + {t("settings.info.reader_settings.title")} + + + {t("settings.info.reader_settings.description")} + + + + {/* Local Overrides Warning */} + {hasLocalOverrides && ( + + + +
+

+ {t("settings.info.reader_settings.local_overrides_title")} +

+

+ {t( + "settings.info.reader_settings.local_overrides_description", + )} +

+
    + {localOverrides.fontFamily !== undefined && ( +
  • + {t("settings.info.reader_settings.font_family")}:{" "} + {formatLocalOverride("fontFamily")} +
  • + )} + {localOverrides.fontSize !== undefined && ( +
  • + {t("settings.info.reader_settings.font_size")}:{" "} + {formatLocalOverride("fontSize")} +
  • + )} + {localOverrides.lineHeight !== undefined && ( +
  • + {t("settings.info.reader_settings.line_height")}:{" "} + {formatLocalOverride("lineHeight")} +
  • + )} +
+
+ +
+
+ )} + + {/* Font Family */} +
+ + + {serverSettings.fontFamily === null && ( +

+ {t("settings.info.reader_settings.using_default")}:{" "} + {READER_DEFAULTS.fontFamily} +

+ )} +
+ + {/* Font Size */} +
+
+ + + {formatFontSize(draggingFontSize ?? settings.fontSize)} + {serverSettings.fontSize === null && + draggingFontSize === null && + ` (${t("common.default").toLowerCase()})`} + +
+ setDraggingFontSize(value)} + onValueCommit={([value]) => { + updateServerSetting({ fontSize: value }); + setDraggingFontSize(null); + }} + max={READER_SETTING_CONSTRAINTS.fontSize.max} + min={READER_SETTING_CONSTRAINTS.fontSize.min} + step={READER_SETTING_CONSTRAINTS.fontSize.step} + /> +
+ + {/* Line Height */} +
+
+ + + {formatLineHeight(draggingLineHeight ?? settings.lineHeight)} + {serverSettings.lineHeight === null && + draggingLineHeight === null && + ` (${t("common.default").toLowerCase()})`} + +
+ setDraggingLineHeight(value)} + onValueCommit={([value]) => { + updateServerSetting({ lineHeight: value }); + setDraggingLineHeight(null); + }} + max={READER_SETTING_CONSTRAINTS.lineHeight.max} + min={READER_SETTING_CONSTRAINTS.lineHeight.min} + step={READER_SETTING_CONSTRAINTS.lineHeight.step} + /> +
+ + {/* Clear Defaults Button */} + {hasServerSettings && ( + + )} + + {/* Preview */} +
+

+ {t("settings.info.reader_settings.preview")} +

+

+ {t("settings.info.reader_settings.preview_text")} +
+ {t("settings.info.reader_settings.preview_text")} +
+ {t("settings.info.reader_settings.preview_text")} +

+
+
+
+ ); +} -- cgit v1.2.3-70-g09d2