feat: deliver P2 v0.2.0 with unified editor, search, and snapshots
Implement schema v2 migration, outline/setting/inspiration CRUD, unified TipTap editing, reference panel with mentions, FTS global search, chapter version history, writing landmarks, word frequency analysis, light theme contrast fixes, and full unit/E2E test coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import type { Snapshot, SnapshotType } from '../../../shared/types'
|
||||
import type { SqliteDb } from '../types'
|
||||
|
||||
function mapRow(row: Record<string, unknown>): Snapshot {
|
||||
return {
|
||||
id: row.id as string,
|
||||
chapterId: row.chapter_id as string,
|
||||
type: row.type as SnapshotType,
|
||||
name: (row.name as string) ?? '',
|
||||
content: row.content as string,
|
||||
createdAt: row.created_at as string
|
||||
}
|
||||
}
|
||||
|
||||
export class SnapshotRepository {
|
||||
constructor(private db: SqliteDb) {}
|
||||
|
||||
create(chapterId: string, type: SnapshotType, content: string, name = ''): Snapshot {
|
||||
const id = randomUUID()
|
||||
this.db
|
||||
.prepare(`INSERT INTO snapshots (id, chapter_id, type, name, content) VALUES (?, ?, ?, ?, ?)`)
|
||||
.run(id, chapterId, type, name, content)
|
||||
return this.get(id)!
|
||||
}
|
||||
|
||||
get(id: string): Snapshot | null {
|
||||
const row = this.db.prepare('SELECT * FROM snapshots WHERE id = ?').get(id) as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
return row ? mapRow(row) : null
|
||||
}
|
||||
|
||||
list(chapterId: string): Snapshot[] {
|
||||
return (
|
||||
this.db
|
||||
.prepare('SELECT * FROM snapshots WHERE chapter_id = ? ORDER BY created_at DESC')
|
||||
.all(chapterId) as Record<string, unknown>[]
|
||||
).map(mapRow)
|
||||
}
|
||||
|
||||
delete(id: string): void {
|
||||
this.db.prepare('DELETE FROM snapshots WHERE id = ?').run(id)
|
||||
}
|
||||
|
||||
pruneAuto(chapterId: string, max: number): void {
|
||||
const autos = (
|
||||
this.db
|
||||
.prepare(
|
||||
`SELECT id FROM snapshots WHERE chapter_id = ? AND type = 'auto' ORDER BY created_at ASC`
|
||||
)
|
||||
.all(chapterId) as { id: string }[]
|
||||
)
|
||||
const excess = autos.length - max
|
||||
if (excess <= 0) return
|
||||
for (let i = 0; i < excess; i++) {
|
||||
this.delete(autos[i].id)
|
||||
}
|
||||
}
|
||||
|
||||
prunePersist(chapterId: string, max: number): void {
|
||||
const items = (
|
||||
this.db
|
||||
.prepare(
|
||||
`SELECT id FROM snapshots WHERE chapter_id = ? AND type = 'persist' ORDER BY created_at ASC`
|
||||
)
|
||||
.all(chapterId) as { id: string }[]
|
||||
)
|
||||
const excess = items.length - max
|
||||
if (excess <= 0) return
|
||||
for (let i = 0; i < excess; i++) {
|
||||
this.delete(items[i].id)
|
||||
}
|
||||
}
|
||||
|
||||
restore(snapshotId: string): string {
|
||||
const snap = this.get(snapshotId)
|
||||
if (!snap) throw new Error('Snapshot not found')
|
||||
return snap.content
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user