Skip to content

Commit d87a92a

Browse files
committed
feat: custom content font size and line height, resolves FOL-354
Signed-off-by: Innei <tukon479@gmail.com>
1 parent c01984d commit d87a92a

File tree

10 files changed

+168
-16
lines changed

10 files changed

+168
-16
lines changed

apps/renderer/src/atoms/settings/ui.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ export const createDefaultSettings = (): UISettings => ({
3030
// Font
3131
uiFontFamily: "SN Pro",
3232
readerFontFamily: "inherit",
33+
contentFontSize: 16,
34+
dateFormat: "default",
35+
contentLineHeight: 1.75,
3336
// Content
3437
readerRenderInlineStyle: false,
3538
codeHighlightThemeLight: "github-light",

apps/renderer/src/modules/entry-content/components/EntryTitle.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import type { SupportedLanguages } from "@follow/models/types"
22
import { cn } from "@follow/utils/utils"
3+
import dayjs from "dayjs"
34
import { useMemo } from "react"
45

56
import { useShowAITranslation } from "~/atoms/ai-translation"
67
import { useGeneralSettingSelector } from "~/atoms/settings/general"
8+
import { useUISettingKey } from "~/atoms/settings/ui"
79
import { useWhoami } from "~/atoms/user"
810
import { RelativeTime } from "~/components/ui/datetime"
911
import { useAuthQuery } from "~/hooks/common"
@@ -65,6 +67,14 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
6567
},
6668
)
6769

70+
const dateFormat = useUISettingKey("dateFormat")
71+
72+
const dateRender = useMemo(() => {
73+
if (!entry?.entries.publishedAt) return null
74+
if (dateFormat === "default") return new Date(entry.entries.publishedAt).toLocaleString()
75+
return dayjs(entry.entries.publishedAt).format(dateFormat)
76+
}, [dateFormat, entry?.entries.publishedAt])
77+
6878
if (!entry) return null
6979

