feat: custom content font size and line height, resolves FOL-354
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
c01984d50f
commit
d87a92a095
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ export interface UISettings {
|
|||
pictureViewMasonry: boolean
|
||||
pictureViewFilterNoImage: boolean
|
||||
wideMode: boolean
|
||||
contentFontSize: number
|
||||
dateFormat: string
|
||||
contentLineHeight: number
|
||||
|
||||
// Action Order
|
||||
toolbarOrder: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue