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.
This commit is contained in:
自由的世界人 2025-07-17 16:13:22 +08:00
parent 6bdb157af3
commit e2d3d7357d
10 changed files with 176 additions and 9 deletions

View File

@ -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 {
<Route path="/files" element={<FilesPage />} />
<Route path="/knowledge" element={<KnowledgePage />} />
<Route path="/apps" element={<AppsPage />} />
<Route path="/notes" element={<NotesPage />} />
<Route path="/settings/*" element={<SettingsPage />} />
</Routes>
</HashRouter>

View File

@ -22,6 +22,7 @@ import {
LayoutGrid,
MessageSquare,
Moon,
NotepadText,
Palette,
Settings,
Sparkle,
@ -155,7 +156,8 @@ const MainMenus: FC = () => {
translate: <Languages size={18} className="icon" />,
minapp: <LayoutGrid size={18} className="icon" />,
knowledge: <FileSearch size={18} className="icon" />,
files: <Folder size={17} className="icon" />
files: <Folder size={18} className="icon" />,
notes: <NotepadText size={18} className="icon" />
}
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) => {

View File

@ -2558,6 +2558,9 @@
"select_embedding_model_placeholder": "选择嵌入模型",
"embedding_dimensions": "嵌入维度",
"stored_memories": "已存储记忆"
},
"notes": {
"title": "笔记"
}
}
}

View File

@ -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 (
<Container id="notes-page">
<Navbar>
<NavbarCenter style={{ borderRight: 'none', gap: 10 }}></NavbarCenter>
</Navbar>
<ContentContainer>
<h1></h1>
<p></p>
</ContentContainer>
</Container>
)
}
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

View File

@ -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<SidebarIconsManagerProps> = ({
translate: <Languages size={16} />,
minapp: <LayoutGrid size={16} />,
knowledge: <FileSearch size={16} />,
files: <Folder size={15} />
files: <Folder size={16} />,
notes: <NotepadText size={16} />
}),
[]
)
@ -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`

View File

@ -56,7 +56,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
version: 121,
version: 122,
blacklist: ['runtime', 'messages', 'messageBlocks'],
migrate
},

View File

@ -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
}
}

View File

@ -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 {}

View File

@ -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[]

View File

@ -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'
}