Files
Momento/memento-note/components/note-editor/note-content-area.tsx
Antigravity 91b1201112 refactor: split NoteEditor into focused components + consolidate contexts
Phase 1: NoteEditor Split (64KB → 9 focused components)
- components/note-editor/: types.ts, context, toolbar, title-block,
  content-area, metadata-section, full-page, dialog compositions
- Maintains backwards compatibility via re-export from note-editor.tsx

Phase 2: Context Consolidation (5 → 3 contexts)
- NotebooksContext absorbs LabelContext (labels CRUD)
- EditorUIContext merges HomeViewContext + NotebookDragContext
- Removed: LabelContext, home-view-context, notebook-drag-context

Phase 3: React Query Infrastructure
- Added QueryProvider with @tanstack/react-query
- lib/query-keys.ts: centralized query key definitions
- lib/query-hooks.ts: useNotes, useNotebooksQuery, useLabelsQuery
- lib/use-refresh.ts: hybrid invalidateQueries + triggerRefresh helper
- NotebooksContext: invalidateQueries on mutations (with triggerRefresh fallback)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 14:31:08 +00:00

179 lines
6.2 KiB
TypeScript

'use client'
import { useNoteEditorContext } from './note-editor-context'
import { RichTextEditor } from '@/components/rich-text-editor'
import { MarkdownContent } from '@/components/markdown-content'
import { MarkdownSlashCommands } from '@/components/markdown-slash-commands'
import { GhostTags } from '@/components/ghost-tags'
import { Textarea } from '@/components/ui/textarea'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { X, Plus } from 'lucide-react'
import { useLanguage } from '@/lib/i18n'
import { cn } from '@/lib/utils'
export function NoteContentArea() {
const { state, actions, readOnly, fullPage, textareaRef } = useNoteEditorContext()
const { t } = useLanguage()
const uploadImageFile = async (file: File) => {
const formData = new FormData()
formData.append('file', file)
const response = await fetch('/api/upload', { method: 'POST', body: formData })
if (!response.ok) throw new Error('Upload failed')
const data = await response.json()
return data.url
}
if (state.noteType === 'richtext') {
if (fullPage) {
return (
<div className="fullpage-editor">
<RichTextEditor
content={state.content}
onChange={(v: string) => actions.setContent(v)}
className="min-h-[280px]"
onImageUpload={uploadImageFile}
/>
</div>
)
}
return (
<RichTextEditor
content={state.content}
onChange={actions.setContent}
className="min-h-[200px]"
onImageUpload={uploadImageFile}
/>
)
}
if (state.noteType === 'markdown' && state.showMarkdownPreview) {
return (
<div
className={cn(
'min-h-[280px] cursor-text prose prose-lg dark:prose-invert max-w-none leading-relaxed',
fullPage ? '' : 'p-3 rounded-md border border-border/40 bg-muted/20'
)}
onClick={() => !readOnly && actions.setShowMarkdownPreview(false)}
>
<MarkdownContent content={state.content} />
{!readOnly && (
<p className="text-[11px] text-foreground/30 mt-8 select-none not-prose italic">
Cliquez pour éditer
</p>
)}
</div>
)
}
if (state.noteType === 'markdown' || state.noteType === 'text') {
if (fullPage) {
return (
<div className="relative">
<textarea
ref={textareaRef}
dir="auto"
placeholder={t('notes.takeNote') || "Commencez à écrire… tapez '/' pour les commandes"}
value={state.content}
onFocus={() => actions.setShowMarkdownPreview(false)}
onChange={(e) => actions.setContent(e.target.value)}
disabled={readOnly}
className="w-full min-h-[280px] border-0 outline-none px-0 bg-transparent editor-body leading-relaxed resize-none overflow-hidden placeholder:text-foreground/30 text-foreground"
/>
{!readOnly && (
<MarkdownSlashCommands
textareaRef={textareaRef as React.RefObject<HTMLTextAreaElement>}
value={state.content}
onChange={(v: string) => actions.setContent(v)}
/>
)}
</div>
)
}
// Dialog mode
return (
<div className="space-y-2">
<Textarea
dir="auto"
placeholder={state.isMarkdown ? t('notes.takeNoteMarkdown') : t('notes.takeNote')}
value={state.content}
onChange={(e) => actions.setContent(e.target.value)}
disabled={readOnly}
className={cn(
"min-h-[200px] border-0 focus-visible:ring-0 px-0 bg-transparent resize-none text-sm leading-relaxed",
readOnly && "cursor-default"
)}
/>
<GhostTags
suggestions={state.filteredSuggestions}
addedTags={state.labels}
isAnalyzing={state.isAnalyzingSuggestions}
onSelectTag={actions.handleSelectGhostTag}
onDismissTag={actions.handleDismissGhostTag}
/>
</div>
)
}
// Checklist mode
if (fullPage) {
return (
<div className="space-y-2">
{state.checkItems.map((item) => (
<div key={item.id} className="flex items-start gap-2 group">
<Checkbox
checked={item.checked}
onCheckedChange={() => actions.handleCheckItem(item.id)}
className="mt-2"
/>
<Input
value={item.text}
onChange={(e) => actions.handleUpdateCheckItem(item.id, e.target.value)}
placeholder={t('notes.listItem')}
className="flex-1 border-0 focus-visible:ring-0 px-0 bg-transparent"
/>
<Button variant="ghost" size="sm" className="opacity-0 group-hover:opacity-100 h-8 w-8 p-0"
onClick={() => actions.handleRemoveCheckItem(item.id)}>
<X className="h-4 w-4" />
</Button>
</div>
))}
<Button variant="ghost" size="sm" onClick={actions.handleAddCheckItem} className="text-gray-600 dark:text-gray-400">
<Plus className="h-4 w-4 mr-1" />
{t('notes.addItem')}
</Button>
</div>
)
}
return (
<div className="space-y-2">
{state.checkItems.map((item) => (
<div key={item.id} className="flex items-start gap-2 group">
<Checkbox
checked={item.checked}
onCheckedChange={() => actions.handleCheckItem(item.id)}
className="mt-2"
/>
<Input
value={item.text}
onChange={(e) => actions.handleUpdateCheckItem(item.id, e.target.value)}
placeholder={t('notes.listItem')}
className="flex-1 border-0 focus-visible:ring-0 px-0 bg-transparent"
/>
<Button variant="ghost" size="sm" className="opacity-0 group-hover:opacity-100 h-8 w-8 p-0"
onClick={() => actions.handleRemoveCheckItem(item.id)}>
<X className="h-4 w-4" />
</Button>
</div>
))}
<Button variant="ghost" size="sm" onClick={actions.handleAddCheckItem} className="text-gray-600 dark:text-gray-400">
<Plus className="h-4 w-4 mr-1" />
{t('notes.addItem')}
</Button>
</div>
)
}