diff --git a/src/renderer/src/pages/notes/NotesPage.tsx b/src/renderer/src/pages/notes/NotesPage.tsx index f0bc003bb1..7b95d07e2e 100644 --- a/src/renderer/src/pages/notes/NotesPage.tsx +++ b/src/renderer/src/pages/notes/NotesPage.tsx @@ -4,10 +4,11 @@ import Scrollbar from '@renderer/components/Scrollbar' import { useTheme } from '@renderer/context/ThemeProvider' import { useSettings } from '@renderer/hooks/useSettings' import NotesNavbar from '@renderer/pages/notes/NotesNavbar' +import FileManager from '@renderer/services/FileManager' import { ThemeMode } from '@renderer/types' import { NotesTreeNode } from '@renderer/types/note' import { Empty } from 'antd' -import { FC, useEffect, useRef, useState } from 'react' +import { FC, useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import Vditor from 'vditor' @@ -25,6 +26,37 @@ const NotesPage: FC = () => { const [activeNodeId, setActiveNodeId] = useState(undefined) const [isLoading, setIsLoading] = useState(false) + // 查找树节点 by ID + const findNodeById = useCallback((tree: NotesTreeNode[], nodeId: string): NotesTreeNode | null => { + for (const node of tree) { + if (node.id === nodeId) { + return node + } + if (node.children) { + const found = findNodeById(node.children, nodeId) + if (found) return found + } + } + return null + }, []) + + // 保存当前笔记内容 + const saveCurrentNote = useCallback( + async (content: string) => { + if (!activeNodeId) return + + try { + const activeNode = findNodeById(notesTree, activeNodeId) + if (activeNode && activeNode.type === 'file') { + await NotesService.updateNote(activeNode, content) + } + } catch (error) { + console.error('Failed to save note:', error) + } + }, + [activeNodeId, findNodeById, notesTree] + ) + useEffect(() => { const loadNotesTree = async () => { try { @@ -38,63 +70,79 @@ const NotesPage: FC = () => { loadNotesTree() }, []) - // 初始化编辑器 - 只有在选择笔记后才初始化 useEffect(() => { - if (editorRef.current && !vditor && activeNodeId) { - const editor = new Vditor(editorRef.current, { - height: '100%', - mode: 'ir', - theme: theme === ThemeMode.dark ? 'dark' : 'classic', - toolbar: [ - 'headings', - 'bold', - 'italic', - 'strike', - 'link', - '|', - 'list', - 'ordered-list', - 'check', - 'outdent', - 'indent', - '|', - 'quote', - 'line', - 'code', - 'inline-code', - '|', - 'upload', - 'table', - '|', - 'undo', - 'redo', - '|', - 'fullscreen', - 'preview' - ], - placeholder: t('notes.content_placeholder'), - cache: { - enable: false - }, - after: () => { - setVditor(editor) - }, - input: (value) => { - // 自动保存当前笔记 - if (activeNodeId) { - saveCurrentNote(value) + const initEditor = async () => { + if (editorRef.current && !vditor && activeNodeId) { + const editor = new Vditor(editorRef.current, { + height: '100%', + mode: 'ir', + theme: theme === ThemeMode.dark ? 'dark' : 'classic', + toolbar: [ + 'headings', + 'bold', + 'italic', + 'strike', + 'link', + '|', + 'list', + 'ordered-list', + 'check', + 'outdent', + 'indent', + '|', + 'quote', + 'line', + 'code', + 'inline-code', + '|', + 'upload', + 'table', + '|', + 'undo', + 'redo', + '|', + 'fullscreen', + 'preview' + ], + placeholder: t('notes.content_placeholder'), + cache: { + enable: false + }, + after: async () => { + setVditor(editor) + + // 编辑器初始化完成后,加载笔记内容 + if (activeNodeId) { + try { + const activeNode = findNodeById(notesTree, activeNodeId) + if (activeNode && activeNode.type === 'file') { + const content = await NotesService.readNote(activeNode) + editor.setValue(content) + } + } catch (error) { + console.error('Failed to load note content after editor init:', error) + } + } + }, + input: (value) => { + // 自动保存当前笔记 + if (activeNodeId) { + saveCurrentNote(value) + } } - } - }) + }) + } } + initEditor() + return () => { if (vditor) { vditor.destroy() setVditor(null) } } - }, [theme, activeNodeId, t]) + }, [theme, activeNodeId, t, notesTree, vditor, findNodeById, saveCurrentNote]) // 监听主题变化,更新编辑器样式 useEffect(() => { @@ -107,34 +155,6 @@ const NotesPage: FC = () => { } }, [theme, vditor]) - // 自动保存笔记内容 - const saveCurrentNote = async (content: string) => { - if (!activeNodeId) return - - try { - const activeNode = findNodeById(notesTree, activeNodeId) - if (activeNode && activeNode.type === 'file') { - await NotesService.updateNote(activeNode, content) - } - } catch (error) { - console.error('Failed to save note:', error) - } - } - - // 在树中查找节点 - const findNodeById = (tree: NotesTreeNode[], nodeId: string): NotesTreeNode | null => { - for (const node of tree) { - if (node.id === nodeId) { - return node - } - if (node.children) { - const found = findNodeById(node.children, nodeId) - if (found) return found - } - } - return null - } - // 创建文件夹 const handleCreateFolder = async (name: string, parentId?: string) => { try { @@ -153,7 +173,13 @@ const NotesPage: FC = () => { const handleCreateNote = async (name: string, parentId?: string) => { try { setIsLoading(true) - const newNote = await NotesService.createNote(name, '', parentId) + + let noteName = name + if (!noteName.toLowerCase().endsWith('.md')) { + noteName += '.md' + } + + const newNote = await NotesService.createNote(noteName, '', parentId) const updatedTree = await NotesService.getNotesTree() setNotesTree(updatedTree) @@ -173,10 +199,20 @@ const NotesPage: FC = () => { setIsLoading(true) setActiveNodeId(node.id) - // 读取笔记内容 - const content = await NotesService.readNote(node) + if (node.fileId) { + const updatedFileMetadata = await FileManager.getFile(node.fileId) + if (updatedFileMetadata && updatedFileMetadata.origin_name !== node.name) { + // 如果数据库中的显示名称与树节点中的名称不同,更新树节点 + const updatedTree = [...notesTree] + const updatedNode = findNodeById(updatedTree, node.id) + if (updatedNode) { + updatedNode.name = updatedFileMetadata.origin_name + setNotesTree(updatedTree) + } + } + } - // 如果编辑器已初始化,则更新内容 + const content = await NotesService.readNote(node) if (vditor) { vditor.setValue(content) } diff --git a/src/renderer/src/pages/notes/utils/NotesService.ts b/src/renderer/src/pages/notes/utils/NotesService.ts index b7b53493a4..bfd6e5fde9 100644 --- a/src/renderer/src/pages/notes/utils/NotesService.ts +++ b/src/renderer/src/pages/notes/utils/NotesService.ts @@ -1,9 +1,11 @@ +import db from '@renderer/databases' import FileManager from '@renderer/services/FileManager' -import { FileTypes } from '@renderer/types/file' +import { FileMetadata, FileTypes } from '@renderer/types' import { NotesTreeNode } from '@renderer/types/note' import { v4 as uuidv4 } from 'uuid' const NOTES_FOLDER_PREFIX = 'notes' +const MARKDOWN_EXT = '.md' export class NotesService { private static readonly NOTES_STORAGE_KEY = 'notes-tree-structure' @@ -14,16 +16,79 @@ export class NotesService { static async getNotesTree(): Promise { try { const storedTree = localStorage.getItem(this.NOTES_STORAGE_KEY) - if (storedTree) { - return JSON.parse(storedTree) - } - return [] + const tree: NotesTreeNode[] = storedTree ? JSON.parse(storedTree) : [] + + await this.syncFileNames(tree) + + return tree } catch (error) { console.error('Failed to get notes tree:', error) return [] } } + /** + * 同步所有文件节点的名称 + */ + private static async syncFileNames(tree: NotesTreeNode[]): Promise { + // 收集所有文件ID + const fileIds: string[] = [] + this.collectFileIds(tree, fileIds) + + if (fileIds.length === 0) return + + try { + const filesMetadata = await Promise.all(fileIds.map((id) => FileManager.getFile(id))) + const metadataMap = new Map(filesMetadata.filter((file) => file !== null).map((file) => [file!.id, file])) + const hasChanges = this.updateFileNames(tree, metadataMap) + + if (hasChanges) { + await this.saveNotesTree(tree) + } + } catch (error) { + console.error('Failed to sync file names:', error) + } + } + + /** + * 收集树中所有文件节点的ID + */ + private static collectFileIds(tree: NotesTreeNode[], fileIds: string[]): void { + for (const node of tree) { + if (node.type === 'file' && node.fileId) { + fileIds.push(node.fileId) + } + if (node.children && node.children.length > 0) { + this.collectFileIds(node.children, fileIds) + } + } + } + + /** + * 更新树中的文件名称 + * @returns 是否有名称更新 + */ + private static updateFileNames(tree: NotesTreeNode[], metadataMap: Map): boolean { + let hasChanges = false + + for (const node of tree) { + if (node.type === 'file' && node.fileId) { + const metadata = metadataMap.get(node.fileId) + if (metadata && metadata.origin_name !== node.name) { + node.name = metadata.origin_name + node.updatedAt = new Date().toISOString() + hasChanges = true + } + } + if (node.children && node.children.length > 0) { + const childChanges = this.updateFileNames(node.children, metadataMap) + hasChanges = hasChanges || childChanges + } + } + + return hasChanges + } + /** * 保存笔记树结构 */ @@ -62,37 +127,43 @@ export class NotesService { /** * 创建新笔记文件 + * 只允许创建Markdown格式的文件,以noteId.md的格式存储 */ static async createNote(name: string, content: string = '', parentId?: string): Promise { const noteId = uuidv4() const notePath = this.buildPath(name, parentId) - try { - // 创建临时文件并写入内容 - const tempPath = await window.api.file.createTempFile(noteId) - await window.api.file.write(tempPath, content) + // 确保文件名是markdown格式 + let displayName = name + if (!displayName.toLowerCase().endsWith(MARKDOWN_EXT)) { + displayName += MARKDOWN_EXT + } - // 通过FileManager上传文件 - const fileMetadata = await FileManager.uploadFile({ + try { + const fileMetadata: FileMetadata = { id: noteId, - name, - origin_name: name, - path: tempPath, + name: noteId + MARKDOWN_EXT, + origin_name: displayName, + path: notePath, size: content.length, - ext: '.md', + ext: MARKDOWN_EXT, type: FileTypes.TEXT, created_at: new Date().toISOString(), count: 1 - }) + } + await window.api.file.writeWithId(fileMetadata.id + fileMetadata.ext, content) + await FileManager.addFile(fileMetadata) + + // 创建树节点 const note: NotesTreeNode = { id: noteId, - name, + name: displayName, type: 'file', path: notePath, - fileId: fileMetadata.id, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() + fileId: noteId, + createdAt: fileMetadata.created_at, + updatedAt: fileMetadata.created_at } const tree = await this.getNotesTree() @@ -115,8 +186,12 @@ export class NotesService { } try { - // 直接使用文件ID读取 - return await window.api.file.read(node.fileId) + const fileMetadata = await FileManager.getFile(node.fileId) + if (!fileMetadata) { + throw new Error('Note file not found in database') + } + + return await window.api.file.read(fileMetadata.id + fileMetadata.ext) } catch (error) { console.error('Failed to read note:', error) throw error @@ -132,9 +207,17 @@ export class NotesService { } try { - await window.api.file.writeWithId(node.fileId, content) + const fileMetadata = await FileManager.getFile(node.fileId) + if (!fileMetadata) { + throw new Error('Note file not found in database') + } + + await window.api.file.writeWithId(fileMetadata.id + fileMetadata.ext, content) + await db.files.update(fileMetadata.id, { + size: content.length, + count: fileMetadata.count + 1 + }) - // 更新树结构中的修改时间 const tree = await this.getNotesTree() const targetNode = this.findNodeInTree(tree, node.id) if (targetNode) { @@ -159,10 +242,7 @@ export class NotesService { } try { - // 递归删除所有子节点的文件 await this.deleteNodeRecursively(node) - - // 从树结构中移除节点 this.removeNodeFromTree(tree, nodeId) await this.saveNotesTree(tree) } catch (error) { @@ -182,9 +262,33 @@ export class NotesService { throw new Error('Node not found') } - node.name = newName + // 为文件类型自动添加.md后缀 + let finalName = newName + if (node.type === 'file' && !finalName.toLowerCase().endsWith(MARKDOWN_EXT)) { + finalName += MARKDOWN_EXT + } + + // 更新节点名称 + node.name = finalName node.updatedAt = new Date().toISOString() + // 如果是文件类型,还需要更新文件记录 + if (node.type === 'file' && node.fileId) { + try { + // 获取文件元数据 + const fileMetadata = await FileManager.getFile(node.fileId) + if (fileMetadata) { + // 更新文件的原始名称(显示名称) + await db.files.update(node.fileId, { + origin_name: finalName + }) + } + } catch (error) { + console.error('Failed to update file metadata:', error) + throw error + } + } + await this.saveNotesTree(tree) } @@ -225,10 +329,8 @@ export class NotesService { throw new Error('Node not found') } - // 从当前位置移除 this.removeNodeFromTree(tree, nodeId) - // 插入到新位置 this.insertNodeIntoTree(tree, node, newParentId) await this.saveNotesTree(tree) @@ -309,10 +411,12 @@ export class NotesService { */ private static async deleteNodeRecursively(node: NotesTreeNode): Promise { if (node.type === 'file' && node.fileId) { - // 删除文件 - await FileManager.deleteFile(node.fileId) + try { + await FileManager.deleteFile(node.fileId, true) + } catch (error) { + console.error(`Failed to delete file with id ${node.fileId}:`, error) + } } else if (node.type === 'folder' && node.children) { - // 递归删除子节点 for (const child of node.children) { await this.deleteNodeRecursively(child) }