feat: show notebook tree hierarchy in note move, tabs, and agent config
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m32s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m32s
- note-card.tsx: parent/child notebook dropdown with indentation - notes-tabs-view.tsx: tree-structured notebook picker with check marks - agent-detail-view.tsx: source/target notebook selects with optgroup tree - Add notebook-tree-select.tsx shared utility component
This commit is contained in:
@@ -97,7 +97,7 @@ interface AgentDetailViewProps {
|
|||||||
_count: { actions: number }
|
_count: { actions: number }
|
||||||
actions: { id: string; status: string; createdAt: string | Date }[]
|
actions: { id: string; status: string; createdAt: string | Date }[]
|
||||||
} | null
|
} | null
|
||||||
notebooks: { id: string; name: string; icon?: string | null }[]
|
notebooks: { id: string; name: string; icon?: string | null; parentId?: string | null }[]
|
||||||
onSave: (data: FormData) => Promise<void>
|
onSave: (data: FormData) => Promise<void>
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
onOpenLogs: (agentId: string, agentName: string) => void
|
onOpenLogs: (agentId: string, agentName: string) => void
|
||||||
@@ -494,8 +494,13 @@ export function AgentDetailView({
|
|||||||
className={selectCls}
|
className={selectCls}
|
||||||
>
|
>
|
||||||
<option value="">{t('agents.form.selectNotebook')}</option>
|
<option value="">{t('agents.form.selectNotebook')}</option>
|
||||||
{notebooks.map(nb => (
|
{notebooks.filter(nb => !nb.parentId).map(nb => (
|
||||||
<option key={nb.id} value={nb.id}>{nb.name}</option>
|
<optgroup key={nb.id} label={nb.name}>
|
||||||
|
<option value={nb.id}>{nb.name}</option>
|
||||||
|
{notebooks.filter(c => c.parentId === nb.id).map(child => (
|
||||||
|
<option key={child.id} value={child.id}> └ {child.name}</option>
|
||||||
|
))}
|
||||||
|
</optgroup>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -645,8 +650,13 @@ export function AgentDetailView({
|
|||||||
<label className={labelCls}>{t('agents.form.targetNotebook')}<FieldHelp tooltip={t('agents.help.tooltips.targetNotebook')} /></label>
|
<label className={labelCls}>{t('agents.form.targetNotebook')}<FieldHelp tooltip={t('agents.help.tooltips.targetNotebook')} /></label>
|
||||||
<select value={targetNotebookId} onChange={e => setTargetNotebookId(e.target.value)} className={selectCls}>
|
<select value={targetNotebookId} onChange={e => setTargetNotebookId(e.target.value)} className={selectCls}>
|
||||||
<option value="">{t('agents.form.inbox')}</option>
|
<option value="">{t('agents.form.inbox')}</option>
|
||||||
{notebooks.map(nb => (
|
{notebooks.filter(nb => !nb.parentId).map(nb => (
|
||||||
<option key={nb.id} value={nb.id}>{nb.name}</option>
|
<optgroup key={nb.id} label={nb.name}>
|
||||||
|
<option value={nb.id}>{nb.name}</option>
|
||||||
|
{notebooks.filter(c => c.parentId === nb.id).map(child => (
|
||||||
|
<option key={child.id} value={child.id}> └ {child.name}</option>
|
||||||
|
))}
|
||||||
|
</optgroup>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -504,18 +504,27 @@ export const NoteCard = memo(function NoteCard({
|
|||||||
<StickyNote className="h-4 w-4 mr-2" />
|
<StickyNote className="h-4 w-4 mr-2" />
|
||||||
{t('notebookSuggestion.generalNotes')}
|
{t('notebookSuggestion.generalNotes')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{notebooks.map((notebook: any) => {
|
{notebooks.filter(nb => !nb.parentId).map((notebook: any) => {
|
||||||
const NotebookIcon = getNotebookIcon(notebook.icon || 'folder')
|
const NotebookIcon = getNotebookIcon(notebook.icon || 'folder')
|
||||||
return (
|
const children = notebooks.filter((c: any) => c.parentId === notebook.id)
|
||||||
<DropdownMenuItem
|
return (
|
||||||
key={notebook.id}
|
<div key={notebook.id}>
|
||||||
onClick={() => handleMoveToNotebook(notebook.id)}
|
<DropdownMenuItem onClick={() => handleMoveToNotebook(notebook.id)}>
|
||||||
>
|
<NotebookIcon className="h-4 w-4 mr-2" />
|
||||||
<NotebookIcon className="h-4 w-4 mr-2" />
|
{notebook.name}
|
||||||
{notebook.name}
|
</DropdownMenuItem>
|
||||||
</DropdownMenuItem>
|
{children.map((child: any) => {
|
||||||
)
|
const ChildIcon = getNotebookIcon(child.icon || 'folder')
|
||||||
})}
|
return (
|
||||||
|
<DropdownMenuItem key={child.id} onClick={() => handleMoveToNotebook(child.id)} className="pl-8">
|
||||||
|
<ChildIcon className="h-4 w-4 mr-2" />
|
||||||
|
└ {child.name}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>}
|
</div>}
|
||||||
|
|||||||
94
memento-note/components/notebook-tree-select.tsx
Normal file
94
memento-note/components/notebook-tree-select.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Notebook } from '@/lib/types'
|
||||||
|
|
||||||
|
interface NotebookTreeSelectProps {
|
||||||
|
notebooks: Notebook[]
|
||||||
|
value: string | null
|
||||||
|
onChange: (notebookId: string | null) => void
|
||||||
|
placeholder?: string
|
||||||
|
inboxLabel?: string
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildNotebookTree(notebooks: Notebook[]): { roots: Notebook[]; children: Map<string, Notebook[]> } {
|
||||||
|
const roots: Notebook[] = []
|
||||||
|
const children = new Map<string, Notebook[]>()
|
||||||
|
for (const nb of notebooks) {
|
||||||
|
if (nb.parentId) {
|
||||||
|
const arr = children.get(nb.parentId) || []
|
||||||
|
arr.push(nb)
|
||||||
|
children.set(nb.parentId, arr)
|
||||||
|
} else {
|
||||||
|
roots.push(nb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { roots, children }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotebookTreeSelect({ notebooks, value, onChange, placeholder, inboxLabel, className }: NotebookTreeSelectProps) {
|
||||||
|
const { roots, children } = buildNotebookTree(notebooks)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={value || ''}
|
||||||
|
onChange={e => onChange(e.target.value || null)}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{placeholder && <option value="">{placeholder}</option>}
|
||||||
|
{inboxLabel && <option value="">{inboxLabel}</option>}
|
||||||
|
{roots.map(nb => (
|
||||||
|
<optgroup key={nb.id} label={nb.name}>
|
||||||
|
<option value={nb.id}>{nb.name}</option>
|
||||||
|
{(children.get(nb.id) || []).map(child => (
|
||||||
|
<option key={child.id} value={child.id}> └ {child.name}</option>
|
||||||
|
))}
|
||||||
|
</optgroup>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotebookTreeList({ notebooks, activeNotebookId, onSelect, inboxLabel }: {
|
||||||
|
notebooks: Notebook[]
|
||||||
|
activeNotebookId: string | null
|
||||||
|
onSelect: (notebookId: string | null) => void
|
||||||
|
inboxLabel?: string
|
||||||
|
}) {
|
||||||
|
const { roots, children } = buildNotebookTree(notebooks)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{inboxLabel && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onSelect(null)}
|
||||||
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-[12px] font-medium hover:bg-muted transition-colors text-foreground/70"
|
||||||
|
>
|
||||||
|
{activeNotebookId === null ? '✓ ' : ' '}{inboxLabel}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{roots.map(nb => (
|
||||||
|
<div key={nb.id}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onSelect(nb.id)}
|
||||||
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-[12px] font-medium hover:bg-muted transition-colors text-foreground/70"
|
||||||
|
>
|
||||||
|
{activeNotebookId === nb.id ? '✓ ' : ' '}{nb.name}
|
||||||
|
</button>
|
||||||
|
{(children.get(nb.id) || []).map(child => (
|
||||||
|
<button
|
||||||
|
key={child.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => onSelect(child.id)}
|
||||||
|
className="flex w-full items-center gap-2 rounded pl-6 pr-2 py-1.5 text-[12px] font-medium hover:bg-muted transition-colors text-foreground/70"
|
||||||
|
>
|
||||||
|
{activeNotebookId === child.id ? '✓ ' : ' '}└ {child.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -547,23 +547,36 @@ function NoteMetaSidebar({
|
|||||||
: <span className="h-3 w-3 shrink-0" />}
|
: <span className="h-3 w-3 shrink-0" />}
|
||||||
{t('notes.generalNotes')}
|
{t('notes.generalNotes')}
|
||||||
</button>
|
</button>
|
||||||
{notebooks.map((nb) => (
|
{notebooks.filter(nb => !nb.parentId).map((nb) => (
|
||||||
<button
|
<div key={nb.id}>
|
||||||
key={nb.id}
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleMoveToNotebook(nb.id)}
|
onClick={() => handleMoveToNotebook(nb.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-2 rounded px-2 py-1.5 text-[12px] font-medium hover:bg-muted transition-colors',
|
'flex w-full items-center gap-2 rounded px-2 py-1.5 text-[12px] font-medium hover:bg-muted transition-colors',
|
||||||
note.notebookId === nb.id ? 'text-primary' : 'text-foreground/70'
|
note.notebookId === nb.id ? 'text-primary' : 'text-foreground/70'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{note.notebookId === nb.id
|
{note.notebookId === nb.id ? <Check className="h-3 w-3 shrink-0" /> : <span className="h-3 w-3 shrink-0" />}
|
||||||
? <Check className="h-3 w-3 shrink-0" />
|
{nb.name}
|
||||||
: <span className="h-3 w-3 shrink-0" />}
|
</button>
|
||||||
{nb.name}
|
{notebooks.filter(c => c.parentId === nb.id).map(child => (
|
||||||
</button>
|
<button
|
||||||
))}
|
key={child.id}
|
||||||
</PopoverContent>
|
type="button"
|
||||||
|
onClick={() => handleMoveToNotebook(child.id)}
|
||||||
|
className={cn(
|
||||||
|
'flex w-full items-center gap-2 rounded pl-6 pr-2 py-1.5 text-[12px] font-medium hover:bg-muted transition-colors',
|
||||||
|
note.notebookId === child.id ? 'text-primary' : 'text-foreground/70'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{note.notebookId === child.id ? <Check className="h-3 w-3 shrink-0" /> : <span className="h-3 w-3 shrink-0" />}
|
||||||
|
└ {child.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
{/* Pin / Unpin */}
|
{/* Pin / Unpin */}
|
||||||
|
|||||||
Reference in New Issue
Block a user