diff --git a/src/main/index.ts b/src/main/index.ts index aa69ea0..80f35b9 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,5 +1,5 @@ import electron from 'electron'; -const { app, BrowserWindow, Notification } = electron; +const { app, BrowserWindow, Notification, dialog } = electron; import '@shared/types'; import { existsSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; @@ -25,6 +25,7 @@ import { import { createTray } from './tray.js'; import { MediaGc } from './services/MediaGc.js'; import { BackupService } from './services/BackupService.js'; +import { ExportService } from './services/ExportService.js'; const HIDDEN_ARG = '--hidden'; const startedHidden = process.argv.includes(HIDDEN_ARG); @@ -102,6 +103,8 @@ app.whenReady().then(async () => { const gc = new MediaGc(db, store); void gc.run().then((r) => logger.info('media.gc', { ...r } as Record)); + const exportSvc = new ExportService(repo, store); + const backup = new BackupService(db, join(paths.profileDir, 'backups')); void backup.runDaily() .then((r) => logger.info('backup.daily', { ...r } as Record)) @@ -142,6 +145,40 @@ app.whenReady().then(async () => { silent: true }).show(); } + }, + async () => { + const win = getInboxWindow(); + const dialogOpts: Electron.OpenDialogOptions = { + title: '내보낼 폴더 선택', + message: '선택한 폴더에 노트를 마크다운으로 내보냅니다. 이미지가 함께 포함됩니다. raw_text 가 평문으로 보관되니 비공개 위치를 권장합니다.', + buttonLabel: '여기에 내보내기', + properties: ['openDirectory', 'createDirectory'] + }; + const result = win + ? await dialog.showOpenDialog(win, dialogOpts) + : await dialog.showOpenDialog(dialogOpts); + if (result.canceled || result.filePaths.length === 0) return; + try { + const r = await exportSvc.export(result.filePaths[0]!, { includeMedia: true }); + logger.info('export.done', { + outDir: r.outDir, + noteCount: r.noteCount, + mediaCount: r.mediaCount, + bytes: r.bytes + }); + new Notification({ + title: 'Inkling', + body: `내보내기 완료 — 노트 ${r.noteCount}개, 이미지 ${r.mediaCount}개`, + silent: true + }).show(); + } catch (e) { + logger.warn('export.failed', { reason: String(e) }); + new Notification({ + title: 'Inkling', + body: '내보내기를 완료하지 못했습니다.', + silent: true + }).show(); + } } ); diff --git a/src/main/tray.ts b/src/main/tray.ts index d8f4fac..6ac6739 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -7,13 +7,15 @@ let tray: TrayType | null = null; function buildMenu( showInbox: () => void, showCapture: () => void, - runBackup: () => void + runBackup: () => void, + runExport: () => void ) { const items: MenuItemConstructorOptions[] = [ { label: '구출한 메모 보기', click: showInbox }, { label: '기억 구출하기', click: showCapture }, { type: 'separator' }, - { label: '지금 백업', click: runBackup } + { label: '지금 백업', click: runBackup }, + { label: '내보내기...', click: runExport } ]; if (app.isPackaged) { const { openAtLogin } = app.getLoginItemSettings(); @@ -39,12 +41,13 @@ function buildMenu( export function createTray( showInbox: () => void, showCapture: () => void, - runBackup: () => void + runBackup: () => void, + runExport: () => void ): TrayType { const icon = nativeImage.createEmpty(); tray = new Tray(icon); tray.setToolTip('Inkling'); - tray.setContextMenu(buildMenu(showInbox, showCapture, runBackup)); + tray.setContextMenu(buildMenu(showInbox, showCapture, runBackup, runExport)); tray.on('click', showInbox); return tray; }