NoteTool — 로컬-퍼스트 노트·플래시카드·캘린더

Electron + React + TypeScript 기반의 학습 생산성 데스크톱 앱. 노트(마크다운), 플래시카드(복습), 캘린더(일정)를 통합하고 PDF/CSV/(경량)XLS 내보내기를 지원합니다.

Electron 37 React 18 TypeScript 5 Vite 7 framer-motion · date-fns
플랫폼
Windows / macOS
Desktop
역할
설계·구현·패키징
개인
스코프
노트 · 플래시카드 · 캘린더
로컬-퍼스트
보안
preload 브릿지 최소화
contextIsolation
핵심 요약
  • 메타(JSON)·본문(.md) 분리 + 지연 로딩으로 초기 메모리/IO 최소화
  • 붙여넣기/드롭 이미지 자동 저장 및 경로 정규화, 미사용 이미지 청소
  • 오프스크린 PDF, CSV, 경량 XLS 내보내기(의존성 절감)
  • preload 브릿지 + contextIsolation으로 보안 경계 강화

주요 기능 (핵심만 빠르게)

📝 Notes

@uiw/react-md-editor 기반, KaTeX/GFM 지원, 자동 저장.

  • 메타만 선로딩 → 본문(.md)은 클릭 시 로딩
  • 북마크/검색/태그, 트리 DnD 정렬

🎴 Flashcards

카테고리-카드 트리, Q/A 편집, 키보드 네비게이션.

  • 뒤집기/이동 애니메이션
  • 진행 상태/필터

📅 Calendar & Export

월간 보기, 기간 이벤트, 모달 편집/삭제/추가.

  • 오프스크린 PDF, CSV, (HTML-table)XLS 내보내기
  • 날짜/중요도 표시

케이스 스터디 — Problem → Solution → Impact

Problem

단일 JSON 저장으로 초기 로딩/메모리 급증, 이미지 누수, 무거운 내보내기 의존성.

  • 대용량 노트 열 때 UX 저하
  • 경로 불일치로 이미지 깨짐

Solution

메타/본문 분리 + 지연 로딩, 이미지 자동 저장·경로 정규화, 경량 Export.

  • file:/// URL 통일, 주기적 미사용 이미지 청소
  • 오프스크린 PDF + CSV/(XLS) 경량화

Impact

초기 메모리/IO 체감 감소, 디스크 안정성 개선, 배포 사이즈/공격면 축소.

  • UX 반응성 향상
  • 유지보수성/보안성 동시 확보

아키텍처

Electron App
 ├─ Main: 파일 I/O, Export(PDF/CSV/XLS), 이미지 관리, IPC
 ├─ Preload: 안전한 브릿지(contextIsolation)
 └─ Renderer(React): 노트/플카/캘린더 UI & 상태

저장 구조
 ├─ notes-meta.json     # 메타(제목/태그/업데이트)
 └─ notes/<id>.md       # 본문(마크다운)

의존성: Electron 37, React 18, TypeScript 5, Vite 7, framer-motion, date-fns

관심사 분리

  • 파일 시스템 접근은 Main에서만 처리
  • Preload에서 최소 IPC 채널만 노출
  • Renderer는 순수 UI/상태 관리 집중

기술 스택

Desktop & Frontend

  • Electron 37, React 18, TypeScript 5
  • Vite 7, framer-motion, date-fns
  • @uiw/react-md-editor, KaTeX/GFM

DX & 품질

  • npm run dev (Vite + Electron 동시 실행)
  • electron-builder(NSIS, asar, 아이콘)
  • ESLint + TS strict

코드 하이라이트 (5)

1) Preload 브릿지 — 최소 API 표면

contextBridge.exposeInMainWorld("nt", {
  openNote: (id) => ipcRenderer.invoke("notes:open", id),
  saveNote: (id, md) => ipcRenderer.invoke("notes:save", { id, md }),
  exportPDF: (ids) => ipcRenderer.invoke("export:pdf", ids),
});

Renderer는 파일 시스템에 직접 접근하지 않음.

2) 오프스크린 PDF Export

const win = new BrowserWindow({ show:false, webPreferences:{ offscreen:true }});
await win.loadURL(htmlPath);
const pdf = await win.webContents.printToPDF({ printBackground:true });
await fs.promises.writeFile(out, pdf);

Headless에 가까운 방식으로 의존성 최소화.

3) 이미지 자동 저장 & 경로 정규화

ipcMain.handle("image:save", async (_e, { buffer, ext }) => {
  const file = join(imgDir, `${Date.now()}.${ext}`);
  await fs.promises.writeFile(file, buffer);
  return pathToFileURL(file).toString(); // file:///... 로 통일
});

Markdown에는 file:// URL만 삽입 → OS간 일관성.

4) 메타/본문 분리 & 지연 로딩

// 초기엔 notes-meta.json만 로드
const meta = await loadMeta();
const open = async (id) => {
  const md = await fs.promises.readFile(`notes/${id}.md`, "utf-8");
  setEditor(md);
};

대용량에서도 초기 반응성 유지.

5) (경량) XLS 내보내기

const html = `<table>...</table>`;
const blob = Buffer.from(html, "utf-8");
await fs.promises.writeFile(out, blob); // 레거시 XLS 열람 호환

IPC Quick Ref

채널 요청 페이로드 설명
notes:open id: string 노트 본문(.md) 로드
notes:save { id, md } 노트 저장(자동 저장과 병행)
image:save { buffer, ext } 이미지 파일 저장 → file:/// URL 반환
export:pdf ids: string[] 선택 노트 PDF로 내보내기
export:csv rows: any[] 플래시카드/캘린더 CSV 내보내기
export:xls html: string HTML-table 기반 경량 XLS 내보내기

REST: N/A (로컬-퍼스트, 네트워크 의존 없음)

성과/하이라이트

Local-First Storage Offscreen PDF Export Preload Bridge Security Electron-builder Packaging Image Auto-Save & Cleanup KaTeX · GFM

보안/마스킹 노트

항목 처리 근거
API Keys N/A 오프라인 로컬-퍼스트
파일 경로 ~/AppData, ~/Library 등 표시 단순화 개인 경로 마스킹
개인 데이터 내보내기 시 선택적 제거 개인정보 최소화

Contact

협업과 코드 리뷰를 좋아합니다. 필요 시 실사용 데모·코드 워크스루 제공 가능합니다.