From e2d3d7357db1db0c43b696fdbfaac65a988a0986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=87=AA=E7=94=B1=E7=9A=84=E4=B8=96=E7=95=8C=E4=BA=BA?= <3196812536@qq.com> Date: Thu, 17 Jul 2025 16:13:22 +0800 Subject: [PATCH] feat: add notes feature with sidebar integration Introduces a new Notes page and integrates it into the sidebar and routing. Updates sidebar icon types, default icons, and migration logic to support the new 'notes' icon. Adds initial types for notes and folders, and provides a basic NotesPage component. Also updates Chinese locale for notes. --- src/renderer/src/App.tsx | 2 + src/renderer/src/components/app/Sidebar.tsx | 7 +- src/renderer/src/i18n/locales/zh-cn.json | 3 + src/renderer/src/pages/notes/NotesPage.tsx | 30 ++++++ .../DisplaySettings/SidebarIconsManager.tsx | 16 +++- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/migrate.ts | 9 ++ src/renderer/src/store/settings.ts | 13 ++- src/renderer/src/types/index.ts | 10 +- src/renderer/src/types/note.ts | 93 +++++++++++++++++++ 10 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 src/renderer/src/pages/notes/NotesPage.tsx create mode 100644 src/renderer/src/types/note.ts diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index b46910cd65..4cd7703869 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,5 +1,6 @@ import '@renderer/databases' +import NotesPage from '@renderer/pages/notes/NotesPage' import store, { persistor } from '@renderer/store' import { Provider } from 'react-redux' import { HashRouter, Route, Routes } from 'react-router-dom' @@ -43,6 +44,7 @@ function App(): React.ReactElement { } /> } /> } /> + } /> } /> diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index 4e03cee5ff..95f2509183 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -22,6 +22,7 @@ import { LayoutGrid, MessageSquare, Moon, + NotepadText, Palette, Settings, Sparkle, @@ -155,7 +156,8 @@ const MainMenus: FC = () => { translate: , minapp: , knowledge: , - files: + files: , + notes: } const pathMap = { @@ -165,7 +167,8 @@ const MainMenus: FC = () => { translate: '/translate', minapp: '/apps', knowledge: '/knowledge', - files: '/files' + files: '/files', + notes: '/notes' } return sidebarIcons.visible.map((icon) => { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index f520aee3d7..1d1fe0242b 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -2558,6 +2558,9 @@ "select_embedding_model_placeholder": "选择嵌入模型", "embedding_dimensions": "嵌入维度", "stored_memories": "已存储记忆" + }, + "notes": { + "title": "笔记" } } } diff --git a/src/renderer/src/pages/notes/NotesPage.tsx b/src/renderer/src/pages/notes/NotesPage.tsx new file mode 100644 index 0000000000..075c1be69d --- /dev/null +++ b/src/renderer/src/pages/notes/NotesPage.tsx @@ -0,0 +1,30 @@ +import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' +import { FC } from 'react' +import styled from 'styled-components' + +const NotesPage: FC = () => { + return ( + + + 笔记 + + +

笔记页面

+

这里是笔记功能的内容区域。

+
+
+ ) +} + +const Container = styled.div` + flex: 1; +` + +const ContentContainer = styled.div` + height: calc(100vh - var(--navbar-height)); + display: flex; + flex-direction: column; + padding: 20px; +` + +export default NotesPage diff --git a/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx b/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx index 89aa11ca4b..b87fe13506 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx @@ -10,7 +10,16 @@ import { import { useAppDispatch } from '@renderer/store' import { setSidebarIcons } from '@renderer/store/settings' import { message } from 'antd' -import { FileSearch, Folder, Languages, LayoutGrid, MessageSquareQuote, Palette, Sparkle } from 'lucide-react' +import { + FileSearch, + Folder, + Languages, + LayoutGrid, + MessageSquareQuote, + NotepadText, + Palette, + Sparkle +} from 'lucide-react' import { FC, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -115,7 +124,8 @@ const SidebarIconsManager: FC = ({ translate: , minapp: , knowledge: , - files: + files: , + notes: }), [] ) @@ -213,7 +223,7 @@ const IconList = styled.div` border: 1px solid var(--color-border); display: flex; flex-direction: column; - overflow-y: hidden; + overflow-y: auto; ` const IconItem = styled.div` diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index bbb2c7378d..5f8c1d84eb 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -56,7 +56,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 121, + version: 122, blacklist: ['runtime', 'messages', 'messageBlocks'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 71990b05b5..76668c547a 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1812,6 +1812,15 @@ const migrateConfig = { } catch (error) { return state } + }, + '122': (state: RootState) => { + if (state.settings && state.settings.sidebarIcons) { + // Check if 'notes' is not already in visible icons + if (!state.settings.sidebarIcons.visible.includes('notes' as any)) { + state.settings.sidebarIcons.visible = [...state.settings.sidebarIcons.visible, 'notes' as any] + } + } + return state } } diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 1d30d9d9fa..607a83ca43 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -19,7 +19,15 @@ import { RemoteSyncState } from './backup' export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter' | 'Alt+Enter' -export type SidebarIcon = 'assistants' | 'agents' | 'paintings' | 'translate' | 'minapp' | 'knowledge' | 'files' +export type SidebarIcon = + | 'assistants' + | 'agents' + | 'paintings' + | 'translate' + | 'minapp' + | 'knowledge' + | 'files' + | 'notes' export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [ 'assistants', @@ -28,7 +36,8 @@ export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [ 'translate', 'minapp', 'knowledge', - 'files' + 'files', + 'notes' ] export interface NutstoreSyncRuntime extends RemoteSyncState {} diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 633a17dd1d..9783a5a184 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -525,7 +525,15 @@ export interface TranslateHistory { createdAt: string } -export type SidebarIcon = 'assistants' | 'agents' | 'paintings' | 'translate' | 'minapp' | 'knowledge' | 'files' +export type SidebarIcon = + | 'assistants' + | 'agents' + | 'paintings' + | 'translate' + | 'minapp' + | 'knowledge' + | 'files' + | 'notes' export type ExternalToolResult = { mcpTools?: MCPTool[] diff --git a/src/renderer/src/types/note.ts b/src/renderer/src/types/note.ts new file mode 100644 index 0000000000..54c75f1571 --- /dev/null +++ b/src/renderer/src/types/note.ts @@ -0,0 +1,93 @@ +export type NoteType = 'note' | 'folder' + +export interface Note { + id: string + title: string + content: string + type: NoteType + parentId?: string // For folder hierarchy + tags?: string[] + created_at: number + updated_at: number + isStarred?: boolean + isArchived?: boolean + wordCount?: number + readingTime?: number // in minutes + metadata?: { + lastCursorPosition?: number + isFullscreen?: boolean + fontSize?: number + theme?: string + } +} + +export interface NoteFolder { + id: string + name: string + type: 'folder' + parentId?: string + created_at: number + updated_at: number + isExpanded?: boolean + color?: string +} + +export interface NoteFilter { + search?: string + tags?: string[] + isStarred?: boolean + isArchived?: boolean + parentId?: string + dateRange?: { + start: number + end: number + } +} + +export interface NoteSortOption { + field: 'title' | 'created_at' | 'updated_at' | 'wordCount' + order: 'asc' | 'desc' +} + +export interface NoteExportOptions { + format: 'markdown' | 'html' | 'pdf' | 'json' + includeMetadata?: boolean + includeTags?: boolean + includeArchived?: boolean +} + +export interface NoteImportOptions { + format: 'markdown' | 'html' | 'json' + targetFolderId?: string + preserveStructure?: boolean + mergeTags?: boolean +} + +export interface NoteStats { + totalNotes: number + totalFolders: number + totalWords: number + totalReadingTime: number + recentActivity: { + date: string + count: number + }[] +} + +export interface NoteEditorConfig { + theme: 'light' | 'dark' | 'auto' + fontSize: number + fontFamily: string + lineHeight: number + tabSize: number + wordWrap: boolean + showLineNumbers: boolean + enableVim: boolean + enableEmmet: boolean + autoSave: boolean + autoSaveInterval: number // in milliseconds + spellCheck: boolean + typewriterMode: boolean + focusMode: boolean + previewMode: 'split' | 'preview' | 'source' +} \ No newline at end of file