mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
feature: add option to change font (#10133)
* feature: add option to change font 1. set app global font 2. set code block font Signed-off-by: Albert Abdilim <albert.abdilim@foxmail.com> * formatted code with Prettier * fix ci errors 1.add migration in `migrate.ts` 2.add to-be-translated strings by running `yarn sync:i18n` * chore: update yarn.lock to include font-list package version 2.0.0 * fix migration issue --------- Signed-off-by: Albert Abdilim <albert.abdilim@foxmail.com> Co-authored-by: suyao <sy20010504@gmail.com>
This commit is contained in:
parent
80afb3a86e
commit
993d497aad
@ -78,6 +78,7 @@
|
||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||
"express": "^5.1.0",
|
||||
"faiss-node": "^0.5.1",
|
||||
"font-list": "^2.0.0",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"jsdom": "26.1.0",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
|
||||
@ -38,6 +38,7 @@ export enum IpcChannel {
|
||||
App_GetDiskInfo = 'app:get-disk-info',
|
||||
App_SetFullScreen = 'app:set-full-screen',
|
||||
App_IsFullScreen = 'app:is-full-screen',
|
||||
App_GetSystemFonts = 'app:get-system-fonts',
|
||||
|
||||
App_MacIsProcessTrusted = 'app:mac-is-process-trusted',
|
||||
App_MacRequestProcessTrust = 'app:mac-request-process-trust',
|
||||
|
||||
@ -14,6 +14,7 @@ import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { FileMetadata, Provider, Shortcut, ThemeMode } from '@types'
|
||||
import checkDiskSpace from 'check-disk-space'
|
||||
import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron'
|
||||
import fontList from 'font-list'
|
||||
import { Notification } from 'src/renderer/src/types/notification'
|
||||
|
||||
import { apiServerService } from './services/ApiServerService'
|
||||
@ -219,6 +220,17 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
return mainWindow.isFullScreen()
|
||||
})
|
||||
|
||||
// Get System Fonts
|
||||
ipcMain.handle(IpcChannel.App_GetSystemFonts, async () => {
|
||||
try {
|
||||
const fonts = await fontList.getFonts()
|
||||
return fonts.map((font: string) => font.replace(/^"(.*)"$/, '$1')).filter((font: string) => font.length > 0)
|
||||
} catch (error) {
|
||||
logger.error('Failed to get system fonts:', error as Error)
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => {
|
||||
configManager.set(key, value, isNotify)
|
||||
})
|
||||
|
||||
@ -84,6 +84,7 @@ const api = {
|
||||
ipcRenderer.invoke(IpcChannel.App_LogToMain, source, level, message, data),
|
||||
setFullScreen: (value: boolean): Promise<void> => ipcRenderer.invoke(IpcChannel.App_SetFullScreen, value),
|
||||
isFullScreen: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_IsFullScreen),
|
||||
getSystemFonts: (): Promise<string[]> => ipcRenderer.invoke(IpcChannel.App_GetSystemFonts),
|
||||
mac: {
|
||||
isProcessTrusted: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacIsProcessTrusted),
|
||||
requestProcessTrust: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacRequestProcessTrust)
|
||||
|
||||
@ -1,23 +1,24 @@
|
||||
:root {
|
||||
--font-family:
|
||||
Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, Oxygen, Cantarell, 'Open Sans',
|
||||
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
var(--user-font-family), Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, Oxygen,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
|
||||
--font-family-serif:
|
||||
serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Ubuntu, Roboto, Oxygen, Cantarell, 'Open Sans',
|
||||
'Helvetica Neue', Arial, 'Noto Sans', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
|
||||
--code-font-family: 'Cascadia Code', 'Fira Code', 'Consolas', Menlo, Courier, monospace;
|
||||
--code-font-family: var(--user-code-font-family), 'Cascadia Code', 'Fira Code', 'Consolas', Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
/* Windows系统专用字体配置 */
|
||||
body[os='windows'] {
|
||||
--font-family:
|
||||
'Twemoji Country Flags', Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, Oxygen,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
var(--user-font-family), 'Twemoji Country Flags', Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui,
|
||||
Roboto, Oxygen, Cantarell, 'Open Sans', 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
|
||||
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
|
||||
--code-font-family:
|
||||
'Cascadia Code', 'Fira Code', 'Consolas', 'Sarasa Mono SC', 'Microsoft YaHei UI', Courier, monospace;
|
||||
var(--user-code-font-family), 'Cascadia Code', 'Fira Code', 'Consolas', 'Sarasa Mono SC', 'Microsoft YaHei UI',
|
||||
Courier, monospace;
|
||||
}
|
||||
|
||||
@ -15,6 +15,10 @@ export default function useUserTheme() {
|
||||
document.body.style.setProperty('--primary', colorPrimary.toString())
|
||||
document.body.style.setProperty('--color-primary-soft', colorPrimary.alpha(0.6).toString())
|
||||
document.body.style.setProperty('--color-primary-mute', colorPrimary.alpha(0.3).toString())
|
||||
|
||||
// Set font family CSS variables
|
||||
document.documentElement.style.setProperty('--user-font-family', `'${theme.userFontFamily}'`)
|
||||
document.documentElement.style.setProperty('--user-code-font-family', `'${theme.userCodeFontFamily}'`)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -3116,6 +3116,13 @@
|
||||
"placeholder": "/* Put custom CSS here */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "Code Font",
|
||||
"default": "Default",
|
||||
"global": "Global Font",
|
||||
"select": "Select Font",
|
||||
"title": "Font Settings"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "Navbar Position",
|
||||
|
||||
@ -3117,6 +3117,13 @@
|
||||
"placeholder": "/* 这里写自定义 CSS */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "代码字体",
|
||||
"default": "默认",
|
||||
"global": "全局字体",
|
||||
"select": "选择字体",
|
||||
"title": "字体设置"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "导航栏位置",
|
||||
|
||||
@ -3116,6 +3116,13 @@
|
||||
"placeholder": "/* 這裡寫自訂 CSS */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "[to be translated]:代码字体",
|
||||
"default": "[to be translated]:默认",
|
||||
"global": "[to be translated]:全局字体",
|
||||
"select": "[to be translated]:选择字体",
|
||||
"title": "[to be translated]:字体设置"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "導航欄位置",
|
||||
|
||||
@ -3116,6 +3116,13 @@
|
||||
"placeholder": "/* Γράψτε εδώ την προσαρμοστική CSS */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "[to be translated]:代码字体",
|
||||
"default": "[to be translated]:默认",
|
||||
"global": "[to be translated]:全局字体",
|
||||
"select": "[to be translated]:选择字体",
|
||||
"title": "[to be translated]:字体设置"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "Θέση Γραμμής Πλοήγησης",
|
||||
|
||||
@ -3116,6 +3116,13 @@
|
||||
"placeholder": "/* Escribe tu CSS personalizado aquí */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "[to be translated]:代码字体",
|
||||
"default": "[to be translated]:默认",
|
||||
"global": "[to be translated]:全局字体",
|
||||
"select": "[to be translated]:选择字体",
|
||||
"title": "[to be translated]:字体设置"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "Posición de la barra de navegación",
|
||||
|
||||
@ -3116,6 +3116,13 @@
|
||||
"placeholder": "/* Écrire votre CSS personnalisé ici */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "[to be translated]:代码字体",
|
||||
"default": "[to be translated]:默认",
|
||||
"global": "[to be translated]:全局字体",
|
||||
"select": "[to be translated]:选择字体",
|
||||
"title": "[to be translated]:字体设置"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "Position de la barre de navigation",
|
||||
|
||||
@ -3116,6 +3116,13 @@
|
||||
"placeholder": "/* ここにカスタムCSSを入力 */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "[to be translated]:代码字体",
|
||||
"default": "[to be translated]:默认",
|
||||
"global": "[to be translated]:全局字体",
|
||||
"select": "[to be translated]:选择字体",
|
||||
"title": "[to be translated]:字体设置"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "ナビゲーションバー位置",
|
||||
|
||||
@ -3116,6 +3116,13 @@
|
||||
"placeholder": "/* Escreva seu CSS personalizado aqui */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "[to be translated]:代码字体",
|
||||
"default": "[to be translated]:默认",
|
||||
"global": "[to be translated]:全局字体",
|
||||
"select": "[to be translated]:选择字体",
|
||||
"title": "[to be translated]:字体设置"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "Posição da Barra de Navegação",
|
||||
|
||||
@ -3116,6 +3116,13 @@
|
||||
"placeholder": "/* Здесь введите пользовательский CSS */"
|
||||
}
|
||||
},
|
||||
"font": {
|
||||
"code": "[to be translated]:代码字体",
|
||||
"default": "[to be translated]:默认",
|
||||
"global": "[to be translated]:全局字体",
|
||||
"select": "[to be translated]:选择字体",
|
||||
"title": "[to be translated]:字体设置"
|
||||
},
|
||||
"navbar": {
|
||||
"position": {
|
||||
"label": "Положение навигации",
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
setSidebarIcons
|
||||
} from '@renderer/store/settings'
|
||||
import { ThemeMode } from '@renderer/types'
|
||||
import { Button, ColorPicker, Segmented, Switch } from 'antd'
|
||||
import { Button, ColorPicker, Segmented, Select, Switch } from 'antd'
|
||||
import { Minus, Monitor, Moon, Plus, Sun } from 'lucide-react'
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -78,6 +78,7 @@ const DisplaySettings: FC = () => {
|
||||
|
||||
const [visibleIcons, setVisibleIcons] = useState(sidebarIcons?.visible || DEFAULT_SIDEBAR_ICONS)
|
||||
const [disabledIcons, setDisabledIcons] = useState(sidebarIcons?.disabled || [])
|
||||
const [fontList, setFontList] = useState<string[]>([])
|
||||
|
||||
const handleWindowStyleChange = useCallback(
|
||||
(checked: boolean) => {
|
||||
@ -136,6 +137,11 @@ const DisplaySettings: FC = () => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化获取所有系统字体
|
||||
window.api.getSystemFonts().then((fonts: string[]) => {
|
||||
setFontList(fonts)
|
||||
})
|
||||
|
||||
// 初始化获取当前缩放值
|
||||
window.api.handleZoomFactor(0).then((factor) => {
|
||||
setCurrentZoom(factor)
|
||||
@ -160,6 +166,26 @@ const DisplaySettings: FC = () => {
|
||||
setCurrentZoom(zoomFactor)
|
||||
}
|
||||
|
||||
const handleUserFontChange = useCallback(
|
||||
(value: string) => {
|
||||
setUserTheme({
|
||||
...userTheme,
|
||||
userFontFamily: value
|
||||
})
|
||||
},
|
||||
[setUserTheme, userTheme]
|
||||
)
|
||||
|
||||
const handleUserCodeFontChange = useCallback(
|
||||
(value: string) => {
|
||||
setUserTheme({
|
||||
...userTheme,
|
||||
userCodeFontFamily: value
|
||||
})
|
||||
},
|
||||
[setUserTheme, userTheme]
|
||||
)
|
||||
|
||||
const assistantIconTypeOptions = useMemo(
|
||||
() => [
|
||||
{ value: 'model', label: t('settings.assistant.icon.type.model') },
|
||||
@ -194,6 +220,7 @@ const DisplaySettings: FC = () => {
|
||||
))}
|
||||
</HStack>
|
||||
<ColorPicker
|
||||
style={{ fontFamily: 'inherit' }}
|
||||
className="color-picker"
|
||||
value={userTheme.colorPrimary}
|
||||
onChange={(color) => handleColorPrimaryChange(color.toHexString())}
|
||||
@ -255,6 +282,75 @@ const DisplaySettings: FC = () => {
|
||||
</ZoomButtonGroup>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
<SettingGroup theme={theme}>
|
||||
<SettingTitle style={{ justifyContent: 'flex-start', gap: 5 }}>
|
||||
{t('settings.display.font.title')} <TextBadge text="New" />
|
||||
</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.display.font.global')}</SettingRowTitle>
|
||||
<SelectRow>
|
||||
<Select
|
||||
style={{ width: 200 }}
|
||||
placeholder={t('settings.display.font.select')}
|
||||
options={[
|
||||
{
|
||||
label: (
|
||||
<span style={{ fontFamily: 'Ubuntu, -apple-system, system-ui, Arial, sans-serif' }}>
|
||||
{t('settings.display.font.default')}
|
||||
</span>
|
||||
),
|
||||
value: ''
|
||||
},
|
||||
...fontList.map((font) => ({ label: <span style={{ fontFamily: font }}>{font}</span>, value: font }))
|
||||
]}
|
||||
value={userTheme.userFontFamily || ''}
|
||||
onChange={(font) => handleUserFontChange(font)}
|
||||
showSearch
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentElement || document.body}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleUserFontChange('')}
|
||||
style={{ marginLeft: 8 }}
|
||||
icon={<ResetIcon size="14" />}
|
||||
color="default"
|
||||
variant="text"
|
||||
/>
|
||||
</SelectRow>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.display.font.code')}</SettingRowTitle>
|
||||
<SelectRow>
|
||||
<Select
|
||||
style={{ width: 200 }}
|
||||
placeholder={t('settings.display.font.select')}
|
||||
options={[
|
||||
{
|
||||
label: (
|
||||
<span style={{ fontFamily: 'Ubuntu, -apple-system, system-ui, Arial, sans-serif' }}>
|
||||
{t('settings.display.font.default')}
|
||||
</span>
|
||||
),
|
||||
value: ''
|
||||
},
|
||||
...fontList.map((font) => ({ label: <span style={{ fontFamily: font }}>{font}</span>, value: font }))
|
||||
]}
|
||||
value={userTheme.userCodeFontFamily || ''}
|
||||
onChange={(font) => handleUserCodeFontChange(font)}
|
||||
showSearch
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentElement || document.body}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleUserCodeFontChange('')}
|
||||
style={{ marginLeft: 8 }}
|
||||
icon={<ResetIcon size="14" />}
|
||||
color="default"
|
||||
variant="text"
|
||||
/>
|
||||
</SelectRow>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
<SettingGroup theme={theme}>
|
||||
<SettingTitle>{t('settings.display.topic.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
@ -379,4 +475,11 @@ const ZoomValue = styled.span`
|
||||
margin: 0 5px;
|
||||
`
|
||||
|
||||
const SelectRow = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 300px;
|
||||
`
|
||||
|
||||
export default DisplaySettings
|
||||
|
||||
@ -2451,6 +2451,18 @@ const migrateConfig = {
|
||||
logger.error('migrate 153 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'154': (state: RootState) => {
|
||||
try {
|
||||
if (state.settings.userTheme) {
|
||||
state.settings.userTheme.userFontFamily = settingsInitialState.userTheme.userFontFamily
|
||||
state.settings.userTheme.userCodeFontFamily = settingsInitialState.userTheme.userCodeFontFamily
|
||||
}
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 154 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,8 @@ export type AssistantIconType = 'model' | 'emoji' | 'none'
|
||||
|
||||
export type UserTheme = {
|
||||
colorPrimary: string
|
||||
userFontFamily: string
|
||||
userCodeFontFamily: string
|
||||
}
|
||||
|
||||
export interface SettingsState {
|
||||
@ -242,7 +244,9 @@ export const initialState: SettingsState = {
|
||||
tray: true,
|
||||
theme: ThemeMode.system,
|
||||
userTheme: {
|
||||
colorPrimary: '#00b96b'
|
||||
colorPrimary: '#00b96b',
|
||||
userFontFamily: '',
|
||||
userCodeFontFamily: ''
|
||||
},
|
||||
windowStyle: isMac ? 'transparent' : 'opaque',
|
||||
fontSize: 14,
|
||||
|
||||
@ -13198,6 +13198,7 @@ __metadata:
|
||||
fast-diff: "npm:^1.3.0"
|
||||
fast-xml-parser: "npm:^5.2.0"
|
||||
fetch-socks: "npm:1.3.2"
|
||||
font-list: "npm:^2.0.0"
|
||||
framer-motion: "npm:^12.23.12"
|
||||
franc-min: "npm:^6.2.0"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
@ -18043,6 +18044,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"font-list@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "font-list@npm:2.0.0"
|
||||
checksum: 10c0/9fc8600fa40a5d079982505ea101e49b21260a36f33167ac993fd7b26cec8372a16017c00d6fb404e259600ce8d588830167c9141c2df7dedb0fedd5953905f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"foreground-child@npm:^3.1.0":
|
||||
version: 3.3.1
|
||||
resolution: "foreground-child@npm:3.3.1"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user