feat(w3): ship v1.4.0 export matrix, cockpit tasks, and focus ambient

Add ExportMatrixService for txt/md/docx/pdf, extend ExportModal, cockpit outline tasks, focus ambient sound, and E2E-PDF-01 with 134 passing unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 16:56:53 +08:00
parent c10be87d18
commit c5c0b7329b
23 changed files with 890 additions and 37 deletions
+58
View File
@@ -0,0 +1,58 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtempSync, rmSync, existsSync, readFileSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'
import { BookRegistryService } from '../../src/main/services/book-registry'
import { GlobalSettingsService } from '../../src/main/services/global-settings'
import { ExportMatrixService } from '../../src/main/services/export-matrix.service'
import { closeAllBookDbs } from '../../src/main/db/connection'
describe('ExportMatrixService', () => {
let userData: string
let registry: BookRegistryService
let settings: GlobalSettingsService
let matrix: ExportMatrixService
beforeEach(() => {
userData = mkdtempSync(join(tmpdir(), 'bilin-export-matrix-'))
registry = new BookRegistryService(userData)
settings = new GlobalSettingsService(userData)
matrix = new ExportMatrixService(registry, settings)
})
afterEach(() => {
closeAllBookDbs()
rmSync(userData, { recursive: true, force: true })
})
it('exports chapter as txt', async () => {
const book = registry.create({ name: '导出矩阵书', category: '玄幻', createSampleChapter: true })
registry.open(book.id)
const chapter = registry.getChapterRepo(book.id).list()[0]
registry.getChapterRepo(book.id).update(chapter.id, {
content: '<p>矩阵导出测试正文。</p>'
})
const dest = join(userData, 'chapter.txt')
await matrix.exportToPath(
{ bookId: book.id, scope: 'chapter', scopeId: chapter.id, format: 'txt' },
dest
)
const text = readFileSync(dest, 'utf8')
expect(text).toContain('矩阵导出测试正文')
expect(existsSync(dest)).toBe(true)
})
it('IT-DOCX-01: exports volume docx with table of contents marker', async () => {
const book = registry.create({ name: 'Docx书', category: '玄幻', createSampleChapter: true })
registry.open(book.id)
const chapterRepo = registry.getChapterRepo(book.id)
const ch = chapterRepo.list()[0]
chapterRepo.update(ch.id, { title: '开篇', content: '<p>Docx正文。</p>' })
const dest = join(userData, 'book.docx')
await matrix.exportToPath({ bookId: book.id, scope: 'book', format: 'docx' }, dest)
expect(existsSync(dest)).toBe(true)
expect(readFileSync(dest).length).toBeGreaterThan(1000)
})
})