@@ -1010,11 +1010,50 @@ const App: Component = () => {
10101010 }
10111011 } ;
10121012
1013- const handleFileDeleted = ( path : string ) => {
1013+ const handleFileDeleted = async ( path : string ) => {
10141014 const idx = tabs ( ) . findIndex ( t => t . path === path ) ;
10151015 if ( idx >= 0 ) {
10161016 closeTab ( idx ) ;
10171017 }
1018+
1019+ // Track deletion for Nostr sync - store relative path
1020+ const vault = vaultPath ( ) ;
1021+ if ( vault ) {
1022+ const deletedPaths = JSON . parse ( localStorage . getItem ( 'deleted_paths' ) || '[]' ) as string [ ] ;
1023+ const relativePath = path . replace ( vault + '/' , '' ) ;
1024+
1025+ // For folder deletions, track all files that were inside
1026+ try {
1027+ const engine = getSyncEngine ( ) ;
1028+ const signer = engine . getSigner ( ) ;
1029+ if ( signer ) {
1030+ const vaults = await engine . fetchVaults ( ) ;
1031+ const vaultData = vaults [ 0 ] ;
1032+ if ( vaultData ) {
1033+ // Find all files in vault that start with this folder path
1034+ const folderPrefix = relativePath + '/' ;
1035+ const filesInFolder = vaultData . data . files
1036+ . filter ( f => f . path === relativePath || f . path . startsWith ( folderPrefix ) )
1037+ . map ( f => f . path ) ;
1038+
1039+ for ( const filePath of filesInFolder ) {
1040+ if ( ! deletedPaths . includes ( filePath ) ) {
1041+ deletedPaths . push ( filePath ) ;
1042+ }
1043+ }
1044+ }
1045+ }
1046+ } catch {
1047+ // Could not check for folder contents, will track path as-is
1048+ }
1049+
1050+ // Also track the path itself (in case it's a file, or as a fallback for folders)
1051+ if ( ! deletedPaths . includes ( relativePath ) ) {
1052+ deletedPaths . push ( relativePath ) ;
1053+ }
1054+
1055+ localStorage . setItem ( 'deleted_paths' , JSON . stringify ( deletedPaths ) ) ;
1056+ }
10181057 } ;
10191058
10201059 const toggleBookmark = async ( path : string ) => {
@@ -1244,18 +1283,18 @@ const App: Component = () => {
12441283 vault = await engine . createVault ( 'My Notes' , 'Default vault' ) ;
12451284 }
12461285
1247- // Get local files
1286+ // Get local files with their full paths
12481287 const entries = await invoke < Array < { name : string ; path : string ; isDirectory : boolean ; children ?: unknown [ ] } > > ( 'list_files' , { path : vaultPath ( ) } ) ;
12491288
1250- const localFiles : { path : string ; content : string } [ ] = [ ] ;
1289+ const localFiles : { path : string ; fullPath : string ; content : string } [ ] = [ ] ;
12511290 const processEntries = async ( entries : Array < { name : string ; path : string ; isDirectory : boolean ; children ?: unknown [ ] } > ) => {
12521291 for ( const entry of entries ) {
12531292 if ( entry . isDirectory && entry . children ) {
12541293 await processEntries ( entry . children as typeof entries ) ;
12551294 } else if ( entry . name . endsWith ( '.md' ) ) {
12561295 const content = await invoke < string > ( 'read_file' , { path : entry . path } ) ;
12571296 const relativePath = entry . path . replace ( vaultPath ( ) ! + '/' , '' ) ;
1258- localFiles . push ( { path : relativePath , content } ) ;
1297+ localFiles . push ( { path : relativePath , fullPath : entry . path , content } ) ;
12591298 }
12601299 }
12611300 } ;
@@ -1265,21 +1304,103 @@ const App: Component = () => {
12651304 const remoteFiles = await engine . fetchVaultFiles ( vault ) ;
12661305 const remoteFileMap = new Map ( remoteFiles . map ( f => [ f . data . path , f ] ) ) ;
12671306
1268- // Sync files
1307+ // Get locally deleted files that need to be synced
1308+ const locallyDeletedPaths = JSON . parse ( localStorage . getItem ( 'deleted_paths' ) || '[]' ) as string [ ] ;
1309+ const localFilePathSet = new Set ( localFiles . map ( f => f . path ) ) ;
1310+
1311+ // Sync files - compare timestamps to determine which version is newer
12691312 let downloadedCount = 0 ;
1313+ let uploadedCount = 0 ;
1314+ let deletedCount = 0 ;
12701315
12711316 for ( const localFile of localFiles ) {
12721317 const remoteFile = remoteFileMap . get ( localFile . path ) ;
1273- if ( ! remoteFile || remoteFile . data . content !== localFile . content ) {
1274- const result = await engine . publishFile ( vault , localFile . path , localFile . content , remoteFile ) ;
1318+
1319+ if ( remoteFile ) {
1320+ // File exists both locally and remotely
1321+ if ( remoteFile . data . content !== localFile . content ) {
1322+ // Content differs - compare timestamps to decide direction
1323+ try {
1324+ const localModifiedTime = await invoke < number > ( 'get_file_modified_time' , { path : localFile . fullPath } ) ;
1325+ const remoteModifiedTime = remoteFile . data . modified ;
1326+
1327+ console . log ( `[Sync] File ${ localFile . path } : local=${ localModifiedTime } , remote=${ remoteModifiedTime } ` ) ;
1328+
1329+ if ( remoteModifiedTime > localModifiedTime ) {
1330+ // Remote is newer - download
1331+ console . log ( `[Sync] Downloading newer remote version: ${ localFile . path } ` ) ;
1332+ await invoke ( 'write_file' , { path : localFile . fullPath , content : remoteFile . data . content } ) ;
1333+ downloadedCount ++ ;
1334+ } else {
1335+ // Local is newer or same time - upload
1336+ console . log ( `[Sync] Uploading newer local version: ${ localFile . path } ` ) ;
1337+ const result = await engine . publishFile ( vault , localFile . path , localFile . content , remoteFile ) ;
1338+ vault = result . vault ;
1339+ uploadedCount ++ ;
1340+ }
1341+ } catch ( err ) {
1342+ // If we can't get local modified time, default to uploading
1343+ console . warn ( `[Sync] Could not get modified time for ${ localFile . path } , uploading:` , err ) ;
1344+ const result = await engine . publishFile ( vault , localFile . path , localFile . content , remoteFile ) ;
1345+ vault = result . vault ;
1346+ uploadedCount ++ ;
1347+ }
1348+ }
1349+ // Remove from map - we've handled this file
1350+ remoteFileMap . delete ( localFile . path ) ;
1351+ } else {
1352+ // Local-only file - upload to remote
1353+ console . log ( `[Sync] Uploading new local file: ${ localFile . path } ` ) ;
1354+ const result = await engine . publishFile ( vault , localFile . path , localFile . content ) ;
12751355 vault = result . vault ;
1356+ uploadedCount ++ ;
1357+ }
1358+ }
1359+
1360+ // Process local deletions - sync them to the vault
1361+ const pathsToKeepTracking : string [ ] = [ ] ;
1362+
1363+ for ( const deletedPath of locallyDeletedPaths ) {
1364+ const inRemoteMap = remoteFileMap . has ( deletedPath ) ;
1365+ const inLocalFiles = localFilePathSet . has ( deletedPath ) ;
1366+
1367+ // Only process if the file exists on remote and not locally
1368+ if ( inRemoteMap && ! inLocalFiles ) {
1369+ try {
1370+ vault = await engine . deleteFile ( vault , deletedPath ) ;
1371+ deletedCount ++ ;
1372+ } catch {
1373+ // Keep tracking this path since deletion failed
1374+ pathsToKeepTracking . push ( deletedPath ) ;
1375+ }
1376+ } else if ( inLocalFiles ) {
1377+ // File still exists locally (was recreated?), keep tracking
1378+ pathsToKeepTracking . push ( deletedPath ) ;
12761379 }
1277- remoteFileMap . delete ( localFile . path ) ;
1380+ // Remove from remoteFileMap so we don't re-download it
1381+ remoteFileMap . delete ( deletedPath ) ;
12781382 }
1383+
1384+ // Update the locally deleted paths - only keep those that need continued tracking
1385+ localStorage . setItem ( 'deleted_paths' , JSON . stringify ( pathsToKeepTracking ) ) ;
12791386
1280- // Download remote-only files
1387+
1388+ // Download remote-only files (files that exist on remote but not locally)
1389+ // Skip files that were deleted locally or are in the vault's deleted list
12811390 for ( const [ path , remoteFile ] of remoteFileMap ) {
1282- if ( vault . data . deleted ?. some ( d => d . path === path ) ) continue ;
1391+ // Skip if in vault's deleted list
1392+ if ( vault . data . deleted ?. some ( d => d . path === path ) ) {
1393+ continue ;
1394+ }
1395+
1396+ // Skip if locally deleted (but not yet synced)
1397+ // Also check for folder deletions - if any deleted path is a prefix of this file path
1398+ const isLocallyDeleted = locallyDeletedPaths . some ( deletedPath =>
1399+ path === deletedPath || path . startsWith ( deletedPath + '/' )
1400+ ) ;
1401+ if ( isLocallyDeleted ) {
1402+ continue ;
1403+ }
12831404
12841405 const fullPath = `${ vaultPath ( ) } /${ path } ` ;
12851406 const parentDir = fullPath . substring ( 0 , fullPath . lastIndexOf ( '/' ) ) ;
@@ -1289,12 +1410,30 @@ const App: Component = () => {
12891410 await invoke ( 'write_file' , { path : fullPath , content : remoteFile . data . content } ) ;
12901411 downloadedCount ++ ;
12911412 }
1413+
1414+ console . log ( `[Sync] Complete: ${ uploadedCount } uploaded, ${ downloadedCount } downloaded, ${ deletedCount } deleted` ) ;
12921415
12931416 setSyncStatus ( 'idle' ) ;
12941417
1295- // Refresh sidebar if files were downloaded
1418+ // Refresh sidebar and reload open tabs if files were downloaded
12961419 if ( downloadedCount > 0 ) {
12971420 refreshSidebar ?.( ) ;
1421+
1422+ // Reload any open tabs that may have been updated
1423+ const currentTabs = tabs ( ) ;
1424+ for ( let i = 0 ; i < currentTabs . length ; i ++ ) {
1425+ const tab = currentTabs [ i ] ;
1426+ try {
1427+ const newContent = await invoke < string > ( 'read_file' , { path : tab . path } ) ;
1428+ if ( newContent !== tab . content ) {
1429+ setTabs ( prev => prev . map ( ( t , idx ) =>
1430+ idx === i ? { ...t , content : newContent , isDirty : false } : t
1431+ ) ) ;
1432+ }
1433+ } catch {
1434+ // File may have been deleted or moved
1435+ }
1436+ }
12981437 }
12991438 } catch ( err ) {
13001439 console . error ( 'Sync failed:' , err ) ;
@@ -1407,6 +1546,8 @@ const App: Component = () => {
14071546 if ( tab === 'files' || tab === 'search' || tab === 'bookmarks' ) {
14081547 setSidebarView ( tab ) ;
14091548 setMobileDrawerOpen ( true ) ;
1549+ } else if ( tab === 'notifications' ) {
1550+ setShowNotifications ( true ) ;
14101551 } else if ( tab === 'settings' ) {
14111552 setShowSettings ( true ) ;
14121553 }
@@ -1428,6 +1569,8 @@ const App: Component = () => {
14281569 title = { currentFileTitle ( ) }
14291570 isDirty = { currentTab ( ) ?. isDirty }
14301571 onMenuClick = { ( ) => setMobileDrawerOpen ( true ) }
1572+ onNotificationsClick = { ( ) => setShowNotifications ( true ) }
1573+ unreadNotifications = { unreadShareCount ( ) }
14311574 onSyncClick = { ( ) => handleStatusBarSync ( ) }
14321575 syncStatus = { syncStatus ( ) }
14331576 />
0 commit comments