diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 6747d8d..33efd2f 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -15,7 +15,8 @@
"Bash(npx electron-vite *)",
"WebSearch",
"WebFetch(domain:primevue.org)",
- "Bash(curl -s http://localhost:5173)"
+ "Bash(curl -s http://localhost:5173)",
+ "Bash(curl -s http://localhost:5173/)"
]
}
}
diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue
index fffe277..27952fb 100644
--- a/src/renderer/src/App.vue
+++ b/src/renderer/src/App.vue
@@ -61,6 +61,8 @@ async function onNavigate(id) {
if (id === 'trash') {
await notesStore.loadNotes({ isTrashed: 1 })
+ } else if (id === 'favorites') {
+ await notesStore.loadNotes({ isTrashed: 0, isFavorited: 1 })
} else {
await notesStore.loadNotes({ isTrashed: 0 })
}
@@ -71,6 +73,15 @@ function onSelectNote(note) {
notesStore.getNote(note.id)
}
+async function onToggleFavorite(note) {
+ await notesStore.toggleFavorite(note.id)
+ // If we're in the favorites view, reload to remove unfavorited notes
+ if (currentNav.value === 'favorites') {
+ await notesStore.loadNotes({ isTrashed: 0, isFavorited: 1 })
+ computeOverviewNotes()
+ }
+}
+
function onBackToOverview() {
notesStore.clearCurrentNote()
// Reload notes to reflect any changes
@@ -241,6 +252,7 @@ async function onWorkspaceConfirm(path) {
:notes="overviewNotes"
:title="getOverviewTitle()"
@select-note="onSelectNote"
+ @toggle-favorite="onToggleFavorite"
/>
diff --git a/src/renderer/src/components/NotesOverview.vue b/src/renderer/src/components/NotesOverview.vue
index b3519a3..34a3901 100644
--- a/src/renderer/src/components/NotesOverview.vue
+++ b/src/renderer/src/components/NotesOverview.vue
@@ -3,14 +3,14 @@ import { ref, computed } from 'vue'
import Button from 'primevue/button'
import Tag from 'primevue/tag'
import ScrollPanel from 'primevue/scrollpanel'
-import { Search, LayoutGrid, List, FileText, Clock, Calendar, Folder } from '@lucide/vue'
+import { Search, LayoutGrid, List, FileText, Clock, Calendar, Folder, Star } from '@lucide/vue'
const props = defineProps({
notes: { type: Array, required: true },
title: { type: String, default: '所有笔记' }
})
-const emit = defineEmits(['select-note'])
+const emit = defineEmits(['select-note', 'toggle-favorite'])
const viewMode = ref('card') // 'card' | 'list'
@@ -18,6 +18,11 @@ function selectNote(note) {
emit('select-note', note)
}
+function toggleFavorite(note, event) {
+ event.stopPropagation()
+ emit('toggle-favorite', note)
+}
+
function formatDate(dateStr) {
if (!dateStr) return ''
const d = new Date(dateStr)
@@ -104,9 +109,16 @@ function getPreview(note) {
-
+
{{ note.title || '无标题' }}
+
@@ -150,9 +162,18 @@ function getPreview(note) {
-
- {{ note.title || '无标题' }}
-
+
+
+ {{ note.title || '无标题' }}
+
+
+
{{ getPreview(note) }}
diff --git a/src/renderer/src/components/SideBar.vue b/src/renderer/src/components/SideBar.vue
index ecb1c32..907b2ad 100644
--- a/src/renderer/src/components/SideBar.vue
+++ b/src/renderer/src/components/SideBar.vue
@@ -17,13 +17,19 @@ import {
import {useThemeStore} from '../stores/theme'
import {useNotesStore} from '../stores/notes'
import {useNotebooksStore} from '../stores/notebooks'
-import {useNotebookTree} from '../composables/useNotebookTree'
+import {useSidebarTree} from '../composables/useSidebarTree'
import ButtonIcon from "./ButtonIcon.vue";
const themeStore = useThemeStore()
const notesStore = useNotesStore()
const notebooksStore = useNotebooksStore()
-const {notebookTree, flatNotebooks, expandedKeys, loadNotebooks, loadNotes} = useNotebookTree()
+
+const props = defineProps({
+ notebooks: {type: Array, default: () => []},
+ tags: {type: Array, default: () => []}
+})
+
+const {sidebarTree, flatNotebooks, expandedKeys, toggleNodeExpanded, loadNotebooks, loadNotes} = useSidebarTree(computed(() => props.tags))
const theme = computed(() => themeStore.theme)
const toggleTheme = themeStore.toggleTheme
@@ -34,11 +40,6 @@ const newNotebookParentName = computed(() => {
return nb ? nb.name : ''
})
-const props = defineProps({
- notebooks: {type: Array, default: () => []},
- tags: {type: Array, default: () => []}
-})
-
const emit = defineEmits(['navigate', 'create-note', 'select-notebook', 'select-tag', 'select-note'])
// Selected tree node
@@ -60,17 +61,6 @@ function toggleCollapsed() {
// Navigation state
const activeNav = ref('all')
-// Section expand state
-const expandedSections = ref({
- notes: true,
- notebooks: true,
- tags: true
-})
-
-function toggleSection(section) {
- expandedSections.value[section] = !expandedSections.value[section]
-}
-
// Search
const searchQuery = ref('')
const searchResults = ref([])
@@ -88,18 +78,6 @@ async function onSearchInput() {
}, 200)
}
-// Navigation items
-const navSections = [
- {
- id: 'notes',
- items: [
- {id: 'all', icon: File, label: '所有笔记'},
- {id: 'favorites', icon: Star, label: '收藏夹'},
- {id: 'trash', icon: Trash2, label: '回收站'},
- ]
- }
-]
-
// Methods
function handleNav(id) {
activeNav.value = id
@@ -116,14 +94,23 @@ function selectNotebook(notebook) {
* PrimeVue v4 passes node directly (not wrapped in event object)
*/
function onTreeSelect(node) {
- if (node.data.type === 'notebook') {
+ const { type, id } = node.data
+
+ if (type === 'nav') {
selectedTreeKey.value = node.key
- activeNav.value = `notebook-${node.data.id}`
+ handleNav(id)
+ } else if (type === 'notebook') {
+ selectedTreeKey.value = node.key
+ activeNav.value = `notebook-${id}`
emit('select-notebook', node.data)
- } else if (node.data.type === 'note') {
+ } else if (type === 'note') {
selectedTreeKey.value = node.key
- activeNav.value = `note-${node.data.id}`
+ 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)
}
}
@@ -218,6 +205,29 @@ function getNotebookName(note) {
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 ''
+}
@@ -278,15 +288,30 @@ function getNotebookName(note) {
+
-
+
+
+
+
+
+
+
@@ -319,145 +344,103 @@ function getNotebookName(note) {
-
+
-
-
-
+
-
-
+
+
-
-
{{ item.label }}
+
+ {{ slotProps.node.label }}
+
+
-
-
-
-
-
-
-
-
-
-
-
- {{ slotProps.node.label }}1
- 1
-
-
-
-
-
-
-
-
-
-
-
- 标签
-
-
-
-
-
+
+
+ {{ slotProps.node.label }}
+
+
+
+
+ {{ slotProps.node.label }}
+
+
+
+
+
+
+ {{ slotProps.node.label }}
+
+
+
+
-
{{ tag.name }}
-
{{
- tag.note_count || 0
- }}
+
{{ slotProps.node.label }}
+
+ {{ slotProps.node.data.note_count || 0 }}
+
-
-
+
+
+
@@ -556,44 +539,56 @@ function getNotebookName(note) {
max-height: 500px;
}
-/* Custom styles for notebook tree */
-.notebook-tree :deep(.p-tree) {
+/* Custom styles for sidebar tree */
+.sidebar-tree :deep(.p-tree) {
padding: 0;
}
-.notebook-tree :deep(.p-tree-node) {
+.sidebar-tree :deep(.p-tree-node) {
padding: 0;
+ width: 100%;
}
-.notebook-tree :deep(.p-tree-node-content) {
+.sidebar-tree :deep(.p-tree-node-content) {
padding: 0.25rem 0.5rem;
border-radius: 0.5rem;
transition: background-color 0.2s;
min-width: 0;
+ width: 100%;
}
-.notebook-tree :deep(.p-tree-node-content:hover) {
+.sidebar-tree :deep(.p-tree-node-content:hover) {
background-color: var(--surface-100);
}
-.notebook-tree :deep(.p-tree-node-selected) {
+:root.dark .sidebar-tree :deep(.p-tree-node-content:hover) {
+ background-color: var(--surface-800);
+}
+
+.sidebar-tree :deep(.p-tree-node-selected) {
/* Handled by pt nodeContent dynamic class */
}
-.notebook-tree :deep(.p-tree-toggler) {
+.sidebar-tree :deep(.p-tree-toggler) {
width: 1.5rem;
height: 1.5rem;
margin-right: 0;
}
-.notebook-tree :deep(.p-tree-node-icon) {
- margin-right: 0.5rem;
-}
-
-.notebook-tree :deep(.p-tree-node-label) {
+.sidebar-tree :deep(.p-tree-node-label) {
font-size: 0.875rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+ width: 100%;
+}
+
+/* Group nodes should not show hover effect */
+.sidebar-tree :deep(.p-tree-node-content:has(.text-xs.font-semibold)) {
+ cursor: default;
+}
+
+.sidebar-tree :deep(.p-tree-node-content:has(.text-xs.font-semibold):hover) {
+ background-color: transparent;
}