'use client' import { useState, useRef } from 'react' import { useNoteEditorContext } from './note-editor-context' import { LabelManager } from '@/components/label-manager' import { LabelBadge } from '@/components/label-badge' import { GhostTags } from '@/components/ghost-tags' import { EditorImages } from '@/components/editor-images' import { TitleSuggestions } from '@/components/title-suggestions' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { X, Plus, Palette, Image as ImageIcon, Bell, Eye, Link as LinkIcon, Sparkles, Maximize2, Copy, ArrowLeft, ChevronRight, PanelRight, Check, Loader2, Save, MoreHorizontal, Trash2, LogOut, Wand2, Share2, Wind, Paperclip, GraduationCap } from 'lucide-react' import { FlashcardGenerateDialog } from '@/components/flashcards/flashcard-generate-dialog' import { NoteShareDialog } from './note-share-dialog' import { deleteNote, leaveSharedNote } from '@/app/actions/notes' import { emitNoteChange } from '@/lib/note-change-sync' import { useLanguage } from '@/lib/i18n' import { NOTE_COLORS, NoteColor, Note } from '@/lib/types' import { cn } from '@/lib/utils' import { toast } from 'sonner' import { format } from 'date-fns' interface NoteEditorToolbarProps { mode: 'fullPage' | 'dialog' onClose: () => void onToggleAttachments?: () => void attachmentsCount?: number } export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachmentsCount }: NoteEditorToolbarProps) { const { state, actions, note, readOnly, fullPage, notebooks, fileInputRef } = useNoteEditorContext() const { t } = useLanguage() const [isConverting, setIsConverting] = useState(false) const [shareOpen, setShareOpen] = useState(false) const [flashcardsOpen, setFlashcardsOpen] = useState(false) const notebookName = notebooks.find(nb => nb.id === note.notebookId)?.name || null const undoSnapshotRef = useRef<{ content: string; isMarkdown: boolean } | null>(null) const handleConvertToRichtext = async () => { if (isConverting || !state.content.trim()) return setIsConverting(true) const snapshot = { content: state.content, isMarkdown: state.isMarkdown } undoSnapshotRef.current = snapshot try { let html: string if (state.isMarkdown) { const { marked } = await import('marked') html = await marked(state.content, { async: false }) as string } else { html = state.content .split(/\n{2,}/) .map(para => `

${para.trim().replace(/\n/g, '
')}

`) .join('') } actions.setContent(html) actions.setIsMarkdown(false) toast.success(t('notes.convertedToRichText') || 'Converted to rich text', { duration: 8000, action: { label: t('notes.undo') || '↩ Undo', onClick: () => { const snap = undoSnapshotRef.current if (!snap) return actions.setContent(snap.content) if (snap.isMarkdown) actions.setIsMarkdown(true) undoSnapshotRef.current = null toast.info(t('ai.undoApplied') || 'Conversion undone') }, }, }) } catch { toast.error(t('notes.transformFailed') || 'Conversion failed') } finally { setIsConverting(false) } } if (mode === 'fullPage') { const handleCloseWithSave = async () => { if (state.isDirty && !state.isSaving) { await actions.handleSaveInPlace() } onClose() } return (
{state.isSaving ? <>{t('notes.saving')} : state.isDirty ? <>{t('notes.dirtyStatus')} : <>{t('notes.savedStatus')}} {state.isMarkdown && !readOnly && ( )} {state.isMarkdown && !readOnly && ( )} {!readOnly && ( )} {!readOnly && onToggleAttachments && ( )} {!readOnly && ( )} {!readOnly && ( )} {!readOnly && ( { try { await deleteNote(note.id, { skipRevalidation: true }) emitNoteChange({ type: 'deleted', noteId: note.id, notebookId: note.notebookId }) toast.success(t('notes.noteDeletedToast')) onClose() } catch { toast.error(t('notes.deleteNoteFailedToast')) } }} className="text-red-600 dark:text-red-400 focus:text-red-600" > {t('notes.deleteNoteConfirmItem')} )} {shareOpen && ( setShareOpen(false)} /> )} setFlashcardsOpen(false)} noteId={note.id} noteTitle={state.title || note.title || 'Untitled'} onSaved={(deckId) => { toast.success(t('flashcards.savedCount', { count: '' }).replace('{count}', ''), { description: t('flashcards.reviewNow') || 'Review now', action: { label: t('flashcards.reviewNow') || 'Review now →', onClick: () => { window.open(`/revision?deckId=${encodeURIComponent(deckId)}`, '_self') }, }, duration: 8000, }) }} />
) } return (
{!readOnly && ( <> {state.isMarkdown && ( )}
{(['small', 'medium', 'large'] as const).map((s) => ( ))}
{Object.entries(NOTE_COLORS).map(([colorName, classes]) => (
)} {readOnly && (
{t('notes.sharedReadOnly')}
)}
{readOnly ? ( <> ) : ( <> )}
) }