diff --git a/package-lock.json b/package-lock.json index d2e330f..5470a46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "electron": "^42.3.0", "electron-vite": "^5.0.0", "gray-matter": "^4.0.3", + "lucide-vue-next": "^1.0.0", "nanoid": "^5.1.11", "pinia": "^3.0.4", "primeicons": "^7.0.0", @@ -5478,6 +5479,16 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-vue-next": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-1.0.0.tgz", + "integrity": "sha512-V6SPvx1IHTj/UY+FrIYWV5faISsPSb8BnWSFDxAtezWKvWc9ZZ40PDrdu1/Qb5vg4lHWr1hs1BAMGVGm6V1Xdg==", + "deprecated": "Package deprecated. Please use @lucide/vue instead.", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/package.json b/package.json index aa59034..1441f03 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "electron": "^42.3.0", "electron-vite": "^5.0.0", "gray-matter": "^4.0.3", + "lucide-vue-next": "^1.0.0", "nanoid": "^5.1.11", "pinia": "^3.0.4", "primeicons": "^7.0.0", diff --git a/src/renderer/src/components/LucideIcon.vue b/src/renderer/src/components/LucideIcon.vue new file mode 100644 index 0000000..1097940 --- /dev/null +++ b/src/renderer/src/components/LucideIcon.vue @@ -0,0 +1,42 @@ + + + diff --git a/src/renderer/src/components/SideBar.vue b/src/renderer/src/components/SideBar.vue index 907b2ad..e0af2f2 100644 --- a/src/renderer/src/components/SideBar.vue +++ b/src/renderer/src/components/SideBar.vue @@ -3,22 +3,15 @@ import {ref, computed, onMounted, nextTick} from 'vue' import Button from 'primevue/button' import InputText from 'primevue/inputtext' import Dialog from 'primevue/dialog' -import Menu from 'primevue/menu' import IconField from 'primevue/iconfield' import InputIcon from 'primevue/inputicon' -import ScrollPanel from 'primevue/scrollpanel' -import Tree from 'primevue/tree' -import { - Search, Plus, File, Star, Trash2, - Settings, Sun, Moon, Monitor, - BookOpen, Tag, ChevronDown, ChevronRight, Hash, - MoreHorizontal -} from '@lucide/vue' +import {Plus, File, Sun, Moon, Monitor, Settings} from '@lucide/vue' import {useThemeStore} from '../stores/theme' import {useNotesStore} from '../stores/notes' import {useNotebooksStore} from '../stores/notebooks' -import {useSidebarTree} from '../composables/useSidebarTree' -import ButtonIcon from "./ButtonIcon.vue"; + +import Menu from 'primevue/menu' +import LucideIcon from "./LucideIcon.vue"; const themeStore = useThemeStore() const notesStore = useNotesStore() @@ -29,7 +22,22 @@ const props = defineProps({ tags: {type: Array, default: () => []} }) -const {sidebarTree, flatNotebooks, expandedKeys, toggleNodeExpanded, loadNotebooks, loadNotes} = useSidebarTree(computed(() => props.tags)) +async function loadNotebooks() { + await notebooksStore.loadNotebooks() +} + +async function loadNotes() { + await notesStore.loadNotes() +} + + +const items11 = ref([ + + {label: 'Documents', icon: 'search' }, + {label: '收藏夹', icon: 'search'}, + {label: '标签', icon: 'search'}, + {label: '笔记本', icon: 'search'}, +]); const theme = computed(() => themeStore.theme) const toggleTheme = themeStore.toggleTheme @@ -42,22 +50,11 @@ const newNotebookParentName = computed(() => { const emit = defineEmits(['navigate', 'create-note', 'select-notebook', 'select-tag', 'select-note']) -// Selected tree node -const selectedTreeKey = ref(null) - -// Load notebooks and notes on mount onMounted(async () => { await loadNotebooks() await loadNotes() }) -// Collapse state -const collapsed = ref(false) - -function toggleCollapsed() { - collapsed.value = !collapsed.value -} - // Navigation state const activeNav = ref('all') @@ -78,7 +75,6 @@ async function onSearchInput() { }, 200) } -// Methods function handleNav(id) { activeNav.value = id emit('navigate', id) @@ -89,72 +85,122 @@ function selectNotebook(notebook) { emit('select-notebook', notebook) } -/** - * Handle Tree node selection - * PrimeVue v4 passes node directly (not wrapped in event object) - */ -function onTreeSelect(node) { - const { type, id } = node.data - - if (type === 'nav') { - selectedTreeKey.value = node.key - handleNav(id) - } else if (type === 'notebook') { - selectedTreeKey.value = node.key - activeNav.value = `notebook-${id}` - emit('select-notebook', node.data) - } else if (type === 'note') { - selectedTreeKey.value = node.key - activeNav.value = `note-${id}` - emit('select-note', node.data) - } else if (type === 'tag') { - selectedTreeKey.value = node.key - activeNav.value = `tag-${id}` - emit('select-tag', node.data) - } -} - function selectTag(tag) { activeNav.value = `tag-${tag.id}` emit('select-tag', tag) } +function selectSearchNote(note) { + emit('select-note', note) + searchQuery.value = '' + searchResults.value = [] +} + +function getNotebookName(note) { + if (note.notebook_name) return note.notebook_name + const nb = props.notebooks.find(n => n.id === note.notebook_id) + return nb ? nb.name : '' +} + function createNote(notebookId) { - closeMenu() emit('create-note', notebookId) } -// Notebook actions menu (PrimeVue Menu) -const notebookMenuRef = ref(null) -const activeMenuNotebookId = ref(null) +// Notebook tree for PanelMenu +const notebookTreeItems = computed(() => { + const notebookList = notebooksStore.notebooks + const noteList = notesStore.notes -const notebookMenuItems = computed(() => [ + function buildNotebookChildren(parentId) { + const children = [] + + const childNotebooks = notebookList.filter(nb => (nb.parent_id || null) === parentId) + childNotebooks.sort((a, b) => a.name.localeCompare(b.name)) + childNotebooks.forEach(nb => { + const nbChildren = buildNotebookChildren(nb.id) + children.push({ + key: `notebook-${nb.id}`, + label: nb.name, + icon: 'pi pi-folder', + command: () => selectNotebook(nb), + items: nbChildren.length > 0 ? nbChildren : undefined + }) + }) + + const notebookNotes = noteList.filter(n => n.notebook_id === parentId && !n.is_trashed) + notebookNotes.sort((a, b) => (a.title || '').localeCompare(b.title || '')) + notebookNotes.forEach(note => { + children.push({ + key: `note-${note.id}`, + label: note.title || '无标题', + icon: 'pi pi-file', + command: () => emit('select-note', note) + }) + }) + + return children + } + + const rootNotebooks = notebookList.filter(nb => !nb.parent_id) + rootNotebooks.sort((a, b) => a.name.localeCompare(b.name)) + + return rootNotebooks.map(nb => { + const children = buildNotebookChildren(nb.id) + return { + key: `notebook-${nb.id}`, + label: nb.name, + icon: 'pi pi-folder', + command: () => selectNotebook(nb), + items: children.length > 0 ? children : undefined + } + }) +}) + +// PanelMenu items +const items = computed(() => [ { - label: '新建子笔记本', - icon: 'pi pi-folder', - command: () => startNewNotebook(activeMenuNotebookId.value) + label: '导航', + icon: 'pi pi-compass', + items: [ + { + label: '全部笔记', + icon: 'pi pi-home', + command: () => handleNav('all') + }, + { + label: '收藏', + icon: 'pi pi-star', + command: () => handleNav('favorites') + }, + { + label: '最近编辑', + icon: 'pi pi-clock', + command: () => handleNav('recent') + }, + { + label: '回收站', + icon: 'pi pi-trash', + command: () => handleNav('trash') + } + ] }, { - label: '新建笔记', - icon: 'pi pi-file', - command: () => { - closeMenu(); - emit('create-note', activeMenuNotebookId.value) - } - } + label: '笔记本', + icon: 'pi pi-book', + items: notebookTreeItems.value + }, + ...(props.tags && props.tags.length > 0 ? [{ + label: '标签', + icon: 'pi pi-tag', + items: props.tags.map(tag => ({ + label: tag.name, + icon: 'pi pi-tag', + command: () => selectTag(tag) + })) + }] : []) ]) -function toggleNotebookMenu(notebookId, event) { - event.stopPropagation() - activeMenuNotebookId.value = notebookId - notebookMenuRef.value.toggle(event) -} - -function closeMenu() { - activeMenuNotebookId.value = null -} - -// New notebook creation (supports parent_id for sub-notebooks) +// New notebook dialog const showNewNotebookDialog = ref(false) const newNotebookName = ref('') const newNotebookInputRef = ref(null) @@ -164,7 +210,6 @@ function startNewNotebook(parentId = null) { newNotebookName.value = '' newNotebookParentId.value = parentId showNewNotebookDialog.value = true - closeMenu() nextTick(() => { newNotebookInputRef.value?.$el?.focus() }) @@ -192,74 +237,36 @@ function cancelNewNotebook() { newNotebookName.value = '' newNotebookParentId.value = null } - -function selectSearchNote(note) { - emit('select-note', note) - searchQuery.value = '' - searchResults.value = [] -} - -// Get notebook display name for a note -function getNotebookName(note) { - if (note.notebook_name) return note.notebook_name - const nb = props.notebooks.find(n => n.id === note.notebook_id) - return nb ? nb.name : '' -} - -/** - * Get the active style class based on node type - */ -function getNodeActiveClass(node) { - const { type, id } = node.data - if (type === 'nav') { - return activeNav.value === id - ? 'bg-surface-200/70 text-primary-700 font-medium shadow-sm' - : 'text-surface-600 hover:bg-surface-100' - } - if (type === 'notebook') { - return activeNav.value === `notebook-${id}` - ? 'bg-emerald-50 text-emerald-700 font-medium shadow-sm' - : 'text-surface-600 hover:bg-surface-100' - } - if (type === 'tag') { - return activeNav.value === `tag-${id}` - ? 'bg-amber-50 text-amber-700 font-medium shadow-sm' - : 'text-surface-600 hover:bg-surface-100' - } - return '' -}