aac51bf183
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>
69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import type { SearchSourceKind } from '../../shared/types'
|
|
import { stripHtml } from './word-count'
|
|
import type { SqliteDb } from '../db/types'
|
|
|
|
export class FtsSyncService {
|
|
upsert(db: SqliteDb, kind: SearchSourceKind, id: string, title: string, body: string): void {
|
|
const plainBody = stripHtml(body)
|
|
db.prepare('DELETE FROM search_fts WHERE source_kind = ? AND source_id = ?').run(kind, id)
|
|
db.prepare(
|
|
'INSERT INTO search_fts (source_kind, source_id, title, body) VALUES (?, ?, ?, ?)'
|
|
).run(kind, id, title, plainBody)
|
|
}
|
|
|
|
remove(db: SqliteDb, kind: SearchSourceKind, id: string): void {
|
|
db.prepare('DELETE FROM search_fts WHERE source_kind = ? AND source_id = ?').run(kind, id)
|
|
}
|
|
|
|
rebuildAll(db: SqliteDb): void {
|
|
db.exec('DELETE FROM search_fts')
|
|
|
|
const chapters = db.prepare('SELECT id, title, content FROM chapters').all() as {
|
|
id: string
|
|
title: string
|
|
content: string
|
|
}[]
|
|
for (const ch of chapters) {
|
|
this.upsert(db, 'chapter', ch.id, ch.title, ch.content)
|
|
}
|
|
|
|
const outlines = db.prepare('SELECT id, title, description FROM outline_items').all() as {
|
|
id: string
|
|
title: string
|
|
description: string
|
|
}[]
|
|
for (const o of outlines) {
|
|
this.upsert(db, 'outline', o.id, o.title, o.description)
|
|
}
|
|
|
|
const settings = db.prepare('SELECT id, name, description FROM settings').all() as {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
}[]
|
|
for (const s of settings) {
|
|
this.upsert(db, 'setting', s.id, s.name, s.description)
|
|
}
|
|
|
|
const inspirations = db.prepare('SELECT id, title, content FROM inspiration').all() as {
|
|
id: string
|
|
title: string
|
|
content: string
|
|
}[]
|
|
for (const i of inspirations) {
|
|
this.upsert(db, 'inspiration', i.id, i.title, i.content)
|
|
}
|
|
|
|
const bookmarks = db.prepare('SELECT id, label, chapter_id FROM bookmarks').all() as {
|
|
id: string
|
|
label: string
|
|
chapter_id: string
|
|
}[]
|
|
for (const b of bookmarks) {
|
|
this.upsert(db, 'bookmark', b.id, b.label, b.label)
|
|
}
|
|
}
|
|
}
|
|
|
|
export const ftsSync = new FtsSyncService()
|