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
+52
View File
@@ -0,0 +1,52 @@
import { randomUUID } from 'crypto'
import type { Volume } from '../../../shared/types'
import type { SqliteDb } from '../types'
function mapRow(row: Record<string, unknown>): Volume {
return {
id: row.id as string,
name: row.name as string,
description: row.description as string,
sortOrder: row.sort_order as number
}
}
export class VolumeRepository {
constructor(private db: SqliteDb) {}
create(name: string, sortOrder: number): Volume {
const id = randomUUID()
this.db
.prepare(
`INSERT INTO volumes (id, name, sort_order) VALUES (?, ?, ?)`
)
.run(id, name, sortOrder)
return this.get(id)!
}
get(id: string): Volume | null {
const row = this.db.prepare('SELECT * FROM volumes WHERE id = ?').get(id) as
| Record<string, unknown>
| undefined
return row ? mapRow(row) : null
}
list(): Volume[] {
return (
this.db.prepare('SELECT * FROM volumes ORDER BY sort_order').all() as Record<string, unknown>[]
).map(mapRow)
}
update(id: string, patch: { name?: string; description?: string }): Volume {
const existing = this.get(id)
if (!existing) throw new Error('Volume not found')
this.db
.prepare(
`UPDATE volumes SET name = ?, description = ?, updated_at = datetime('now') WHERE id = ?`
)
.run(patch.name ?? existing.name, patch.description ?? existing.description, id)
return this.get(id)!
}
delete(id: string): void {
this.db.prepare('DELETE FROM volumes WHERE id = ?').run(id)
}
}