@@ -349,7 +349,8 @@ function createWindow() {
349349 preload : path . join ( __dirname , 'preload.js' ) ,
350350 nodeIntegration : false ,
351351 contextIsolation : true ,
352- webSecurity : true
352+ webSecurity : true ,
353+ webviewTag : true
353354 } ,
354355 show : false , // Don't show until ready
355356 backgroundColor : '#1a1a1a' ,
@@ -405,6 +406,60 @@ function createWindow() {
405406 } ) ;
406407}
407408
409+ // ============================================================================
410+ // DRAG-OUT FROM LOCAL EXPLORER (OS DRAG)
411+ // ============================================================================
412+
413+ ipcMain . on ( 'start-drag' , async ( event , paths ) => {
414+ const list = Array . isArray ( paths ) ? paths . map ( String ) . filter ( Boolean ) : [ ] ;
415+ if ( ! list . length ) return ;
416+
417+ const wc = event . sender ;
418+ if ( ! wc || typeof wc . startDrag !== 'function' ) return ;
419+
420+ // If folders are included, expand them into file paths so web uploads can accept the drop.
421+ // NOTE: Many sites don't accept directory drops, but do accept multiple files.
422+ let dragFiles = [ ] ;
423+ for ( const p of list ) {
424+ const st = safeStat ( p ) ;
425+ if ( ! st ) continue ;
426+ if ( st . isFile ( ) ) {
427+ dragFiles . push ( p ) ;
428+ } else if ( st . isDirectory ( ) ) {
429+ try {
430+ const files = getAllFilesInFolder ( p ) ;
431+ dragFiles . push ( ...files ) ;
432+ } catch ( _ ) { }
433+ }
434+ }
435+
436+ // Prevent pathological huge drags from freezing the app.
437+ if ( dragFiles . length > 2000 ) {
438+ dragFiles = dragFiles . slice ( 0 , 2000 ) ;
439+ }
440+
441+ if ( ! dragFiles . length ) {
442+ // Fall back to dragging the first original path if expansion produced nothing.
443+ dragFiles = [ list [ 0 ] ] ;
444+ }
445+
446+ const iconPath = path . join ( __dirname , 'assets' , 'icon.png' ) ;
447+ const icon = nativeImage . createFromPath ( iconPath ) ;
448+
449+ try {
450+ // Electron supports `files` in newer versions; fall back to single file.
451+ try {
452+ if ( dragFiles . length > 1 ) {
453+ wc . startDrag ( { files : dragFiles , icon } ) ;
454+ } else {
455+ wc . startDrag ( { file : dragFiles [ 0 ] , icon } ) ;
456+ }
457+ } catch ( _ ) {
458+ wc . startDrag ( { file : dragFiles [ 0 ] , icon } ) ;
459+ }
460+ }
461+ } ) ;
462+
408463/**
409464 * Create system tray icon
410465 */
@@ -907,12 +962,37 @@ ipcMain.handle('shred-start', async (event, payload) => {
907962
908963ipcMain . handle ( 'go-online' , async ( ) => {
909964 if ( ! mainWindow ) return { success : false } ;
910- await loadFrontendFallback ( { preferredPath : '/' , reason : 'renderer:go-online' } ) ;
965+
966+ // Always keep the local UI shell so the sidebar remains available.
967+ // The renderer will embed the live site in a <webview> panel.
968+ const url = String ( mainWindow . webContents . getURL ( ) || '' ) ;
969+ const inLocalShell = url . startsWith ( 'file:' ) && url . includes ( 'renderer/local/index.html' ) ;
970+ if ( ! inLocalShell ) {
971+ try {
972+ await mainWindow . loadFile ( LOCAL_UI_INDEX ) ;
973+ } catch ( _ ) { }
974+ }
975+
976+ try {
977+ mainWindow . webContents . send ( 'navigate-to' , { tool : 'online' , url : FRONTEND_URL } ) ;
978+ } catch ( _ ) { }
979+
911980 mainWindow . show ( ) ;
912981 mainWindow . focus ( ) ;
913982 return { success : true } ;
914983} ) ;
915984
985+ ipcMain . handle ( 'open-external' , async ( _event , url ) => {
986+ const u = String ( url || '' ) . trim ( ) ;
987+ if ( ! u ) return { success : false } ;
988+ try {
989+ await shell . openExternal ( u ) ;
990+ return { success : true } ;
991+ } catch ( e ) {
992+ return { success : false , error : e && e . message ? e . message : String ( e ) } ;
993+ }
994+ } ) ;
995+
916996ipcMain . handle ( 'vault-list' , async ( ) => {
917997 const items = getVaultItems ( ) ;
918998 return { items, totalBytes : vaultTotalBytes ( items ) } ;
0 commit comments