feat: custom content font size and line height, resolves FOL-354

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-02-11 23:09:35 +08:00
parent c01984d50f
commit d87a92a095
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
10 changed files with 168 additions and 16 deletions

View File

@ -30,6 +30,9 @@ export const createDefaultSettings = (): UISettings => ({
// Font
uiFontFamily: "SN Pro",
readerFontFamily: "inherit",
contentFontSize: 16,
dateFormat: "default",
contentLineHeight: 1.75,
// Content
readerRenderInlineStyle: false,
codeHighlightThemeLight: "github-light",

View File

@ -1,9 +1,11 @@
import type { SupportedLanguages } from "@follow/models/types"
import { cn } from "@follow/utils/utils"
import dayjs from "dayjs"
import { useMemo } from "react"
import { useShowAITranslation } from "~/atoms/ai-translation"
import { useGeneralSettingSelector } from "~/atoms/settings/general"
import { useUISettingKey } from "~/atoms/settings/ui"
import { useWhoami } from "~/atoms/user"
import { RelativeTime } from "~/components/ui/datetime"
import { useAuthQuery } from "~/hooks/common"
@ -65,6 +67,14 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
},
)
const dateFormat = useUISettingKey("dateFormat")
const dateRender = useMemo(() => {
if (!entry?.entries.publishedAt) return null
if (dateFormat === "default") return new Date(entry.entries.publishedAt).toLocaleString()
return dayjs(entry.entries.publishedAt).format(dateFormat)
}, [dateFormat, entry?.entries.publishedAt])
if (!entry) return null
return compact ? (
@ -104,7 +114,7 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
{getPreferredTitle(feed || inbox, entry.entries)}
</div>
<div className="flex select-none items-center gap-2 text-[13px] text-zinc-500">
{entry.entries.publishedAt && new Date(entry.entries.publishedAt).toLocaleString()}
{dateRender}
<div className="flex items-center gap-1">
<i className="i-mgc-eye-2-cute-re" />

View File

@ -6,6 +6,7 @@ import { IN_ELECTRON } from "@follow/shared/constants"
import { stopPropagation } from "@follow/utils/dom"
import { cn } from "@follow/utils/utils"
import { ErrorBoundary } from "@sentry/react"
import * as React from "react"
import { useEffect, useMemo, useRef } from "react"
import { useShowAITranslation } from "~/atoms/ai-translation"
@ -82,15 +83,23 @@ export const EntryContent: Component<EntryContentProps> = ({
[entryId, isPeekModal],
)
useFocusEntryContainerSubscriptions(scrollerRef)
const stableRenderStyle = useMemo(
() =>
readerFontFamily
? {
fontFamily: readerFontFamily,
}
: undefined,
[readerFontFamily],
)
const contentLineHeight = useUISettingKey("contentLineHeight")
const contentFontSize = useUISettingKey("contentFontSize")
const stableRenderStyle = useMemo(() => {
const css = {} as React.CSSProperties
if (readerFontFamily) {
css.fontFamily = readerFontFamily
}
if (contentLineHeight) {
css.lineHeight = contentLineHeight
}
if (contentFontSize) {
css.fontSize = contentFontSize
}
return css
}, [readerFontFamily, contentLineHeight, contentFontSize])
const mediaInfo = useMemo(
() =>
Object.fromEntries(

View File

@ -108,6 +108,22 @@ export const EntryContent: Component<{
(s) => s.translationLanguage,
) as SupportedLanguages
const contentLineHeight = useUISettingKey("contentLineHeight")
const contentFontSize = useUISettingKey("contentFontSize")
const stableRenderStyle = useMemo(() => {
const css = {} as React.CSSProperties
if (contentLineHeight) {
css.lineHeight = contentLineHeight
}
if (contentFontSize) {
css.fontSize = contentFontSize
}
return css
}, [contentLineHeight, contentFontSize])
if (!entry) return null
const content = entry?.entries.content ?? data?.entries.content
@ -207,6 +223,7 @@ export const EntryContent: Component<{
as="article"
className="prose !max-w-full hyphens-auto dark:prose-invert prose-h1:text-[1.6em] prose-h1:font-bold"
renderInlineStyle={readerRenderInlineStyle}
style={stableRenderStyle}
>
{content}
</EntryContentHTMLRenderer>

View File

@ -70,7 +70,7 @@ export const ContentFontSelector = () => {
)
return (
<div className="-mt-1 mb-3 flex items-center justify-between">
<div className="mb-3 flex items-center justify-between">
<span className="shrink-0 text-sm font-medium">{t("appearance.content_font")}</span>
<Select
defaultValue={FALLBACK_FONT}

View File

@ -12,8 +12,9 @@ import { ResponsiveSelect } from "@follow/components/ui/select/responsive.js"
import { useIsDark, useThemeAtomValue } from "@follow/hooks"
import { IN_ELECTRON } from "@follow/shared/constants"
import { getOS } from "@follow/utils/utils"
import dayjs from "dayjs"
import { useForceUpdate } from "framer-motion"
import { lazy, Suspense, useEffect, useRef } from "react"
import { lazy, Suspense, useEffect, useRef, useState } from "react"
import { useTranslation } from "react-i18next"
import { bundledThemesInfo } from "shiki/themes"
@ -95,14 +96,19 @@ export const SettingAppearance = () => {
value: t("appearance.fonts"),
disabled: isMobile,
},
!isMobile && TextSize,
!isMobile && UIFontSelector,
!isMobile && TextSize,
!isMobile && ContentFontSelector,
ContentFontSize,
ContentLineHeight,
{
type: "title",
value: t("appearance.content"),
},
ShikiTheme,
DateFormat,
defineItem("guessCodeLanguage", {
label: t("appearance.guess_code_language.label"),
@ -187,7 +193,7 @@ export const TextSize = () => {
const uiTextSize = useUISettingSelector((state) => state.uiTextSize)
return (
<div className="-mt-1 mb-3 flex items-center justify-between">
<div className="mb-3 flex items-center justify-between">
<span className="shrink-0 text-sm font-medium">{t("appearance.text_size")}</span>
<Select
defaultValue={textSizeMap.default.toString()}
@ -392,3 +398,95 @@ const CustomCSSModal = () => {
</form>
)
}
const ContentFontSize = () => {
const { t } = useTranslation("settings")
const contentFontSize = useUISettingKey("contentFontSize")
return (
<div className="mb-3 flex items-center justify-between">
<span className="shrink-0 text-sm font-medium">{t("appearance.content_font_size")}</span>
<ResponsiveSelect
items={[
{ value: "12", label: "12" },
{ value: "14", label: "14" },
{ value: "16", label: "16" },
{ value: "18", label: "18" },
{ value: "20", label: "20" },
]}
value={contentFontSize.toString()}
onValueChange={(value) => {
setUISetting("contentFontSize", Number.parseInt(value))
}}
triggerClassName="w-48"
size="sm"
/>
</div>
)
}
const ContentLineHeight = () => {
const { t } = useTranslation("settings")
const contentLineHeight = useUISettingKey("contentLineHeight")
return (
<div className="mb-3 flex items-center justify-between">
<span className="shrink-0 text-sm font-medium">
{t("appearance.content_line_height.label")}
</span>
<ResponsiveSelect
items={[
{ value: "1.25", label: t("appearance.content_line_height.tight") },
{ value: "1.375", label: t("appearance.content_line_height.snug") },
{ value: "1.5", label: t("appearance.content_line_height.normal") },
{ value: "1.75", label: t("appearance.content_line_height.relaxed") },
{ value: "2", label: t("appearance.content_line_height.loose") },
]}
value={contentLineHeight.toString()}
onValueChange={(value) => {
setUISetting("contentLineHeight", Number.parseFloat(value))
}}
triggerClassName="w-48"
size="sm"
/>
</div>
)
}
const DateFormat = () => {
const { t } = useTranslation("settings")
const { t: commonT } = useTranslation("common")
const dateFormat = useUISettingKey("dateFormat")
const [date] = useState(() => new Date())
const generateItem = (format: string) => ({
value: format,
label: dayjs(date).format(format),
})
return (
<div className="mb-3 flex items-center justify-between">
<span className="shrink-0 text-sm font-medium">{t("appearance.date_format")}</span>
<ResponsiveSelect
items={[
{ value: "default", label: commonT("words.default") },
generateItem("MM/DD/YY HH:mm"),
generateItem("DD/MM/YYYY HH:mm"),
generateItem("L"),
generateItem("LTS"),
generateItem("LT"),
generateItem("LLLL"),
generateItem("LL"),
generateItem("LLL"),
]}
value={dateFormat}
onValueChange={(value) => {
setUISetting("dateFormat", value)
}}
triggerClassName="w-48"
size="sm"
/>
</div>
)
}

View File

@ -2,6 +2,9 @@
## New Features
- Support custom font and line height for content
- Support custom date format for entry title date
## Improvements
## Bug Fixes

View File

@ -18,6 +18,7 @@
"words.back": "Back",
"words.copy": "Copy",
"words.create": "Create",
"words.default": "Default",
"words.delete": "Delete",
"words.download": "Download",
"words.edit": "Edit",

View File

@ -55,10 +55,18 @@
"appearance.code_highlight_theme": "Code highlight theme",
"appearance.content": "Content",
"appearance.content_font": "Content Font",
"appearance.content_font_size": "Content Font Size",
"appearance.content_line_height.label": "Content Line Height",
"appearance.content_line_height.loose": "Loose",
"appearance.content_line_height.normal": "Normal",
"appearance.content_line_height.relaxed": "Relaxed",
"appearance.content_line_height.snug": "Snug",
"appearance.content_line_height.tight": "Tight",
"appearance.custom_css.button": "Edit",
"appearance.custom_css.description": "Custom CSS style for content",
"appearance.custom_css.label": "Custom CSS",
"appearance.custom_font": "Custom Font",
"appearance.date_format": "Date format",
"appearance.fonts": "Fonts",
"appearance.general": "General",
"appearance.guess_code_language.description": "Major programming languages that use models to infer unlabeled code blocks",
@ -80,7 +88,7 @@
"appearance.sidebar": "Sidebar",
"appearance.sidebar_show_unread_count.label": "Show in sidebar",
"appearance.sidebar_title": "Appearance",
"appearance.text_size": "Text size",
"appearance.text_size": "Global Text Size",
"appearance.theme.dark": "Dark",
"appearance.theme.label": "Theme",
"appearance.theme.light": "Light",
@ -90,7 +98,7 @@
"appearance.thumbnail_ratio.square": "Square",
"appearance.thumbnail_ratio.title": "Thumbnail ratio",
"appearance.title": "Appearance",
"appearance.ui_font": "UI Font",
"appearance.ui_font": "Global Font",
"appearance.unread_count": "Unread count",
"appearance.use_pointer_cursor.description": "Change the cursor to a pointer when hovering over any interactive element.",
"appearance.use_pointer_cursor.label": "Use pointer cursors",

View File

@ -52,6 +52,9 @@ export interface UISettings {
pictureViewMasonry: boolean
pictureViewFilterNoImage: boolean
wideMode: boolean
contentFontSize: number
dateFormat: string
contentLineHeight: number
// Action Order
toolbarOrder: {