@@ -289,9 +289,11 @@ export default function EditorScreen() {
289289 const [ rawMarkdown , setRawMarkdown ] = useState ( '' ) ;
290290 const [ isEditingMarkdown , setIsEditingMarkdown ] = useState ( false ) ;
291291 const [ editedMarkdown , setEditedMarkdown ] = useState ( '' ) ;
292+ const [ saveStatus , setSaveStatus ] = useState < 'saved' | 'saving' | 'unsaved' > ( 'saved' ) ;
292293 const isInitialLoad = useRef ( true ) ;
293294 const initialBlocksRef = useRef < EditorBlock [ ] > ( [ ] ) ;
294295 const isTitleManuallySet = useRef ( false ) ;
296+ const autoSaveTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
295297
296298 const colors = Colors [ colorScheme ?? 'light' ] ;
297299 const styles = getStyles ( colorScheme ?? 'light' ) ;
@@ -420,24 +422,21 @@ export default function EditorScreen() {
420422 [ markAsChanged , clearUnsavedChanges , hasUnsavedChanges ]
421423 ) ;
422424
423- // Save note handler
424- const handleSaveNote = useCallback ( async ( ) => {
425- if ( isSaving ) return ;
425+ // Auto-save with debounce
426+ const performSave = useCallback ( async ( ) => {
427+ if ( isSaving || blocks . length === 0 ) return ;
426428
427429 setIsSaving ( true ) ;
430+ setSaveStatus ( 'saving' ) ;
428431 try {
429432 if ( __DEV__ ) {
430433 const footnoteBlocks = blocks . filter ( b => b . type === 'footnote' ) ;
431- console . log ( '[handleSaveNote ] Saving blocks count:' , blocks . length ) ;
434+ console . log ( '[performSave ] Saving blocks count:' , blocks . length ) ;
432435 console . log (
433- '[handleSaveNote ] Footnote blocks:' ,
436+ '[performSave ] Footnote blocks:' ,
434437 footnoteBlocks . length ,
435438 footnoteBlocks . map ( b => b . meta ?. footnoteId )
436439 ) ;
437- console . log (
438- '[handleSaveNote] Block types:' ,
439- blocks . map ( b => b . type )
440- ) ;
441440 }
442441
443442 // Generate note preview from blocks
@@ -473,14 +472,52 @@ export default function EditorScreen() {
473472 setCurrentNote ( savedNote ) ;
474473 // Update initial blocks reference after successful save
475474 initialBlocksRef . current = blocks ;
476- Alert . alert ( 'Success' , 'Note saved successfully!' ) ;
475+ clearUnsavedChanges ( ) ;
476+ setSaveStatus ( 'saved' ) ;
477+
478+ // Show saved status briefly, then hide
479+ setTimeout ( ( ) => setSaveStatus ( 'saved' ) , 1000 ) ;
477480 } catch ( error ) {
478481 console . error ( 'Failed to save note:' , error ) ;
479- Alert . alert ( 'Error' , 'Failed to save note ') ;
482+ setSaveStatus ( 'unsaved ') ;
480483 } finally {
481484 setIsSaving ( false ) ;
482485 }
483- } , [ blocks , currentNote , saveNote , isSaving , noteTitle ] ) ;
486+ } , [ blocks , currentNote , saveNote , isSaving , noteTitle , clearUnsavedChanges , setCurrentNote ] ) ;
487+
488+ // Trigger auto-save when blocks change
489+ useEffect ( ( ) => {
490+ if ( isInitialLoad . current || blocks . length === 0 ) {
491+ return ;
492+ }
493+
494+ // Clear existing timer
495+ if ( autoSaveTimerRef . current ) {
496+ clearTimeout ( autoSaveTimerRef . current ) ;
497+ }
498+
499+ // Set status to unsaved immediately
500+ setSaveStatus ( 'unsaved' ) ;
501+
502+ // Debounce save by 2 seconds
503+ autoSaveTimerRef . current = setTimeout ( ( ) => {
504+ performSave ( ) ;
505+ } , 2000 ) ;
506+
507+ return ( ) => {
508+ if ( autoSaveTimerRef . current ) {
509+ clearTimeout ( autoSaveTimerRef . current ) ;
510+ }
511+ } ;
512+ } , [ blocks , performSave ] ) ;
513+
514+ // Manual save handler (for explicit save button)
515+ const handleSaveNote = useCallback ( async ( ) => {
516+ if ( autoSaveTimerRef . current ) {
517+ clearTimeout ( autoSaveTimerRef . current ) ;
518+ }
519+ await performSave ( ) ;
520+ } , [ performSave ] ) ;
484521
485522 // Handle undo
486523 const handleUndo = useCallback ( ( ) => {
@@ -498,9 +535,49 @@ export default function EditorScreen() {
498535
499536 // Handle formatting actions
500537 const handleFormattingAction = useCallback ( ( actionId : string ) => {
501- console . log ( 'Formatting action:' , actionId ) ;
502- // TODO: Implement formatting actions
503- } , [ ] ) ;
538+ if ( ! editorRef . current ) return ;
539+
540+ // Get current blocks
541+ const currentBlocks = editorRef . current . getBlocks ( ) ;
542+ if ( currentBlocks . length === 0 ) return ;
543+
544+ // For now, apply formatting to the last block (where cursor likely is)
545+ const lastBlock = currentBlocks [ currentBlocks . length - 1 ] ;
546+ if ( ! lastBlock || lastBlock . type !== 'paragraph' ) return ;
547+
548+ const content = lastBlock . content ;
549+ let newContent = content ;
550+
551+ switch ( actionId ) {
552+ case 'bold' :
553+ newContent = content . includes ( '**' ) ? content . replace ( / \* \* / g, '' ) : `**${ content } **` ;
554+ break ;
555+ case 'italic' :
556+ newContent = content . includes ( '*' ) && ! content . includes ( '**' ) ? content . replace ( / \* / g, '' ) : `*${ content } *` ;
557+ break ;
558+ case 'strikethrough' :
559+ newContent = content . includes ( '~~' ) ? content . replace ( / ~ ~ / g, '' ) : `~~${ content } ~~` ;
560+ break ;
561+ case 'code' :
562+ newContent = content . includes ( '`' ) ? content . replace ( / ` / g, '' ) : `\`${ content } \`` ;
563+ break ;
564+ case 'link' :
565+ if ( ! content . includes ( '[' ) ) {
566+ newContent = `[${ content } ](url)` ;
567+ }
568+ break ;
569+ default :
570+ return ;
571+ }
572+
573+ // Update blocks array
574+ const updatedBlocks = [ ...currentBlocks ] ;
575+ updatedBlocks [ updatedBlocks . length - 1 ] = { ...lastBlock , content : newContent } ;
576+
577+ // Apply via setBlocks
578+ setBlocks ( updatedBlocks ) ;
579+ markAsChanged ( ) ;
580+ } , [ markAsChanged ] ) ;
504581
505582 // Handle rename
506583 const handleRename = useCallback ( ( ) => {
@@ -697,6 +774,16 @@ export default function EditorScreen() {
697774 barStyle = { colorScheme === 'dark' ? 'light-content' : 'dark-content' }
698775 backgroundColor = { colors . background }
699776 />
777+
778+ { /* Save Status Toast */ }
779+ { saveStatus !== 'saved' && (
780+ < View style = { [ styles . saveToast , { backgroundColor : colorScheme === 'dark' ? 'rgba(0,0,0,0.8)' : 'rgba(255,255,255,0.95)' } ] } >
781+ < Text style = { [ styles . saveToastText , { color : colors . text } ] } >
782+ { saveStatus === 'saving' ? '💾 Saving...' : saveStatus === 'unsaved' ? '✏️ Unsaved changes' : '' }
783+ </ Text >
784+ </ View >
785+ ) }
786+
700787 { /* Compact Header */ }
701788 < View style = { styles . header } >
702789 < View style = { styles . headerRow } >
@@ -997,6 +1084,24 @@ const getStyles = (colorScheme: 'light' | 'dark') => {
9971084 saveButton : {
9981085 backgroundColor : colors . tint ,
9991086 } ,
1087+ saveToast : {
1088+ position : 'absolute' ,
1089+ top : 60 ,
1090+ alignSelf : 'center' ,
1091+ paddingHorizontal : 16 ,
1092+ paddingVertical : 8 ,
1093+ borderRadius : 20 ,
1094+ zIndex : 1000 ,
1095+ shadowColor : '#000' ,
1096+ shadowOffset : { width : 0 , height : 2 } ,
1097+ shadowOpacity : 0.1 ,
1098+ shadowRadius : 4 ,
1099+ elevation : 4 ,
1100+ } ,
1101+ saveToastText : {
1102+ fontSize : 13 ,
1103+ fontFamily : 'AlbertSans_500Medium' ,
1104+ } ,
10001105 loadingContainer : {
10011106 justifyContent : 'center' ,
10021107 alignItems : 'center' ,
0 commit comments