@@ -417,131 +546,7 @@ const DataSettings: FC = () => {
}
handleDataMigration()
- }, [])
-
- // 显示进度模态框
- const showProgressModal = (title: React.ReactNode, className: string, PathsContent: React.FC) => {
- let currentProgress = 0
- let progressInterval: NodeJS.Timeout | null = null
-
- // 创建进度更新模态框
- const loadingModal = window.modal.info({
- title,
- className,
- width: 'min(600px, 90vw)',
- style: { minHeight: '400px' },
- icon:
,
- content: (
-
-
-
- {t('settings.data.app_data.copying')}
-
-
- {t('settings.data.app_data.copying_warning')}
-
-
-
- ),
- centered: true,
- closable: false,
- maskClosable: false,
- okButtonProps: { style: { display: 'none' } }
- })
-
- // 更新进度的函数
- const updateProgress = (progress: number, status: 'active' | 'success' = 'active') => {
- loadingModal.update({
- title,
- content: (
-
-
-
- {t('settings.data.app_data.copying')}
-
-
- {t('settings.data.app_data.copying_warning')}
-
-
-
- )
- })
- }
-
- // 开始模拟进度更新
- progressInterval = setInterval(() => {
- if (currentProgress < 95) {
- currentProgress += Math.random() * 5 + 1
- if (currentProgress > 95) currentProgress = 95
- updateProgress(currentProgress)
- }
- }, 500)
-
- return { loadingModal, progressInterval, updateProgress }
- }
-
- // 开始迁移数据
- const startMigration = async (
- originalPath: string,
- newPath: string,
- progressInterval: NodeJS.Timeout | null,
- updateProgress: (progress: number, status?: 'active' | 'success') => void,
- loadingModal: { destroy: () => void },
- messageKey: string
- ): Promise
=> {
- // flush app data
- await window.api.flushAppData()
-
- // wait 2 seconds to flush app data
- await new Promise((resolve) => setTimeout(resolve, 2000))
-
- // 开始复制过程
- const copyResult = await window.api.copy(originalPath, newPath)
-
- // 停止进度更新
- if (progressInterval) {
- clearInterval(progressInterval)
- }
-
- // 显示100%完成
- updateProgress(100, 'success')
-
- if (!copyResult.success) {
- // 延迟关闭加载模态框
- await new Promise((resolve) => {
- setTimeout(() => {
- loadingModal.destroy()
- window.message.error({
- content: t('settings.data.app_data.copy_failed') + ': ' + copyResult.error,
- key: messageKey,
- duration: 5
- })
- resolve()
- }, 500)
- })
-
- throw new Error(copyResult.error || 'Unknown error during copy')
- }
-
- // 在复制成功后设置新的AppDataPath
- await window.api.setAppDataPath(newPath)
-
- // 短暂延迟以显示100%完成
- await new Promise((resolve) => setTimeout(resolve, 500))
-
- // 关闭加载模态框
- loadingModal.destroy()
-
- window.message.success({
- content: t('settings.data.app_data.copy_success'),
- key: messageKey,
- duration: 2
- })
- }
+ }, [t])
const onSkipBackupFilesChange = (value: boolean) => {
setSkipBackupFile(value)
diff --git a/src/renderer/src/pages/settings/DataSettings/JoplinSettings.tsx b/src/renderer/src/pages/settings/DataSettings/JoplinSettings.tsx
index 3574c808d..32b314854 100644
--- a/src/renderer/src/pages/settings/DataSettings/JoplinSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/JoplinSettings.tsx
@@ -4,7 +4,7 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { RootState, useAppDispatch } from '@renderer/store'
import { setJoplinExportReasoning, setJoplinToken, setJoplinUrl } from '@renderer/store/settings'
-import { Button, Switch, Tooltip } from 'antd'
+import { Button, Space, Switch, Tooltip } from 'antd'
import Input from 'antd/es/input/Input'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
@@ -106,14 +106,15 @@ const JoplinSettings: FC = () => {
-
-
+
+
+
+
diff --git a/src/renderer/src/pages/settings/DataSettings/NotionSettings.tsx b/src/renderer/src/pages/settings/DataSettings/NotionSettings.tsx
index 719a2363d..26e8d0872 100644
--- a/src/renderer/src/pages/settings/DataSettings/NotionSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/NotionSettings.tsx
@@ -10,7 +10,7 @@ import {
setNotionExportReasoning,
setNotionPageNameKey
} from '@renderer/store/settings'
-import { Button, Switch, Tooltip } from 'antd'
+import { Button, Space, Switch, Tooltip } from 'antd'
import Input from 'antd/es/input/Input'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
@@ -121,15 +121,16 @@ const NotionSettings: FC = () => {
{t('settings.data.notion.api_key')}
-
-
+
+
+
+
diff --git a/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx b/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx
index 15207200b..108452c13 100644
--- a/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx
@@ -1,6 +1,7 @@
import { CheckOutlined, FolderOutlined, LoadingOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
import NutstorePathPopup from '@renderer/components/Popups/NutsorePathPopup'
+import Selector from '@renderer/components/Selector'
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
import { useWebdavBackupModal, WebdavBackupModal } from '@renderer/components/WebdavModals'
import { useTheme } from '@renderer/context/ThemeProvider'
@@ -23,7 +24,7 @@ import {
} from '@renderer/store/nutstore'
import { modalConfirm } from '@renderer/utils'
import { NUTSTORE_HOST } from '@shared/config/nutstore'
-import { Button, Input, Select, Switch, Tooltip, Typography } from 'antd'
+import { Button, Input, Switch, Tooltip, Typography } from 'antd'
import dayjs from 'dayjs'
import { FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -279,18 +280,23 @@ const NutstoreSettings: FC = () => {
{t('settings.data.webdav.autoSync')}
-
+
{nutstoreAutoSync && syncInterval > 0 && (
<>
diff --git a/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx b/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx
index 3ba6673ee..2681f1305 100644
--- a/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx
@@ -4,7 +4,7 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { RootState, useAppDispatch } from '@renderer/store'
import { setSiyuanApiUrl, setSiyuanBoxId, setSiyuanRootPath, setSiyuanToken } from '@renderer/store/settings'
-import { Button, Tooltip } from 'antd'
+import { Button, Space, Tooltip } from 'antd'
import Input from 'antd/es/input/Input'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
@@ -108,14 +108,15 @@ const SiyuanSettings: FC = () => {
-
-
+
+
+
+
diff --git a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
index 8e2a7e5aa..54db33f02 100644
--- a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx
@@ -1,5 +1,6 @@
import { FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
+import Selector from '@renderer/components/Selector'
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
import { useWebdavBackupModal, WebdavBackupModal } from '@renderer/components/WebdavModals'
import { useTheme } from '@renderer/context/ThemeProvider'
@@ -16,7 +17,7 @@ import {
setWebdavSyncInterval as _setWebdavSyncInterval,
setWebdavUser as _setWebdavUser
} from '@renderer/store/settings'
-import { Button, Input, Select, Switch, Tooltip } from 'antd'
+import { Button, Input, Switch, Tooltip } from 'antd'
import dayjs from 'dayjs'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -173,31 +174,43 @@ const WebDavSettings: FC = () => {
{t('settings.data.webdav.autoSync')}
-
+
{t('settings.data.webdav.maxBackups')}
-
+
diff --git a/src/renderer/src/pages/settings/DataSettings/YuqueSettings.tsx b/src/renderer/src/pages/settings/DataSettings/YuqueSettings.tsx
index 72a629e55..60a8d6ef7 100644
--- a/src/renderer/src/pages/settings/DataSettings/YuqueSettings.tsx
+++ b/src/renderer/src/pages/settings/DataSettings/YuqueSettings.tsx
@@ -4,7 +4,7 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { RootState, useAppDispatch } from '@renderer/store'
import { setYuqueRepoId, setYuqueToken, setYuqueUrl } from '@renderer/store/settings'
-import { Button, Tooltip } from 'antd'
+import { Button, Space, Tooltip } from 'antd'
import Input from 'antd/es/input/Input'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
@@ -100,14 +100,15 @@ const YuqueSettings: FC = () => {
-
-
+
+
+
+
diff --git a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx
index 48c8dcbe9..27453ef1c 100644
--- a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx
+++ b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx
@@ -196,7 +196,7 @@ const DisplaySettings: FC = () => {
value={userTheme.colorPrimary}
onChange={(color) => handleColorPrimaryChange(color.toHexString())}
showText
- style={{ width: '110px' }}
+ size="small"
presets={[
{
label: 'Presets',
@@ -222,13 +222,15 @@ const DisplaySettings: FC = () => {
{t('settings.zoom.title')}
-
@@ -313,9 +315,9 @@ const DisplaySettings: FC = () => {
language="css"
placeholder={t('settings.display.custom.css.placeholder')}
onChange={(value) => dispatch(setCustomCss(value))}
- height="350px"
+ height="60vh"
options={{
- collapsible: true,
+ collapsible: false,
wrappable: true,
autocompletion: true,
lineNumbers: true,
diff --git a/src/renderer/src/pages/settings/GeneralSettings.tsx b/src/renderer/src/pages/settings/GeneralSettings.tsx
index aea88cfdd..411fdd476 100644
--- a/src/renderer/src/pages/settings/GeneralSettings.tsx
+++ b/src/renderer/src/pages/settings/GeneralSettings.tsx
@@ -1,14 +1,22 @@
+import Selector from '@renderer/components/Selector'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n'
import { RootState, useAppDispatch } from '@renderer/store'
-import { setEnableDataCollection, setLanguage, setNotificationSettings } from '@renderer/store/settings'
-import { setProxyMode, setProxyUrl as _setProxyUrl } from '@renderer/store/settings'
+import {
+ setEnableDataCollection,
+ setEnableSpellCheck,
+ setLanguage,
+ setNotificationSettings,
+ setProxyMode,
+ setProxyUrl as _setProxyUrl,
+ setSpellCheckLanguages
+} from '@renderer/store/settings'
import { LanguageVarious } from '@renderer/types'
import { NotificationSource } from '@renderer/types/notification'
import { isValidProxyUrl } from '@renderer/utils'
import { defaultLanguage } from '@shared/config/constant'
-import { Input, Select, Space, Switch } from 'antd'
+import { Flex, Input, Switch } from 'antd'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
@@ -26,7 +34,8 @@ const GeneralSettings: FC = () => {
trayOnClose,
tray,
proxyMode: storeProxyMode,
- enableDataCollection
+ enableDataCollection,
+ enableSpellCheck
} = useSettings()
const [proxyUrl, setProxyUrl] = useState(storeProxyUrl)
const { theme } = useTheme()
@@ -69,6 +78,11 @@ const GeneralSettings: FC = () => {
i18n.changeLanguage(value)
}
+ const handleSpellCheckChange = (checked: boolean) => {
+ dispatch(setEnableSpellCheck(checked))
+ window.api.setEnableSpellCheck(checked)
+ }
+
const onSetProxyUrl = () => {
if (proxyUrl && !isValidProxyUrl(proxyUrl)) {
window.message.error({ content: t('message.error.invalid.proxy.url'), key: 'proxy-error' })
@@ -79,7 +93,7 @@ const GeneralSettings: FC = () => {
window.api.setProxy(proxyUrl)
}
- const proxyModeOptions = [
+ const proxyModeOptions: { value: 'system' | 'custom' | 'none'; label: string }[] = [
{ value: 'system', label: t('settings.proxy.mode.system') },
{ value: 'custom', label: t('settings.proxy.mode.custom') },
{ value: 'none', label: t('settings.proxy.mode.none') }
@@ -109,11 +123,30 @@ const GeneralSettings: FC = () => {
]
const notificationSettings = useSelector((state: RootState) => state.settings.notification)
+ const spellCheckLanguages = useSelector((state: RootState) => state.settings.spellCheckLanguages)
const handleNotificationChange = (type: NotificationSource, value: boolean) => {
dispatch(setNotificationSettings({ ...notificationSettings, [type]: value }))
}
+ // Define available spell check languages with display names (only commonly supported languages)
+ const spellCheckLanguageOptions = [
+ { value: 'en-US', label: 'English (US)', flag: '🇺🇸' },
+ { value: 'es', label: 'Español', flag: '🇪🇸' },
+ { value: 'fr', label: 'Français', flag: '🇫🇷' },
+ { value: 'de', label: 'Deutsch', flag: '🇩🇪' },
+ { value: 'it', label: 'Italiano', flag: '🇮🇹' },
+ { value: 'pt', label: 'Português', flag: '🇵🇹' },
+ { value: 'ru', label: 'Русский', flag: '🇷🇺' },
+ { value: 'nl', label: 'Nederlands', flag: '🇳🇱' },
+ { value: 'pl', label: 'Polski', flag: '🇵🇱' }
+ ]
+
+ const handleSpellCheckLanguagesChange = (selectedLanguages: string[]) => {
+ dispatch(setSpellCheckLanguages(selectedLanguages))
+ window.api.setSpellCheckLanguages(selectedLanguages)
+ }
+
return (
@@ -121,28 +154,58 @@ const GeneralSettings: FC = () => {
{t('common.language')}
-
+ {lang.label}
+
+ ),
+ value: lang.value
+ }))}
+ />
+
+ {t('settings.general.spell_check')}
+
+
+ {enableSpellCheck && (
+ <>
+
+
+ {t('settings.general.spell_check.languages')}
+
+ size={14}
+ multiple
+ value={spellCheckLanguages}
+ placeholder={t('settings.general.spell_check.languages')}
+ onChange={handleSpellCheckLanguagesChange}
+ options={spellCheckLanguageOptions.map((lang) => ({
+ value: lang.value,
+ label: (
+
+
+ {lang.flag}
+
+ {lang.label}
+
+ )
+ }))}
+ />
+
+ >
+ )}
+
{t('settings.proxy.mode.title')}
-
+
{storeProxyMode === 'custom' && (
<>
diff --git a/src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx b/src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx
index 77ad41fb5..0e505b384 100644
--- a/src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx
+++ b/src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx
@@ -3,7 +3,7 @@ import { TopView } from '@renderer/components/TopView'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setMCPServers } from '@renderer/store/mcp'
import { MCPServer } from '@renderer/types'
-import { Modal, Typography } from 'antd'
+import { Modal, Spin, Typography } from 'antd'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -16,12 +16,14 @@ const PopupContainer: React.FC = ({ resolve }) => {
const [jsonConfig, setJsonConfig] = useState('')
const [jsonSaving, setJsonSaving] = useState(false)
const [jsonError, setJsonError] = useState('')
+ const [isLoading, setIsLoading] = useState(true)
const mcpServers = useAppSelector((state) => state.mcp.servers)
const dispatch = useAppDispatch()
const { t } = useTranslation()
useEffect(() => {
+ setIsLoading(true)
try {
const mcpServersObj: Record = {}
@@ -40,6 +42,8 @@ const PopupContainer: React.FC = ({ resolve }) => {
} catch (error) {
console.error('Failed to format JSON:', error)
setJsonError(t('settings.mcp.jsonFormatError'))
+ } finally {
+ setIsLoading(false)
}
}, [mcpServers, t])
@@ -118,24 +122,24 @@ const PopupContainer: React.FC = ({ resolve }) => {
{jsonError ? {jsonError} : ''}
- {jsonConfig && (
-