import { app, BrowserWindow, Menu, MenuItemConstructorOptions, ipcMain } from 'electron'; import * as path from 'path'; import * as fs from 'fs'; import { getDatabase } from './database'; import { registerIpcHandlers } from './ipc'; let mainWindow: BrowserWindow | null = null; // Check if dev server is likely running (only in development) const isDev = process.env.NODE_ENV === 'development'; function createWindow(): void { mainWindow = new BrowserWindow({ width: 1400, height: 900, minWidth: 800, minHeight: 600, title: 'Blogging Desktop Server', backgroundColor: '#1e1e1e', // VS Code dark background webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true, sandbox: false, }, icon: path.join(__dirname, '../../assets/icon.png'), }); // Set up the application menu const menu = createApplicationMenu(); Menu.setApplicationMenu(menu); // Load the app - use built files unless explicitly in dev mode const rendererPath = path.join(__dirname, '../renderer/index.html'); if (isDev) { mainWindow.loadURL('http://localhost:5173'); mainWindow.webContents.openDevTools(); } else if (fs.existsSync(rendererPath)) { mainWindow.loadFile(rendererPath); } else { // Fallback to dev server if built files don't exist mainWindow.loadURL('http://localhost:5173'); mainWindow.webContents.openDevTools(); } // Forward events to renderer ipcMain.on('forward-to-renderer', (_event, eventName: string, ...args: unknown[]) => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send(eventName, ...args); } }); mainWindow.on('closed', () => { mainWindow = null; }); } function createApplicationMenu(): Menu { const template: MenuItemConstructorOptions[] = [ { label: 'File', submenu: [ { label: 'New Post', accelerator: 'CmdOrCtrl+N', click: () => { mainWindow?.webContents.send('menu:newPost'); }, }, { label: 'Import Media...', accelerator: 'CmdOrCtrl+I', click: () => { mainWindow?.webContents.send('menu:importMedia'); }, }, { type: 'separator' }, { label: 'Save', accelerator: 'CmdOrCtrl+S', click: () => { mainWindow?.webContents.send('menu:save'); }, }, { type: 'separator' }, { label: 'Open Data Folder', click: async () => { const { shell } = require('electron'); const paths = getDatabase().getDataPaths(); shell.openPath(path.dirname(paths.database)); }, }, { type: 'separator' }, { label: 'Exit', accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Alt+F4', click: () => { app.quit(); }, }, ], }, { label: 'Edit', submenu: [ { role: 'undo' }, { role: 'redo' }, { type: 'separator' }, { role: 'cut' }, { role: 'copy' }, { role: 'paste' }, { role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }, { type: 'separator' }, { label: 'Find', accelerator: 'CmdOrCtrl+F', click: () => { mainWindow?.webContents.send('menu:find'); }, }, { label: 'Replace', accelerator: 'CmdOrCtrl+H', click: () => { mainWindow?.webContents.send('menu:replace'); }, }, ], }, { label: 'View', submenu: [ { label: 'Posts', accelerator: 'CmdOrCtrl+1', click: () => { mainWindow?.webContents.send('menu:viewPosts'); }, }, { label: 'Media', accelerator: 'CmdOrCtrl+2', click: () => { mainWindow?.webContents.send('menu:viewMedia'); }, }, { type: 'separator' }, { label: 'Toggle Sidebar', accelerator: 'CmdOrCtrl+B', click: () => { mainWindow?.webContents.send('menu:toggleSidebar'); }, }, { label: 'Toggle Panel', accelerator: 'CmdOrCtrl+J', click: () => { mainWindow?.webContents.send('menu:togglePanel'); }, }, { type: 'separator' }, { role: 'reload' }, { role: 'forceReload' }, { role: 'toggleDevTools' }, { type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }, { type: 'separator' }, { role: 'togglefullscreen' }, ], }, { label: 'Blog', submenu: [ { label: 'Publish Selected', accelerator: 'CmdOrCtrl+Shift+P', click: () => { mainWindow?.webContents.send('menu:publishSelected'); }, }, { label: 'Unpublish Selected', click: () => { mainWindow?.webContents.send('menu:unpublishSelected'); }, }, { type: 'separator' }, { label: 'Preview Post', accelerator: 'CmdOrCtrl+Shift+V', click: () => { mainWindow?.webContents.send('menu:previewPost'); }, }, { type: 'separator' }, { label: 'Rebuild Database from Files', click: () => { mainWindow?.webContents.send('menu:rebuildDatabase'); }, }, ], }, { label: 'Sync', submenu: [ { label: 'Sync Now', accelerator: 'CmdOrCtrl+Shift+S', click: () => { mainWindow?.webContents.send('menu:syncNow'); }, }, { label: 'Push Changes', click: () => { mainWindow?.webContents.send('menu:pushChanges'); }, }, { label: 'Pull Changes', click: () => { mainWindow?.webContents.send('menu:pullChanges'); }, }, { type: 'separator' }, { label: 'Configure Sync...', click: () => { mainWindow?.webContents.send('menu:configureSync'); }, }, { label: 'View Sync Log', click: () => { mainWindow?.webContents.send('menu:viewSyncLog'); }, }, ], }, { label: 'Help', submenu: [ { label: 'About Blogging Desktop Server', click: () => { mainWindow?.webContents.send('menu:about'); }, }, { type: 'separator' }, { label: 'View on GitHub', click: async () => { const { shell } = require('electron'); await shell.openExternal('https://github.com/bds/blogging-desktop-server'); }, }, { label: 'Report Issue', click: async () => { const { shell } = require('electron'); await shell.openExternal('https://github.com/bds/blogging-desktop-server/issues'); }, }, ], }, ]; // macOS specific menu adjustments if (process.platform === 'darwin') { template.unshift({ label: app.name, submenu: [ { role: 'about' }, { type: 'separator' }, { role: 'services' }, { type: 'separator' }, { role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' }, { type: 'separator' }, { role: 'quit' }, ], }); } return Menu.buildFromTemplate(template); } async function initialize(): Promise { // Initialize database const db = getDatabase(); await db.initializeLocal(); // Register IPC handlers registerIpcHandlers(); } // App lifecycle app.whenReady().then(async () => { await initialize(); createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('before-quit', async () => { const db = getDatabase(); await db.close(); }); // Handle any uncaught exceptions process.on('uncaughtException', (error) => { console.error('Uncaught exception:', error); }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled rejection at:', promise, 'reason:', reason); });