7080
return compact ? (
@@ -104,7 +114,7 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
104114
{getPreferredTitle(feed || inbox, entry.entries)}
105115
</div>
106116
<div className="flex select-none items-center gap-2 text-[13px] text-zinc-500">
107-
{entry.entries.publishedAt && new Date(entry.entries.publishedAt).toLocaleString()}
117+
{dateRender}
108118

109119
<div className="flex items-center gap-1">
110120
<i className="i-mgc-eye-2-cute-re" />

apps/renderer/src/modules/entry-content/index.desktop.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { IN_ELECTRON } from "@follow/shared/constants"
66
import { stopPropagation } from "@follow/utils/dom"
77
import { cn } from "@follow/utils/utils"
88
import { ErrorBoundary } from "@sentry/react"
9+
import * as React from "react"
910
import { useEffect, useMemo, useRef } from "react"
1011

1112
import { useShowAITranslation } from "~/atoms/ai-translation"
@@ -82,15 +83,23 @@ export const EntryContent: Component<EntryContentProps> = ({
8283
[entryId, isPeekModal],
8384
)
8485
useFocusEntryContainerSubscriptions(scrollerRef)
85-
const stableRenderStyle = useMemo(
86-
() =>
87-
readerFontFamily
88-
? {
89-
fontFamily: readerFontFamily,
90-
}
91-
: undefined,
92-
[readerFontFamily],
93-
)
86+
const contentLineHeight = useUISettingKey("contentLineHeight")
87+
const contentFontSize = useUISettingKey("contentFontSize")
88+
89+
const stableRenderStyle = useMemo(() => {
90+
const css = {} as React.CSSProperties
91+
if (readerFontFamily) {
92+
css.fontFamily = readerFontFamily
93+
}
94+
if (contentLineHeight) {
95+
css.lineHeight = contentLineHeight
96+
}
97+
if (contentFontSize) {
98+
css.fontSize = contentFontSize
99+
}
100+
101+
return css
102+
}, [readerFontFamily, contentLineHeight, contentFontSize])
94103
const mediaInfo = useMemo(
95104
() =>
96105
Object.fromEntries(

apps/renderer/src/modules/entry-content/index.mobile.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,22 @@ export const EntryContent: Component<{
108108
(s) => s.translationLanguage,
109109
) as SupportedLanguages
110110

111+
const contentLineHeight = useUISettingKey("contentLineHeight")
112+
const contentFontSize = useUISettingKey("contentFontSize")
113+
114+
const stableRenderStyle = useMemo(() => {
115+
const css = {} as React.CSSProperties
116+
117+
if (contentLineHeight) {
118+
css.lineHeight = contentLineHeight
119+
}
120+
if (contentFontSize) {
121+
css.fontSize = contentFontSize
122+
}
123+
124+
return css
125+
}, [contentLineHeight, contentFontSize])
126+
111127
if (!entry) return null
112128

113129
const content = entry?.entries.content ?? data?.entries.content
@@ -207,6 +223,7 @@ export const EntryContent: Component<{
207223
as="article"
208224
className="prose !max-w-full hyphens-auto dark:prose-invert prose-h1:text-[1.6em] prose-h1:font-bold"
209225
renderInlineStyle={readerRenderInlineStyle}
226+
style={stableRenderStyle}
210227
>
211228
{content}
212229
</EntryContentHTMLRenderer>

apps/renderer/src/modules/settings/sections/fonts.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export const ContentFontSelector = () => {
7070
)
7171

7272
return (
73-
<div className="-mt-1 mb-3 flex items-center justify-between">
73+
<div className="mb-3 flex items-center justify-between">
7474
<span className="shrink-0 text-sm font-medium">{t("appearance.content_font")}</span>
7575
<Select
7676
defaultValue={FALLBACK_FONT}

apps/renderer/src/modules/settings/tabs/apperance.tsx

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import { ResponsiveSelect } from "@follow/components/ui/select/responsive.js"
1212
import { useIsDark, useThemeAtomValue } from "@follow/hooks"
1313
import { IN_ELECTRON } from "@follow/shared/constants"
1414
import { getOS } from "@follow/utils/utils"
15+
import dayjs from "dayjs"
1516
import { useForceUpdate } from "framer-motion"
16-
import { lazy, Suspense, useEffect, useRef } from "react"
17+
import { lazy, Suspense, useEffect, useRef, useState } from "react"
1718
import { useTranslation } from "react-i18next"
1819
import { bundledThemesInfo } from "shiki/themes"
1920

@@ -95,14 +96,19 @@ export const SettingAppearance = () => {
9596
value: t("appearance.fonts"),
9697
disabled: isMobile,
9798
},
98-
!isMobile && TextSize,
9999
!isMobile && UIFontSelector,
100+
!isMobile && TextSize,
101+
100102
!isMobile && ContentFontSelector,
103+
ContentFontSize,
104+
ContentLineHeight,
105+
101106
{
102107
type: "title",
103108
value: t("appearance.content"),
104109
},
105110
ShikiTheme,
111+
DateFormat,
106112

107113
defineItem("guessCodeLanguage", {
108114
label: t("appearance.guess_code_language.label"),
@@ -187,7 +193,7 @@ export const TextSize = () => {
187193
const uiTextSize = useUISettingSelector((state) => state.uiTextSize)
188194

189195
return (
190-
<div className="-mt-1 mb-3 flex items-center justify-between">
196+
<div className="mb-3 flex items-center justify-between">
191197
<span className="shrink-0 text-sm font-medium">{t("appearance.text_size")}</span>
192198
<Select
193199
defaultValue={textSizeMap.default.toString()}
@@ -392,3 +398,95 @@ const CustomCSSModal = () => {
392398
</form>
393399
)
394400
}
401+
402+
const ContentFontSize = () => {
403+
const { t } = useTranslation("settings")
404+
const contentFontSize = useUISettingKey("contentFontSize")
405+
return (
406+
<div className="mb-3 flex items-center justify-between">
407+
<span className="shrink-0 text-sm font-medium">{t("appearance.content_font_size")}</span>
408+
409+
<ResponsiveSelect
410+
items={[
411+
{ value: "12", label: "12" },
412+
{ value: "14", label: "14" },
413+
{ value: "16", label: "16" },
414+
{ value: "18", label: "18" },
415+
{ value: "20", label: "20" },
416+
]}
417+
value={contentFontSize.toString()}
418+
onValueChange={(value) => {
419+
setUISetting("contentFontSize", Number.parseInt(value))
420+
}}
421+
triggerClassName="w-48"
422+
size="sm"
423+
/>
424+
</div>
425+
)
426+
}
427+
428+
const ContentLineHeight = () => {
429+
const { t } = useTranslation("settings")
430+
const contentLineHeight = useUISettingKey("contentLineHeight")
431+
return (
432+
<div className="mb-3 flex items-center justify-between">
433+
<span className="shrink-0 text-sm font-medium">
434+
{t("appearance.content_line_height.label")}
435+
</span>
436+
437+
<ResponsiveSelect
438+
items={[
439+
{ value: "1.25", label: t("appearance.content_line_height.tight") },
440+
{ value: "1.375", label: t("appearance.content_line_height.snug") },
441+
{ value: "1.5", label: t("appearance.content_line_height.normal") },
442+
{ value: "1.75", label: t("appearance.content_line_height.relaxed") },
443+
{ value: "2", label: t("appearance.content_line_height.loose") },
444+
]}
445+
value={contentLineHeight.toString()}
446+
onValueChange={(value) => {
447+
setUISetting("contentLineHeight", Number.parseFloat(value))
448+
}}
449+
triggerClassName="w-48"
450+
size="sm"
451+
/>
452+
</div>
453+
)
454+
}
455+
456+
const DateFormat = () => {
457+
const { t } = useTranslation("settings")
458+
const { t: commonT } = useTranslation("common")
459+
const dateFormat = useUISettingKey("dateFormat")
460+
const [date] = useState(() => new Date())
461+
462+
const generateItem = (format: string) => ({
463+
value: format,
464+
label: dayjs(date).format(format),
465+
})
466+
return (
467+
<div className="mb-3 flex items-center justify-between">
468+
<span className="shrink-0 text-sm font-medium">{t("appearance.date_format")}</span>
469+
470+
<ResponsiveSelect
471+
items={[
472+
{ value: "default", label: commonT("words.default") },
473+
generateItem("MM/DD/YY HH:mm"),
474+
generateItem("DD/MM/YYYY HH:mm"),
475+
476+
generateItem("L"),
477+
generateItem("LTS"),
478+
generateItem("LT"),
479+
generateItem("LLLL"),
480+
generateItem("LL"),
481+
generateItem("LLL"),
482+
]}
483+
value={dateFormat}
484+
onValueChange={(value) => {
485+
setUISetting("dateFormat", value)
486+
}}
487+
triggerClassName="w-48"
488+
size="sm"
489+
/>
490+
</div>
491+
)
492+
}

changelog/next.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## New Features
44

5+
- Support custom font and line height for content
6+
- Support custom date format for entry title date
7+
58
## Improvements
69

710
## Bug Fixes

locales/common/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"words.back": "Back",
1919
"words.copy": "Copy",
2020
"words.create": "Create",
21+
"words.default": "Default",
2122
"words.delete": "Delete",
2223
"words.download": "Download",
2324
"words.edit": "Edit",

locales/settings/en.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,18 @@
5555
"appearance.code_highlight_theme": "Code highlight theme",
5656
"appearance.content": "Content",
5757
"appearance.content_font": "Content Font",
58+
"appearance.content_font_size": "Content Font Size",
59+
"appearance.content_line_height.label": "Content Line Height",
60+
"appearance.content_line_height.loose": "Loose",
61+
"appearance.content_line_height.normal": "Normal",
62+
"appearance.content_line_height.relaxed": "Relaxed",
63+
"appearance.content_line_height.snug": "Snug",
64+
"appearance.content_line_height.tight": "Tight",
5865
"appearance.custom_css.button": "Edit",
5966
"appearance.custom_css.description": "Custom CSS style for content",
6067
"appearance.custom_css.label": "Custom CSS",
6168
"appearance.custom_font": "Custom Font",
69+
"appearance.date_format": "Date format",
6270
"appearance.fonts": "Fonts",
6371
"appearance.general": "General",
6472
"appearance.guess_code_language.description": "Major programming languages that use models to infer unlabeled code blocks",
@@ -80,7 +88,7 @@
8088
"appearance.sidebar": "Sidebar",
8189
"appearance.sidebar_show_unread_count.label": "Show in sidebar",
8290
"appearance.sidebar_title": "Appearance",
83-
"appearance.text_size": "Text size",
91+
"appearance.text_size": "Global Text Size",
8492
"appearance.theme.dark": "Dark",
8593
"appearance.theme.label": "Theme",
8694
"appearance.theme.light": "Light",
@@ -90,7 +98,7 @@
9098
"appearance.thumbnail_ratio.square": "Square",
9199
"appearance.thumbnail_ratio.title": "Thumbnail ratio",
92100
"appearance.title": "Appearance",
93-
"appearance.ui_font": "UI Font",
101+
"appearance.ui_font": "Global Font",
94102
"appearance.unread_count": "Unread count",
95103
"appearance.use_pointer_cursor.description": "Change the cursor to a pointer when hovering over any interactive element.",
96104
"appearance.use_pointer_cursor.label": "Use pointer cursors",

packages/shared/src/interface/settings.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export interface UISettings {
5252
pictureViewMasonry: boolean
5353
pictureViewFilterNoImage: boolean
5454
wideMode: boolean
55+
contentFontSize: number
56+
dateFormat: string
57+
contentLineHeight: number
5558

5659
// Action Order
5760
toolbarOrder: {

0 commit comments

Comments
 (0)