feat: hierarchical notebook system - trash, selectors, breadcrumb, sidebar tree
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m9s

- Schema: soft delete with trashedAt on Notebook model
- API: PATCH/GET notebooks support trashedAt filtering with cascade
- Sidebar: recursive tree rendering with collapse/expand, visual guides, hover actions
- HierarchicalNotebookSelector: portal-based dropdown with search, breadcrumbs, dropUp support
- AI chat: context selector with Toutes mes notes + notebook selector
- Agent detail: flat selects replaced with HierarchicalNotebookSelector
- Breadcrumb: notebook path display on home page
- Trash view: card grid with countdown, restore/permanent delete
- CSS: design tokens (ink, paper, blueprint, concrete, etc.)
- Types: parentId, trashedAt added to Notebook interface
This commit is contained in:
Antigravity
2026-05-10 10:52:26 +00:00
parent 539c72cf6d
commit 916fb78dfb
20 changed files with 1319 additions and 391 deletions

View File

@@ -1,6 +1,6 @@
'use client'
import { useState, useMemo, useRef, useCallback, useEffect } from 'react'
import React, { useState, useMemo, useRef, useCallback, useEffect } from 'react'
import { motion } from 'motion/react'
import {
ArrowLeft,
@@ -30,6 +30,7 @@ import {
BookOpen,
LifeBuoy,
} from 'lucide-react'
import { HierarchicalNotebookSelector } from '@/components/hierarchical-notebook-selector'
import { toast } from 'sonner'
import { useLanguage } from '@/lib/i18n'
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
@@ -488,21 +489,12 @@ export function AgentDetailView({
{showSourceNotebook && (
<div className="space-y-4">
<label className={labelCls}>{t('agents.form.sourceNotebook')}<FieldHelp tooltip={t('agents.help.tooltips.sourceNotebook')} /></label>
<select
value={sourceNotebookId}
onChange={e => { setSourceNotebookId(e.target.value); setSourceNoteIds([]) }}
className={selectCls}
>
<option value="">{t('agents.form.selectNotebook')}</option>
{notebooks.filter(nb => !nb.parentId).map(nb => (
<>
<option key={nb.id} 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>
))}
</>
))}
</select>
<HierarchicalNotebookSelector
notebooks={notebooks.filter((nb: any) => !nb.trashedAt)}
selectedId={sourceNotebookId || null}
onSelect={(id) => { setSourceNotebookId(id); setSourceNoteIds([]) }}
placeholder={t('agents.form.selectNotebook') || 'Select notebook...'}
/>
</div>
)}
@@ -648,17 +640,12 @@ export function AgentDetailView({
{type !== 'slide-generator' && type !== 'excalidraw-generator' && (
<div className="space-y-4">
<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}>
<option value="">{t('agents.form.inbox')}</option>
{notebooks.filter(nb => !nb.parentId).map(nb => (
<>
<option key={nb.id} 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>
))}
</>
))}
</select>
<HierarchicalNotebookSelector
notebooks={notebooks.filter((nb: any) => !nb.trashedAt)}
selectedId={targetNotebookId || null}
onSelect={(id) => setTargetNotebookId(id)}
placeholder={t('agents.form.inbox') || 'Inbox'}
/>
</div>
)}