import { useState, useEffect, useRef } from 'react'; import { useEditor, EditorContent } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import Underline from '@tiptap/extension-underline'; import Strike from '@tiptap/extension-strike'; import TurndownService from 'turndown'; import { marked } from 'marked'; import { Note } from '../types'; interface NoteEditorProps { note: Note | null; onUpdateNote: (note: Note) => void; fontSize: number; onUnsavedChanges?: (hasChanges: boolean) => void; } const turndownService = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', }); export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges }: NoteEditorProps) { const [localTitle, setLocalTitle] = useState(''); const [localFavorite, setLocalFavorite] = useState(false); const [isSaving, setIsSaving] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [titleManuallyEdited, setTitleManuallyEdited] = useState(false); const previousNoteIdRef = useRef(null); // Notify parent component when unsaved changes state changes useEffect(() => { onUnsavedChanges?.(hasUnsavedChanges); }, [hasUnsavedChanges, onUnsavedChanges]); const editor = useEditor({ extensions: [ StarterKit, Underline, Strike, ], content: '', editorProps: { attributes: { class: 'prose prose-slate max-w-none focus:outline-none p-6', style: `font-size: ${fontSize}px`, }, }, onUpdate: ({ editor }) => { setHasUnsavedChanges(true); if (!titleManuallyEdited) { const text = editor.getText(); const firstLine = text.split('\n')[0].trim(); if (firstLine) { setLocalTitle(firstLine.substring(0, 50)); } } }, }); useEffect(() => { const loadNewNote = () => { if (note && editor) { setLocalTitle(note.title); setLocalFavorite(note.favorite); setHasUnsavedChanges(false); // Only reset titleManuallyEdited when switching to a different note // Check if the current title matches the first line of content const firstLine = note.content.split('\n')[0].replace(/^#+\s*/, '').trim(); const titleMatchesFirstLine = note.title === firstLine || note.title === firstLine.substring(0, 50); setTitleManuallyEdited(!titleMatchesFirstLine); previousNoteIdRef.current = note.id; // Convert markdown to HTML using marked library const html = marked.parse(note.content || '', { async: false }) as string; editor.commands.setContent(html); } }; // If switching notes, save the previous note first if (previousNoteIdRef.current !== null && previousNoteIdRef.current !== note?.id) { // Save if there are unsaved changes if (hasUnsavedChanges && editor) { handleSave(); } // Load new note after a brief delay to ensure save completes setTimeout(loadNewNote, 100); } else { // First load or same note, load immediately loadNewNote(); } }, [note?.id, editor]); const handleSave = () => { if (!note || !hasUnsavedChanges || !editor) return; // Convert HTML to markdown const html = editor.getHTML(); const markdown = turndownService.turndown(html); console.log('Saving note content length:', markdown.length); console.log('Last 50 chars:', markdown.slice(-50)); setIsSaving(true); setHasUnsavedChanges(false); onUpdateNote({ ...note, title: localTitle, content: markdown, category: '', favorite: localFavorite, }); setTimeout(() => setIsSaving(false), 500); }; const handleTitleChange = (value: string) => { setLocalTitle(value); setTitleManuallyEdited(true); setHasUnsavedChanges(true); }; const handleFavoriteToggle = () => { setLocalFavorite(!localFavorite); if (note) { onUpdateNote({ ...note, title: localTitle, content: editor ? turndownService.turndown(editor.getHTML()) : note.content, category: '', favorite: !localFavorite, }); } }; if (!note) { return (

No Note Selected

Select a note from the sidebar or create a new one

); } if (!editor) { return null; } return (
handleTitleChange(e.target.value)} placeholder="Note Title" className="flex-1 text-2xl font-bold border-none outline-none focus:ring-0 bg-transparent text-gray-900 dark:text-gray-100" />
{hasUnsavedChanges && ( Unsaved changes )} {isSaving && ( Saving... )}
{/* Formatting Toolbar */}
); }