feat: 实现笔临 P0/P1 Electron 桌面应用 v0.1.0

交付写作核心(TipTap、自动保存、分卷分章)、Onboarding、7 主题与双语 i18n、快捷键设置;主进程使用 node:sqlite;补全 Vitest 与 Playwright E2E;统一 design/ 目录并移除 desigin 拼写错误路径。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 10:39:20 +08:00
parent a06038035b
commit 011bd6d4ca
70 changed files with 14245 additions and 84 deletions
+78
View File
@@ -0,0 +1,78 @@
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'
import { IPC } from '../shared/ipc-channels'
import type {
ActionId,
BookMeta,
BookOpenResult,
Chapter,
CreateBookParams,
GlobalSettings,
IpcResult,
UpdateChapterParams,
Volume
} from '../shared/types'
const electronAPI = {
window: {
minimize: (): Promise<void> => ipcRenderer.invoke(IPC.WINDOW_MINIMIZE),
maximize: (): Promise<void> => ipcRenderer.invoke(IPC.WINDOW_MAXIMIZE),
close: (): Promise<void> => ipcRenderer.invoke(IPC.WINDOW_CLOSE)
},
settings: {
get: (): Promise<IpcResult<GlobalSettings>> => ipcRenderer.invoke(IPC.SETTINGS_GET),
update: (partial: Partial<GlobalSettings>): Promise<IpcResult<GlobalSettings>> =>
ipcRenderer.invoke(IPC.SETTINGS_UPDATE, partial)
},
book: {
list: (): Promise<IpcResult<BookMeta[]>> => ipcRenderer.invoke(IPC.BOOK_LIST),
create: (params: CreateBookParams): Promise<IpcResult<BookMeta>> =>
ipcRenderer.invoke(IPC.BOOK_CREATE, params),
delete: (bookId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.BOOK_DELETE, { bookId }),
open: (bookId: string): Promise<IpcResult<BookOpenResult>> =>
ipcRenderer.invoke(IPC.BOOK_OPEN, { bookId }),
updateMeta: (
bookId: string,
patch: { lastChapterId?: string | null; status?: BookMeta['status'] }
): Promise<IpcResult<BookMeta>> => ipcRenderer.invoke(IPC.BOOK_UPDATE_META, { bookId, ...patch })
},
volume: {
create: (bookId: string, name: string): Promise<IpcResult<Volume>> =>
ipcRenderer.invoke(IPC.VOLUME_CREATE, { bookId, name }),
update: (
bookId: string,
volumeId: string,
patch: { name?: string; description?: string }
): Promise<IpcResult<Volume>> =>
ipcRenderer.invoke(IPC.VOLUME_UPDATE, { bookId, volumeId, ...patch }),
delete: (bookId: string, volumeId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.VOLUME_DELETE, { bookId, volumeId })
},
chapter: {
create: (bookId: string, volumeId: string, title: string): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.CHAPTER_CREATE, { bookId, volumeId, title }),
get: (bookId: string, chapterId: string): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.CHAPTER_GET, { bookId, chapterId }),
update: (params: UpdateChapterParams): Promise<IpcResult<Chapter>> =>
ipcRenderer.invoke(IPC.CHAPTER_UPDATE, params),
delete: (bookId: string, chapterId: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.CHAPTER_DELETE, { bookId, chapterId })
},
shortcut: {
getAll: (): Promise<IpcResult<Record<ActionId, string>>> =>
ipcRenderer.invoke(IPC.SHORTCUT_GET_ALL),
register: (action: ActionId, accelerator: string): Promise<IpcResult<void>> =>
ipcRenderer.invoke(IPC.SHORTCUT_REGISTER, { action, accelerator })
},
onShortcutTriggered: (callback: (action: ActionId) => void): (() => void) => {
const handler = (_event: IpcRendererEvent, payload: { action: ActionId }) => {
callback(payload.action)
}
ipcRenderer.on(IPC.SHORTCUT_TRIGGERED, handler)
return () => ipcRenderer.removeListener(IPC.SHORTCUT_TRIGGERED, handler)
}
}
contextBridge.exposeInMainWorld('electronAPI', electronAPI)
export type ElectronAPI = typeof electronAPI