feat: 功能迭代
This commit is contained in:
@@ -15,7 +15,8 @@
|
|||||||
"Bash(npx electron-vite *)",
|
"Bash(npx electron-vite *)",
|
||||||
"WebSearch",
|
"WebSearch",
|
||||||
"WebFetch(domain:primevue.org)",
|
"WebFetch(domain:primevue.org)",
|
||||||
"Bash(curl -s http://localhost:5173)"
|
"Bash(curl -s http://localhost:5173)",
|
||||||
|
"Bash(curl -s http://localhost:5173/)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ async function onNavigate(id) {
|
|||||||
|
|
||||||
if (id === 'trash') {
|
if (id === 'trash') {
|
||||||
await notesStore.loadNotes({ isTrashed: 1 })
|
await notesStore.loadNotes({ isTrashed: 1 })
|
||||||
|
} else if (id === 'favorites') {
|
||||||
|
await notesStore.loadNotes({ isTrashed: 0, isFavorited: 1 })
|
||||||
} else {
|
} else {
|
||||||
await notesStore.loadNotes({ isTrashed: 0 })
|
await notesStore.loadNotes({ isTrashed: 0 })
|
||||||
}
|
}
|
||||||
@@ -71,6 +73,15 @@ function onSelectNote(note) {
|
|||||||
notesStore.getNote(note.id)
|
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() {
|
function onBackToOverview() {
|
||||||
notesStore.clearCurrentNote()
|
notesStore.clearCurrentNote()
|
||||||
// Reload notes to reflect any changes
|
// Reload notes to reflect any changes
|
||||||
@@ -241,6 +252,7 @@ async function onWorkspaceConfirm(path) {
|
|||||||
:notes="overviewNotes"
|
:notes="overviewNotes"
|
||||||
:title="getOverviewTitle()"
|
:title="getOverviewTitle()"
|
||||||
@select-note="onSelectNote"
|
@select-note="onSelectNote"
|
||||||
|
@toggle-favorite="onToggleFavorite"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Editor Panel (when a note is selected) -->
|
<!-- Editor Panel (when a note is selected) -->
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { ref, computed } from 'vue'
|
|||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import Tag from 'primevue/tag'
|
import Tag from 'primevue/tag'
|
||||||
import ScrollPanel from 'primevue/scrollpanel'
|
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({
|
const props = defineProps({
|
||||||
notes: { type: Array, required: true },
|
notes: { type: Array, required: true },
|
||||||
title: { type: String, default: '所有笔记' }
|
title: { type: String, default: '所有笔记' }
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['select-note'])
|
const emit = defineEmits(['select-note', 'toggle-favorite'])
|
||||||
|
|
||||||
const viewMode = ref('card') // 'card' | 'list'
|
const viewMode = ref('card') // 'card' | 'list'
|
||||||
|
|
||||||
@@ -18,6 +18,11 @@ function selectNote(note) {
|
|||||||
emit('select-note', note)
|
emit('select-note', note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleFavorite(note, event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
emit('toggle-favorite', note)
|
||||||
|
}
|
||||||
|
|
||||||
function formatDate(dateStr) {
|
function formatDate(dateStr) {
|
||||||
if (!dateStr) return ''
|
if (!dateStr) return ''
|
||||||
const d = new Date(dateStr)
|
const d = new Date(dateStr)
|
||||||
@@ -104,9 +109,16 @@ function getPreview(note) {
|
|||||||
<!-- Card Header -->
|
<!-- Card Header -->
|
||||||
<div class="flex items-start gap-2 mb-3">
|
<div class="flex items-start gap-2 mb-3">
|
||||||
<FileText :size="18" class="text-primary-400 shrink-0 mt-0.5" />
|
<FileText :size="18" class="text-primary-400 shrink-0 mt-0.5" />
|
||||||
<h3 class="text-sm font-semibold text-surface-900 line-clamp-2 group-hover:text-primary-600 transition-colors">
|
<h3 class="flex-1 text-sm font-semibold text-surface-900 line-clamp-2 group-hover:text-primary-600 transition-colors">
|
||||||
{{ note.title || '无标题' }}
|
{{ note.title || '无标题' }}
|
||||||
</h3>
|
</h3>
|
||||||
|
<button
|
||||||
|
class="shrink-0 p-0.5 rounded transition-colors"
|
||||||
|
:class="note.is_favorited ? 'text-amber-400 hover:text-amber-500' : 'text-surface-300 hover:text-amber-400 opacity-0 group-hover:opacity-100'"
|
||||||
|
@click="toggleFavorite(note, $event)"
|
||||||
|
>
|
||||||
|
<Star :size="16" :fill="note.is_favorited ? 'currentColor' : 'none'" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Preview -->
|
<!-- Preview -->
|
||||||
@@ -150,9 +162,18 @@ function getPreview(note) {
|
|||||||
<FileText :size="18" class="text-primary-400 shrink-0" />
|
<FileText :size="18" class="text-primary-400 shrink-0" />
|
||||||
|
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<h3 class="text-sm font-semibold text-surface-900 line-clamp-2 group-hover:text-primary-600 transition-colors">
|
<div class="flex items-center gap-2">
|
||||||
{{ note.title || '无标题' }}
|
<h3 class="text-sm font-semibold text-surface-900 line-clamp-1 group-hover:text-primary-600 transition-colors">
|
||||||
</h3>
|
{{ note.title || '无标题' }}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
class="shrink-0 p-0.5 rounded transition-colors"
|
||||||
|
:class="note.is_favorited ? 'text-amber-400 hover:text-amber-500' : 'text-surface-300 hover:text-amber-400 opacity-0 group-hover:opacity-100'"
|
||||||
|
@click="toggleFavorite(note, $event)"
|
||||||
|
>
|
||||||
|
<Star :size="14" :fill="note.is_favorited ? 'currentColor' : 'none'" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<p class="text-xs text-surface-500 line-clamp-2 mt-0.5">{{ getPreview(note) }}</p>
|
<p class="text-xs text-surface-500 line-clamp-2 mt-0.5">{{ getPreview(note) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,19 @@ import {
|
|||||||
import {useThemeStore} from '../stores/theme'
|
import {useThemeStore} from '../stores/theme'
|
||||||
import {useNotesStore} from '../stores/notes'
|
import {useNotesStore} from '../stores/notes'
|
||||||
import {useNotebooksStore} from '../stores/notebooks'
|
import {useNotebooksStore} from '../stores/notebooks'
|
||||||
import {useNotebookTree} from '../composables/useNotebookTree'
|
import {useSidebarTree} from '../composables/useSidebarTree'
|
||||||
import ButtonIcon from "./ButtonIcon.vue";
|
import ButtonIcon from "./ButtonIcon.vue";
|
||||||
|
|
||||||
const themeStore = useThemeStore()
|
const themeStore = useThemeStore()
|
||||||
const notesStore = useNotesStore()
|
const notesStore = useNotesStore()
|
||||||
const notebooksStore = useNotebooksStore()
|
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 theme = computed(() => themeStore.theme)
|
||||||
const toggleTheme = themeStore.toggleTheme
|
const toggleTheme = themeStore.toggleTheme
|
||||||
@@ -34,11 +40,6 @@ const newNotebookParentName = computed(() => {
|
|||||||
return nb ? nb.name : ''
|
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'])
|
const emit = defineEmits(['navigate', 'create-note', 'select-notebook', 'select-tag', 'select-note'])
|
||||||
|
|
||||||
// Selected tree node
|
// Selected tree node
|
||||||
@@ -60,17 +61,6 @@ function toggleCollapsed() {
|
|||||||
// Navigation state
|
// Navigation state
|
||||||
const activeNav = ref('all')
|
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
|
// Search
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const searchResults = ref([])
|
const searchResults = ref([])
|
||||||
@@ -88,18 +78,6 @@ async function onSearchInput() {
|
|||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation items
|
|
||||||
const navSections = [
|
|
||||||
{
|
|
||||||
id: 'notes',
|
|
||||||
items: [
|
|
||||||
{id: 'all', icon: File, label: '所有笔记'},
|
|
||||||
{id: 'favorites', icon: Star, label: '收藏夹'},
|
|
||||||
{id: 'trash', icon: Trash2, label: '回收站'},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
function handleNav(id) {
|
function handleNav(id) {
|
||||||
activeNav.value = id
|
activeNav.value = id
|
||||||
@@ -116,14 +94,23 @@ function selectNotebook(notebook) {
|
|||||||
* PrimeVue v4 passes node directly (not wrapped in event object)
|
* PrimeVue v4 passes node directly (not wrapped in event object)
|
||||||
*/
|
*/
|
||||||
function onTreeSelect(node) {
|
function onTreeSelect(node) {
|
||||||
if (node.data.type === 'notebook') {
|
const { type, id } = node.data
|
||||||
|
|
||||||
|
if (type === 'nav') {
|
||||||
selectedTreeKey.value = node.key
|
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)
|
emit('select-notebook', node.data)
|
||||||
} else if (node.data.type === 'note') {
|
} else if (type === 'note') {
|
||||||
selectedTreeKey.value = node.key
|
selectedTreeKey.value = node.key
|
||||||
activeNav.value = `note-${node.data.id}`
|
activeNav.value = `note-${id}`
|
||||||
emit('select-note', node.data)
|
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)
|
const nb = props.notebooks.find(n => n.id === note.notebook_id)
|
||||||
return nb ? nb.name : ''
|
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 ''
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -278,15 +288,30 @@ function getNotebookName(note) {
|
|||||||
<!-- Collapsed: Icon-only nav -->
|
<!-- Collapsed: Icon-only nav -->
|
||||||
<template v-if="collapsed">
|
<template v-if="collapsed">
|
||||||
<div class="py-3 flex flex-col items-center gap-1">
|
<div class="py-3 flex flex-col items-center gap-1">
|
||||||
|
<!-- Notes nav items (collapsed) -->
|
||||||
<div
|
<div
|
||||||
v-for="item in navSections[0].items"
|
|
||||||
:key="item.id"
|
|
||||||
class="w-10 h-10 flex items-center justify-center rounded-lg cursor-pointer transition-colors"
|
class="w-10 h-10 flex items-center justify-center rounded-lg cursor-pointer transition-colors"
|
||||||
:class="activeNav === item.id ? 'bg-primary-50 text-primary-600' : 'text-surface-500 hover:bg-surface-100'"
|
:class="activeNav === 'all' ? 'bg-primary-50 text-primary-600' : 'text-surface-500 hover:bg-surface-100'"
|
||||||
@click="handleNav(item.id)"
|
@click="handleNav('all')"
|
||||||
v-tooltip.right="item.label"
|
v-tooltip.right="'所有笔记'"
|
||||||
>
|
>
|
||||||
<component :is="item.icon" :size="18"/>
|
<File :size="18"/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-10 h-10 flex items-center justify-center rounded-lg cursor-pointer transition-colors"
|
||||||
|
:class="activeNav === 'favorites' ? 'bg-primary-50 text-primary-600' : 'text-surface-500 hover:bg-surface-100'"
|
||||||
|
@click="handleNav('favorites')"
|
||||||
|
v-tooltip.right="'收藏夹'"
|
||||||
|
>
|
||||||
|
<Star :size="18"/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="w-10 h-10 flex items-center justify-center rounded-lg cursor-pointer transition-colors"
|
||||||
|
:class="activeNav === 'trash' ? 'bg-primary-50 text-primary-600' : 'text-surface-500 hover:bg-surface-100'"
|
||||||
|
@click="handleNav('trash')"
|
||||||
|
v-tooltip.right="'回收站'"
|
||||||
|
>
|
||||||
|
<Trash2 :size="18"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-8 h-px bg-surface-200 my-2"/>
|
<div class="w-8 h-px bg-surface-200 my-2"/>
|
||||||
@@ -319,145 +344,103 @@ function getNotebookName(note) {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Expanded: Full nav -->
|
<!-- Expanded: Unified Tree -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- Notes Section -->
|
<div class="py-2">
|
||||||
<div class="py-3">
|
<Tree
|
||||||
<div
|
:value="sidebarTree"
|
||||||
class="px-4 py-1.5 flex items-center justify-between cursor-pointer hover:bg-surface-100 transition-colors"
|
:expandedKeys="expandedKeys"
|
||||||
@click="toggleSection('notes')"
|
selectionMode="single"
|
||||||
|
:selectionKeys="selectedTreeKey ? { [selectedTreeKey]: true } : {}"
|
||||||
|
@node-select="onTreeSelect"
|
||||||
|
class="w-full sidebar-tree"
|
||||||
|
:pt="{
|
||||||
|
root: { class: 'border-none bg-transparent w-full !p-0' },
|
||||||
|
node: { class: 'py-0.5 w-full', style: 'width: 100%' },
|
||||||
|
nodeContent: {
|
||||||
|
class: ({ instance }) => [
|
||||||
|
'rounded-lg transition-colors min-w-0 w-full',
|
||||||
|
instance.selected
|
||||||
|
? 'bg-primary-50 text-primary-700 font-medium dark:bg-primary-900/30 dark:text-primary-300'
|
||||||
|
: 'hover:bg-surface-100 dark:hover:bg-surface-800'
|
||||||
|
],
|
||||||
|
style: 'width: 100%'
|
||||||
|
},
|
||||||
|
nodeLabel: { class: 'text-sm truncate' },
|
||||||
|
nodeIcon: { class: 'hidden' },
|
||||||
|
toggler: { class: 'w-6 h-6 shrink-0' }
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<span class="text-xs font-semibold text-surface-400 uppercase tracking-wider">笔记</span>
|
<template #default="slotProps">
|
||||||
<ChevronDown
|
<!-- Group node (non-selectable header) -->
|
||||||
v-if="expandedSections.notes"
|
|
||||||
:size="14"
|
|
||||||
class="text-surface-400 transition-transform"
|
|
||||||
/>
|
|
||||||
<ChevronRight
|
|
||||||
v-else
|
|
||||||
:size="14"
|
|
||||||
class="text-surface-400 transition-transform"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Transition name="collapse">
|
|
||||||
<div v-show="expandedSections.notes">
|
|
||||||
<div
|
<div
|
||||||
v-for="item in navSections[0].items"
|
v-if="slotProps.node.data.type === 'group'"
|
||||||
:key="item.id"
|
class="flex items-center gap-2 py-1 px-1 w-full cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-800 rounded-lg transition-colors"
|
||||||
class="flex items-center gap-3 px-4 py-2 mx-2 cursor-pointer transition-colors rounded-lg"
|
@click="toggleNodeExpanded(slotProps.node.key)"
|
||||||
:class="activeNav === item.id
|
|
||||||
? 'bg-surface-200/70 text-primary-700 font-medium shadow-sm'
|
|
||||||
: 'text-surface-600 hover:bg-surface-100'"
|
|
||||||
@click="handleNav(item.id)"
|
|
||||||
>
|
>
|
||||||
<component :is="item.icon" :size="18"/>
|
<span class="text-xs font-semibold text-surface-400 uppercase tracking-wider">
|
||||||
<span class="text-sm">{{ item.label }}</span>
|
{{ slotProps.node.label }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="slotProps.node.data.id === 'notebooks'"
|
||||||
|
class="w-5 h-5 flex items-center justify-center rounded hover:bg-surface-200 text-surface-400 hover:text-surface-600 transition-colors ml-auto"
|
||||||
|
@click.stop="startNewNotebook"
|
||||||
|
title="新建笔记本"
|
||||||
|
>
|
||||||
|
<Plus :size="12"/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Notebooks Section -->
|
<!-- Nav node (笔记导航项) -->
|
||||||
<div class="py-1">
|
|
||||||
<div class="px-4 py-1.5 flex items-center justify-between">
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-1 cursor-pointer hover:bg-surface-100 transition-colors rounded px-1 py-0.5 -ml-1"
|
|
||||||
@click="toggleSection('notebooks')"
|
|
||||||
>
|
|
||||||
<span class="text-xs font-semibold text-surface-400 uppercase tracking-wider">笔记本</span>
|
|
||||||
<ChevronDown
|
|
||||||
v-if="expandedSections.notebooks"
|
|
||||||
:size="14"
|
|
||||||
class="text-surface-400 transition-transform"
|
|
||||||
/>
|
|
||||||
<ChevronRight
|
|
||||||
v-else
|
|
||||||
:size="14"
|
|
||||||
class="text-surface-400 transition-transform"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="w-6 h-6 flex items-center justify-center rounded hover:bg-surface-200 text-surface-400 hover:text-surface-600 transition-colors"
|
|
||||||
@click.stop="startNewNotebook"
|
|
||||||
title="新建笔记本"
|
|
||||||
>
|
|
||||||
<Plus :size="14"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<Transition name="collapse">
|
|
||||||
<div v-show="expandedSections.notebooks">
|
|
||||||
<Tree
|
|
||||||
:value="notebookTree"
|
|
||||||
:expandedKeys="expandedKeys"
|
|
||||||
selectionMode="single"
|
|
||||||
:selectionKeys="selectedTreeKey ? { [selectedTreeKey]: true } : {}"
|
|
||||||
@node-select="onTreeSelect"
|
|
||||||
class="w-full notebook-tree"
|
|
||||||
:pt="{
|
|
||||||
root: { class: 'border-none bg-transparent w-full' },
|
|
||||||
node: { class: 'py-0.5', style: 'width: 100%' },
|
|
||||||
nodeContent: {
|
|
||||||
class: ({ instance }) => [
|
|
||||||
'rounded-lg transition-colors min-w-0',
|
|
||||||
instance.selected
|
|
||||||
? 'bg-primary-50 text-primary-700 font-medium dark:bg-primary-900/30 dark:text-primary-300'
|
|
||||||
: 'hover:bg-surface-100'
|
|
||||||
],
|
|
||||||
style: 'width: 100%'
|
|
||||||
},
|
|
||||||
nodeLabel: { class: 'text-sm truncate' },
|
|
||||||
toggler: { class: 'w-6 h-6 shrink-0' }
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #default="slotProps">
|
|
||||||
<div class="relative flex items-center justify-between w-full gap-1 py-1 px-1 min-w-0 group/node pr-8">
|
|
||||||
<span class="text-sm truncate flex-1">{{ slotProps.node.label }}1</span>
|
|
||||||
<span>1</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Tree>
|
|
||||||
<Menu ref="notebookMenuRef" :model="notebookMenuItems" :popup="true"/>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tags Section -->
|
|
||||||
<div class="py-1">
|
|
||||||
<div
|
|
||||||
class="px-4 py-1.5 flex items-center justify-between cursor-pointer hover:bg-surface-100 transition-colors"
|
|
||||||
@click="toggleSection('tags')"
|
|
||||||
>
|
|
||||||
<span class="text-xs font-semibold text-surface-400 uppercase tracking-wider">标签</span>
|
|
||||||
<ChevronDown
|
|
||||||
v-if="expandedSections.tags"
|
|
||||||
:size="14"
|
|
||||||
class="text-surface-400 transition-transform"
|
|
||||||
/>
|
|
||||||
<ChevronRight
|
|
||||||
v-else
|
|
||||||
:size="14"
|
|
||||||
class="text-surface-400 transition-transform"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Transition name="collapse">
|
|
||||||
<div v-show="expandedSections.tags">
|
|
||||||
<div
|
<div
|
||||||
v-for="tag in tags"
|
v-else-if="slotProps.node.data.type === 'nav'"
|
||||||
:key="tag.id"
|
class="flex items-center gap-3 py-1.5 px-1 w-full"
|
||||||
class="flex items-center gap-3 px-4 py-2 mx-2 cursor-pointer transition-colors rounded-lg group"
|
>
|
||||||
:class="activeNav === `tag-${tag.id}`
|
<component
|
||||||
? 'bg-amber-50 text-amber-700 font-medium shadow-sm'
|
:is="slotProps.node.data.id === 'all' ? File :
|
||||||
: 'text-surface-600 hover:bg-surface-100'"
|
slotProps.node.data.id === 'favorites' ? Star : Trash2"
|
||||||
@click="selectTag(tag)"
|
:size="18"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">{{ slotProps.node.label }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notebook node -->
|
||||||
|
<div
|
||||||
|
v-else-if="slotProps.node.data.type === 'notebook'"
|
||||||
|
class="relative flex items-center justify-between w-full gap-1 py-1 px-1 min-w-0 group/node pr-8"
|
||||||
|
>
|
||||||
|
<span class="text-sm truncate flex-1">{{ slotProps.node.label }}</span>
|
||||||
|
<button
|
||||||
|
class="absolute right-1 w-6 h-6 flex items-center justify-center rounded hover:bg-surface-200 text-surface-400 hover:text-surface-600 opacity-0 group-hover/node:opacity-100 transition-opacity"
|
||||||
|
@click.stop="toggleNotebookMenu(slotProps.node.data.id, $event)"
|
||||||
|
>
|
||||||
|
<MoreHorizontal :size="14"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Note node -->
|
||||||
|
<div
|
||||||
|
v-else-if="slotProps.node.data.type === 'note'"
|
||||||
|
class="flex items-center gap-2 py-1 px-1 w-full"
|
||||||
|
>
|
||||||
|
<File :size="14" class="text-surface-400 shrink-0"/>
|
||||||
|
<span class="text-sm truncate">{{ slotProps.node.label }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tag node -->
|
||||||
|
<div
|
||||||
|
v-else-if="slotProps.node.data.type === 'tag'"
|
||||||
|
class="flex items-center gap-3 py-1.5 px-1 w-full group/tag"
|
||||||
>
|
>
|
||||||
<div class="w-3 h-3 rounded-full flex-shrink-0 bg-amber-400"/>
|
<div class="w-3 h-3 rounded-full flex-shrink-0 bg-amber-400"/>
|
||||||
<span class="text-sm flex-1">{{ tag.name }}</span>
|
<span class="text-sm flex-1 truncate">{{ slotProps.node.label }}</span>
|
||||||
<span class="text-xs text-surface-400 opacity-0 group-hover:opacity-100 transition-opacity">{{
|
<span class="text-xs text-surface-400 opacity-0 group-hover/tag:opacity-100 transition-opacity">
|
||||||
tag.note_count || 0
|
{{ slotProps.node.data.note_count || 0 }}
|
||||||
}}</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Transition>
|
</Tree>
|
||||||
|
<Menu ref="notebookMenuRef" :model="notebookMenuItems" :popup="true"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ScrollPanel>
|
</ScrollPanel>
|
||||||
@@ -556,44 +539,56 @@ function getNotebookName(note) {
|
|||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom styles for notebook tree */
|
/* Custom styles for sidebar tree */
|
||||||
.notebook-tree :deep(.p-tree) {
|
.sidebar-tree :deep(.p-tree) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebook-tree :deep(.p-tree-node) {
|
.sidebar-tree :deep(.p-tree-node) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebook-tree :deep(.p-tree-node-content) {
|
.sidebar-tree :deep(.p-tree-node-content) {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
min-width: 0;
|
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);
|
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 */
|
/* Handled by pt nodeContent dynamic class */
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebook-tree :deep(.p-tree-toggler) {
|
.sidebar-tree :deep(.p-tree-toggler) {
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notebook-tree :deep(.p-tree-node-icon) {
|
.sidebar-tree :deep(.p-tree-node-label) {
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notebook-tree :deep(.p-tree-node-label) {
|
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user