readshell 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.js +51 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -518,6 +518,12 @@ var zh_default = {
|
|
|
518
518
|
"cli.lang.help": "\u8BED\u8A00\u4EE3\u7801: zh | en",
|
|
519
519
|
"cli.lang.success": "\u2713 \u8BED\u8A00\u5DF2\u5207\u6362\u4E3A: {0}",
|
|
520
520
|
"cli.lang.unsupported": "\u2717 \u4E0D\u652F\u6301\u7684\u8BED\u8A00: {0}",
|
|
521
|
+
"cli.update.desc": "\u68C0\u67E5\u6700\u65B0\u7248\u672C\u5E76\u81EA\u52A8\u66F4\u65B0",
|
|
522
|
+
"cli.update.checking": "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...",
|
|
523
|
+
"cli.update.latest": "\u2713 \u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C (v{0})",
|
|
524
|
+
"cli.update.updating": "\u53D1\u73B0\u65B0\u7248\u672C v{0} (\u5F53\u524D v{1})\uFF0C\u6B63\u5728\u66F4\u65B0...",
|
|
525
|
+
"cli.update.success": "\u2713 \u5347\u7EA7\u6210\u529F\uFF01\u8BF7\u91CD\u65B0\u8FD0\u884C novel \u547D\u4EE4\u3002",
|
|
526
|
+
"cli.update.fail": "\u2717 \u5347\u7EA7\u5931\u8D25: {0}",
|
|
521
527
|
// TUI - Library
|
|
522
528
|
"tui.lib.loading": "\u{1F4DA} \u52A0\u8F7D\u4E66\u67B6...",
|
|
523
529
|
"tui.lib.empty.title": "\u{1F4DA} \u4E66\u67B6",
|
|
@@ -574,6 +580,12 @@ var en = {
|
|
|
574
580
|
"cli.lang.help": "Language code: zh | en",
|
|
575
581
|
"cli.lang.success": "\u2713 Language switched to: {0}",
|
|
576
582
|
"cli.lang.unsupported": "\u2717 Unsupported language: {0}",
|
|
583
|
+
"cli.update.desc": "Check for the latest version and update automatically",
|
|
584
|
+
"cli.update.checking": "Checking for updates...",
|
|
585
|
+
"cli.update.latest": "\u2713 Already up to date (v{0})",
|
|
586
|
+
"cli.update.updating": "New version v{0} found (current v{1}), updating...",
|
|
587
|
+
"cli.update.success": "\u2713 Successfully updated! Please restart novel command.",
|
|
588
|
+
"cli.update.fail": "\u2717 Update failed: {0}",
|
|
577
589
|
// TUI - Library
|
|
578
590
|
"tui.lib.loading": "\u{1F4DA} Loading library...",
|
|
579
591
|
"tui.lib.empty.title": "\u{1F4DA} Library",
|
|
@@ -1793,9 +1805,47 @@ var langCommand = {
|
|
|
1793
1805
|
}
|
|
1794
1806
|
};
|
|
1795
1807
|
|
|
1808
|
+
// src/cli/commands/update.ts
|
|
1809
|
+
import { execSync } from "child_process";
|
|
1810
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1811
|
+
var updateCommand = {
|
|
1812
|
+
command: "update",
|
|
1813
|
+
describe: t("cli.update.desc"),
|
|
1814
|
+
handler: async () => {
|
|
1815
|
+
try {
|
|
1816
|
+
console.log(t("cli.update.checking"));
|
|
1817
|
+
let localVersion = "0.0.0";
|
|
1818
|
+
try {
|
|
1819
|
+
const pkgUrl = new URL("../../package.json", import.meta.url);
|
|
1820
|
+
const pkg = JSON.parse(readFileSync4(pkgUrl, "utf-8"));
|
|
1821
|
+
localVersion = pkg.version;
|
|
1822
|
+
} catch (err) {
|
|
1823
|
+
localVersion = "0.2.0";
|
|
1824
|
+
}
|
|
1825
|
+
const npmOutput = execSync("npm view readshell version", { encoding: "utf-8" });
|
|
1826
|
+
const latestVersion = npmOutput.trim();
|
|
1827
|
+
if (!latestVersion) {
|
|
1828
|
+
throw new Error("Could not fetch npm version");
|
|
1829
|
+
}
|
|
1830
|
+
if (latestVersion === localVersion) {
|
|
1831
|
+
console.log(t("cli.update.latest", localVersion));
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
console.log(t("cli.update.updating", latestVersion, localVersion));
|
|
1835
|
+
execSync("npm install -g readshell@latest", { stdio: "inherit" });
|
|
1836
|
+
console.log(t("cli.update.success"));
|
|
1837
|
+
} catch (error) {
|
|
1838
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1839
|
+
console.log(t("cli.update.fail", msg));
|
|
1840
|
+
logger.error("\u66F4\u65B0\u5931\u8D25:", error);
|
|
1841
|
+
process.exit(1);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1796
1846
|
// src/cli/parser.ts
|
|
1797
1847
|
function createParser() {
|
|
1798
|
-
return yargs(hideBin(process.argv)).scriptName("novel").usage("$0 <command> [options]").command(importCommand).command(resumeCommand).command(openCommand).command(libraryCommand).command(removeCommand).command(langCommand).demandCommand(1, "\u8BF7\u6307\u5B9A\u4E00\u4E2A\u547D\u4EE4\u3002\u4F7F\u7528 --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002").strict().alias("h", "help").alias("v", "version").version("0.1.0").epilogue("ReadShell \u2014 \u7EC8\u7AEF\u5185\u4F4E\u6253\u65AD\u8F7B\u9605\u8BFB\u5DE5\u5177");
|
|
1848
|
+
return yargs(hideBin(process.argv)).scriptName("novel").usage("$0 <command> [options]").command(importCommand).command(resumeCommand).command(openCommand).command(libraryCommand).command(removeCommand).command(langCommand).command(updateCommand).demandCommand(1, "\u8BF7\u6307\u5B9A\u4E00\u4E2A\u547D\u4EE4\u3002\u4F7F\u7528 --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002").strict().alias("h", "help").alias("v", "version").version("0.1.0").epilogue("ReadShell \u2014 \u7EC8\u7AEF\u5185\u4F4E\u6253\u65AD\u8F7B\u9605\u8BFB\u5DE5\u5177");
|
|
1799
1849
|
}
|
|
1800
1850
|
|
|
1801
1851
|
// src/db/migrate.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/parser.ts","../src/services/BookService.ts","../src/db/client.ts","../src/config/paths.ts","../src/utils/logger.ts","../src/db/models/Book.ts","../src/db/models/Chapter.ts","../src/db/models/Recent.ts","../src/db/models/Progress.ts","../src/parsers/TxtParser.ts","../src/parsers/EpubParser.ts","../src/parsers/index.ts","../src/utils/hash.ts","../src/locales/zh.ts","../src/locales/en.ts","../src/config/AppConfig.ts","../src/locales/index.ts","../src/cli/commands/import.ts","../src/services/ProgressService.ts","../src/ui/renderApp.ts","../src/ui/App.tsx","../src/ui/pages/ResumePage.tsx","../src/ui/pages/LibraryPage.tsx","../src/services/RecentService.ts","../src/ui/pages/ReaderPage.tsx","../src/ui/components/TextRenderer.tsx","../src/ui/components/StatusBar.tsx","../src/ui/components/ChapterNav.tsx","../src/ui/hooks/useReader.ts","../src/ui/hooks/useKeyboard.ts","../src/utils/stringWidth.ts","../src/utils/paginate.ts","../src/utils/encoding.ts","../src/services/ChapterService.ts","../src/db/models/Bookmark.ts","../src/services/BookmarkService.ts","../src/utils/bossKey.ts","../src/utils/time.ts","../src/cli/commands/resume.ts","../src/cli/commands/open.ts","../src/cli/commands/library.ts","../src/cli/commands/remove.ts","../src/cli/commands/lang.ts","../src/db/migrate.ts","../src/index.ts"],"sourcesContent":["/**\n * CLI 参数解析器\n * 使用 yargs 解析子命令\n */\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { importCommand } from './commands/import.js';\nimport { resumeCommand } from './commands/resume.js';\nimport { openCommand } from './commands/open.js';\nimport { libraryCommand } from './commands/library.js';\nimport { removeCommand } from './commands/remove.js';\nimport { langCommand } from './commands/lang.js';\n\nexport function createParser() {\n return yargs(hideBin(process.argv))\n .scriptName('novel')\n .usage('$0 <command> [options]')\n .command(importCommand)\n .command(resumeCommand)\n .command(openCommand)\n .command(libraryCommand)\n .command(removeCommand)\n .command(langCommand)\n .demandCommand(1, '请指定一个命令。使用 --help 查看可用命令。')\n .strict()\n .alias('h', 'help')\n .alias('v', 'version')\n .version('0.1.0')\n .epilogue('ReadShell — 终端内低打断轻阅读工具');\n}\n","/**\n * 书籍业务逻辑\n * 书籍 CRUD、导入、去重\n */\n\nimport { resolve } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { nanoid } from 'nanoid';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\nimport { ChapterModel } from '../db/models/Chapter.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { ProgressModel } from '../db/models/Progress.js';\nimport { parseFile } from '../parsers/index.js';\nimport { computeFileHash } from '../utils/hash.js';\nimport { logger } from '../utils/logger.js';\n\nexport class BookService {\n private bookModel = new BookModel();\n private chapterModel = new ChapterModel();\n private recentModel = new RecentModel();\n private progressModel = new ProgressModel();\n\n /**\n * 导入书籍文件\n */\n async importBook(filePath: string): Promise<BookRecord> {\n const absPath = resolve(filePath);\n\n // 检查文件存在\n if (!existsSync(absPath)) {\n throw new Error(`文件不存在: ${absPath}`);\n }\n\n // 检测文件格式\n const format = this.detectFormat(absPath);\n if (!format) {\n throw new Error('不支持的文件格式。目前支持: .txt, .epub');\n }\n\n // 计算文件 hash 用于去重\n const fileHash = await computeFileHash(absPath);\n const existing = this.bookModel.findByHash(fileHash);\n if (existing) {\n logger.debug(`文件已存在: ${existing.title} (${existing.id})`);\n return existing;\n }\n\n // 解析文件\n const parsed = await parseFile(absPath, format);\n\n // 获取文件大小\n const stats = statSync(absPath);\n\n // 创建书籍记录\n const book: BookRecord = {\n id: nanoid(),\n title: parsed.title,\n author: parsed.author || null,\n file_path: absPath,\n format,\n file_hash: fileHash,\n file_size: stats.size,\n created_at: Date.now(),\n };\n\n this.bookModel.insert(book);\n\n // 保存章节索引\n if (parsed.chapters.length > 0) {\n this.chapterModel.insertMany(\n parsed.chapters.map((ch, idx) => ({\n book_id: book.id,\n chapter_no: idx,\n title: ch.title,\n byte_offset: ch.byteOffset,\n })),\n );\n }\n\n logger.debug(`导入成功: ${book.title}`);\n return book;\n }\n\n /**\n * 查找书籍(ID 或模糊匹配书名)\n */\n findBook(target: string): BookRecord | undefined {\n // 先尝试精确 ID 匹配\n const byId = this.bookModel.findById(target);\n if (byId) return byId;\n\n // 再尝试书名模糊匹配\n const results = this.bookModel.searchByTitle(target);\n return results[0];\n }\n\n /**\n * 搜索书籍\n */\n searchBooks(keyword: string): BookRecord[] {\n return this.bookModel.searchByTitle(keyword);\n }\n\n /**\n * 获取所有书籍\n */\n getAllBooks(): BookRecord[] {\n return this.bookModel.findAll();\n }\n\n /**\n * 删除书籍及相关数据\n */\n deleteBook(id: string): void {\n this.chapterModel.deleteByBookId(id);\n this.progressModel.delete(id);\n this.recentModel.delete(id);\n this.bookModel.delete(id);\n }\n\n /**\n * 检测文件格式\n */\n private detectFormat(filePath: string): 'txt' | 'epub' | null {\n const ext = filePath.toLowerCase().split('.').pop();\n if (ext === 'txt') return 'txt';\n if (ext === 'epub') return 'epub';\n return null;\n }\n}\n","/**\n * SQLite 数据库连接初始化\n * 使用 better-sqlite3 同步 API\n */\n\nimport Database from 'better-sqlite3';\nimport { getDbPath } from '../config/paths.js';\nimport { logger } from '../utils/logger.js';\n\nlet db: Database.Database | null = null;\n\n/**\n * 获取数据库连接实例(单例模式)\n */\nexport function getDb(): Database.Database {\n if (!db) {\n const dbPath = getDbPath();\n logger.debug(`数据库路径: ${dbPath}`);\n\n db = new Database(dbPath);\n\n // 启用 WAL 模式以提升写入性能\n db.pragma('journal_mode = WAL');\n // 启用外键约束\n db.pragma('foreign_keys = ON');\n }\n\n return db;\n}\n\n/**\n * 关闭数据库连接\n */\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n logger.debug('数据库连接已关闭');\n }\n}\n\n// 进程退出时自动关闭数据库\nprocess.on('exit', () => closeDb());\nprocess.on('SIGINT', () => {\n closeDb();\n process.exit(0);\n});\nprocess.on('SIGTERM', () => {\n closeDb();\n process.exit(0);\n});\n","/**\n * 跨平台路径管理\n * 数据库、配置文件位置\n */\n\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { mkdirSync, existsSync } from 'fs';\n\n/**\n * 获取应用数据目录\n * ~/.config/readshell/ (macOS/Linux)\n */\nexport function getAppDataDir(): string {\n const platform = process.platform;\n let configDir: string;\n\n if (platform === 'darwin') {\n configDir = join(homedir(), 'Library', 'Application Support', 'readshell');\n } else if (platform === 'win32') {\n configDir = join(process.env['APPDATA'] || join(homedir(), 'AppData', 'Roaming'), 'readshell');\n } else {\n // Linux / 其他\n configDir = join(process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config'), 'readshell');\n }\n\n // 确保目录存在\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n return configDir;\n}\n\n/**\n * 获取数据库文件路径\n */\nexport function getDbPath(): string {\n return join(getAppDataDir(), 'readshell.db');\n}\n\n/**\n * 获取配置文件路径\n */\nexport function getConfigPath(): string {\n return join(getAppDataDir(), 'config.json');\n}\n","/**\n * 调试日志\n * 仅 DEBUG=1 时输出\n */\n\nconst isDebug = process.env['DEBUG'] === '1' || process.env['DEBUG'] === 'true';\n\nexport const logger = {\n debug: (...args: unknown[]): void => {\n if (isDebug) {\n console.error('[DEBUG]', ...args);\n }\n },\n\n info: (...args: unknown[]): void => {\n console.error('[INFO]', ...args);\n },\n\n warn: (...args: unknown[]): void => {\n console.error('[WARN]', ...args);\n },\n\n error: (...args: unknown[]): void => {\n console.error('[ERROR]', ...args);\n },\n};\n","/**\n * books 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookRecord {\n id: string;\n title: string;\n author: string | null;\n file_path: string;\n format: 'txt' | 'epub';\n file_hash: string;\n file_size: number | null;\n created_at: number;\n}\n\nexport class BookModel {\n /**\n * 插入新书\n */\n insert(book: BookRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO books (id, title, author, file_path, format, file_hash, file_size, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(book.id, book.title, book.author, book.file_path, book.format, book.file_hash, book.file_size, book.created_at);\n }\n\n /**\n * 通过 ID 获取书籍\n */\n findById(id: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE id = ?').get(id) as BookRecord | undefined;\n }\n\n /**\n * 通过文件 hash 查找(去重用)\n */\n findByHash(hash: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE file_hash = ?').get(hash) as BookRecord | undefined;\n }\n\n /**\n * 模糊搜索书名\n */\n searchByTitle(keyword: string): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE title LIKE ? ORDER BY created_at DESC').all(`%${keyword}%`) as BookRecord[];\n }\n\n /**\n * 获取所有书籍\n */\n findAll(): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books ORDER BY created_at DESC').all() as BookRecord[];\n }\n\n /**\n * 删除书籍\n */\n delete(id: string): void {\n const db = getDb();\n db.prepare('DELETE FROM books WHERE id = ?').run(id);\n }\n}\n","/**\n * chapter_index 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ChapterRecord {\n id?: number;\n book_id: string;\n chapter_no: number;\n title: string | null;\n byte_offset: number;\n}\n\nexport class ChapterModel {\n /**\n * 批量插入章节索引\n */\n insertMany(chapters: Omit<ChapterRecord, 'id'>[]): void {\n const db = getDb();\n const stmt = db.prepare(`\n INSERT OR REPLACE INTO chapter_index (book_id, chapter_no, title, byte_offset)\n VALUES (?, ?, ?, ?)\n `);\n\n db.transaction(() => {\n for (const chapter of chapters) {\n stmt.run(chapter.book_id, chapter.chapter_no, chapter.title, chapter.byte_offset);\n }\n })();\n }\n\n /**\n * 获取指定书籍的所有章节\n */\n findByBookId(bookId: string): ChapterRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? ORDER BY chapter_no').all(bookId) as ChapterRecord[];\n }\n\n /**\n * 获取指定章节\n */\n findChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? AND chapter_no = ?').get(bookId, chapterNo) as ChapterRecord | undefined;\n }\n\n /**\n * 获取书籍章节总数\n */\n getChapterCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM chapter_index WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除指定书籍的章节索引\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM chapter_index WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * recent_reads 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface RecentRecord {\n book_id: string;\n opened_at: number;\n open_count: number;\n}\n\nexport class RecentModel {\n /**\n * 记录打开(插入或更新计数)\n */\n recordOpen(bookId: string): void {\n const db = getDb();\n const now = Date.now();\n db.prepare(`\n INSERT INTO recent_reads (book_id, opened_at, open_count)\n VALUES (?, ?, 1)\n ON CONFLICT(book_id) DO UPDATE SET\n opened_at = excluded.opened_at,\n open_count = open_count + 1\n `).run(bookId, now);\n }\n\n /**\n * 获取最近阅读列表(按时间倒序)\n */\n getRecent(limit: number = 20): RecentRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM recent_reads ORDER BY opened_at DESC LIMIT ?').all(limit) as RecentRecord[];\n }\n\n /**\n * 删除记录\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM recent_reads WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * reading_progress 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ProgressRecord {\n book_id: string;\n chapter_no: number;\n byte_offset: number;\n percent: number;\n updated_at: number;\n opened_at: number;\n}\n\nexport class ProgressModel {\n /**\n * 保存或更新阅读进度\n */\n upsert(progress: ProgressRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO reading_progress (book_id, chapter_no, byte_offset, percent, updated_at, opened_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(book_id) DO UPDATE SET\n chapter_no = excluded.chapter_no,\n byte_offset = excluded.byte_offset,\n percent = excluded.percent,\n updated_at = excluded.updated_at,\n opened_at = excluded.opened_at\n `).run(progress.book_id, progress.chapter_no, progress.byte_offset, progress.percent, progress.updated_at, progress.opened_at);\n }\n\n /**\n * 获取指定书籍的阅读进度\n */\n findByBookId(bookId: string): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress WHERE book_id = ?').get(bookId) as ProgressRecord | undefined;\n }\n\n /**\n * 获取最近打开的书籍进度(用于 resume)\n */\n getLastOpened(): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress ORDER BY opened_at DESC LIMIT 1').get() as ProgressRecord | undefined;\n }\n\n /**\n * 删除指定书籍的进度\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM reading_progress WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * TXT 文件解析器\n * 编码检测、章节切割\n */\n\nimport { readFileSync } from 'fs';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\n\nexport interface ParsedChapter {\n title: string;\n byteOffset: number;\n}\n\nexport interface ParsedBook {\n title: string;\n author: string | null;\n content: string;\n chapters: ParsedChapter[];\n}\n\n// 常见的章节标题正则\nconst CHAPTER_PATTERNS = [\n /^第[零一二三四五六七八九十百千万\\d]+[章节回卷集部篇]/m,\n /^Chapter\\s+\\d+/im,\n /^CHAPTER\\s+\\d+/m,\n /^第\\s*\\d+\\s*[章节回]/m,\n];\n\n/**\n * 解析 TXT 文件\n */\nexport async function parseTxt(filePath: string): Promise<ParsedBook> {\n // 读取原始字节\n const buffer = readFileSync(filePath);\n\n // 自动检测编码\n const encoding = detect(buffer) || 'utf-8';\n logger.debug(`检测到编码: ${encoding}`);\n\n // 转换为 UTF-8 字符串\n const content = encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n // 从文件名提取标题\n const title = extractTitle(filePath);\n\n // 提取章节索引\n const chapters = extractChapters(content);\n\n return {\n title,\n author: null,\n content,\n chapters,\n };\n}\n\n/**\n * 从文件名提取书名\n */\nfunction extractTitle(filePath: string): string {\n const basename = filePath.split('/').pop() || filePath;\n // 去掉扩展名\n return basename.replace(/\\.txt$/i, '').trim() || '未命名';\n}\n\n/**\n * 提取章节索引\n */\nfunction extractChapters(content: string): ParsedChapter[] {\n const chapters: ParsedChapter[] = [];\n const lines = content.split('\\n');\n let byteOffset = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n for (const pattern of CHAPTER_PATTERNS) {\n if (pattern.test(trimmed)) {\n chapters.push({\n title: trimmed,\n byteOffset,\n });\n break;\n }\n }\n\n // 计算 byte offset(UTF-8 编码)\n byteOffset += Buffer.byteLength(line + '\\n', 'utf-8');\n }\n\n logger.debug(`检测到 ${chapters.length} 个章节`);\n return chapters;\n}\n","/**\n * EPUB 文件解析器\n * 元数据 + 章节提取,并转为纯文本阅读格式\n */\n\nimport { EPub } from 'epub2';\nimport { convert } from 'html-to-text';\nimport { logger } from '../utils/logger.js';\nimport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 解析 EPUB 文件\n */\nexport async function parseEpub(filePath: string): Promise<ParsedBook> {\n logger.debug(`开始解析 EPUB: ${filePath}`);\n\n const epub = await openEpub(filePath);\n\n const title = epub.metadata.title || '未知书名';\n const author = epub.metadata.creator || null;\n\n const chapters: ParsedChapter[] = [];\n let fullContent = '';\n let currentByteOffset = 0;\n\n // epub.flow contains the reading order\n for (const chapterRef of epub.flow) {\n if (!chapterRef.id) continue;\n\n try {\n const htmlText = await getChapterText(epub, chapterRef.id);\n \n // 使用 html-to-text 转为纯文本,保持换行,去除无关标签\n const plainText = convert(htmlText, {\n wordwrap: false,\n selectors: [\n // 忽略图片和链接\n { selector: 'img', format: 'skip' },\n { selector: 'a', options: { ignoreHref: true } },\n ],\n });\n\n if (!plainText.trim()) continue; // 跳过空章节\n\n const chapterTitle = chapterRef.title || '无标题章节';\n\n chapters.push({\n title: chapterTitle,\n byteOffset: currentByteOffset,\n });\n\n // 加上章节标题作为正文本页头部\n const chapterContent = `\\n\\n${chapterTitle}\\n\\n${plainText}\\n`;\n fullContent += chapterContent;\n\n currentByteOffset += Buffer.byteLength(chapterContent, 'utf-8');\n } catch (err) {\n logger.debug(`警告: 读取章节 ${chapterRef.id} 失败`, err);\n }\n }\n\n return {\n title,\n author,\n content: fullContent,\n chapters,\n };\n}\n\n/**\n * 提取 EPUB 元数据\n */\nexport async function getEpubMetadata(filePath: string): Promise<{ title: string; author: string | null }> {\n const epub = await openEpub(filePath);\n return {\n title: epub.metadata.title || '未知',\n author: epub.metadata.creator || null,\n };\n}\n\n// ============== 辅助函数 ==============\n\n/**\n * 包装 epub2 基于回调的初始化过程为 Promise\n */\nfunction openEpub(filePath: string): Promise<EPub> {\n return new Promise((resolve, reject) => {\n const epub = new EPub(filePath);\n epub.on('error', (err) => reject(err));\n epub.on('end', () => resolve(epub));\n epub.parse();\n });\n}\n\n/**\n * 包装获取单个章节内容为 Promise\n */\nfunction getChapterText(epub: EPub, chapterId: string): Promise<string> {\n return new Promise((resolve, reject) => {\n epub.getChapter(chapterId, (err, text) => {\n if (err) return reject(err);\n resolve(text);\n });\n });\n}\n\n","/**\n * 解析器分发\n * 按文件格式分发到对应解析器\n */\n\nimport { parseTxt, type ParsedBook } from './TxtParser.js';\nimport { parseEpub } from './EpubParser.js';\n\nexport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 根据格式解析文件\n */\nexport async function parseFile(filePath: string, format: 'txt' | 'epub'): Promise<ParsedBook> {\n switch (format) {\n case 'txt':\n return parseTxt(filePath);\n case 'epub':\n return parseEpub(filePath);\n default:\n throw new Error(`不支持的文件格式: ${format}`);\n }\n}\n","/**\n * 文件 hash 计算(去重用)\n */\n\nimport { createHash } from 'crypto';\nimport { readFileSync } from 'fs';\n\n/**\n * 计算文件的 SHA-256 hash\n */\nexport async function computeFileHash(filePath: string): Promise<string> {\n const buffer = readFileSync(filePath);\n const hash = createHash('sha256');\n hash.update(buffer);\n return hash.digest('hex');\n}\n","export default {\n // Common\n 'common.yes': '是',\n 'common.no': '否',\n 'common.confirm': '确认',\n 'common.cancel': '取消',\n 'common.quit': '按 q 退出',\n\n // CLI\n 'cli.import.desc': '导入本地文件或目录到书架',\n 'cli.import.help': '文件或目录路径(支持 .txt / .epub)',\n 'cli.import.success': '✓ 已导入:',\n 'cli.import.fail': '导入失败:',\n 'cli.import.not_found': '路径不存在:',\n 'cli.import.unsupported': '不支持的文件格式。目前支持: .txt, .epub',\n 'cli.import.scan_dir': '扫描目录',\n 'cli.import.found_files': '找到以下书籍:',\n 'cli.import.confirm_batch': '是否确认导入上述 {0} 本书?(y/N)',\n 'cli.import.canceled': '✓ 导入已取消',\n\n 'cli.resume.desc': '恢复上次阅读',\n 'cli.resume.none': '✗ 没有找到最近阅读记录,请先使用 novel import <file> 或 novel open <book-id> 打开一本书。',\n\n 'cli.open.desc': '打开指定书籍',\n 'cli.open.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.open.not_found': '✗ 未找到匹配书籍:',\n\n 'cli.library.desc': '查看书架列表',\n 'cli.library.help': '可选关键字搜索',\n 'cli.library.none': '✗ 书架为空。使用 novel import <file> 导入你的第一本书。',\n 'cli.library.search_none': '📚 未找到匹配「{0}」的书籍。',\n 'cli.library.search_result': '📚 搜索结果 ({0} 本):\\n',\n\n 'cli.remove.desc': '从书架移除书籍(仅删除记录,不删源文件)',\n 'cli.remove.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.remove.not_found': '✗ 未找到匹配书籍:',\n 'cli.remove.success': '✓ 已移除书籍:',\n 'cli.remove.fail': '移除书籍失败:',\n\n 'cli.lang.desc': '切换语言',\n 'cli.lang.help': '语言代码: zh | en',\n 'cli.lang.success': '✓ 语言已切换为: {0}',\n 'cli.lang.unsupported': '✗ 不支持的语言: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 加载书架...',\n 'tui.lib.empty.title': '📚 书架',\n 'tui.lib.empty.desc': '书架为空。使用 novel import <file> 导入你的第一本书。',\n 'tui.lib.title': '📚 书架 ({0} 本)',\n 'tui.lib.tips': ' ↑↓/jk 选择 · Enter 打开 · d/x 删除 · q 退出',\n\n // TUI - Reader\n 'tui.reader.loading': '读取中...',\n 'tui.reader.bookmark_add': '✓ 增加书签: {0}',\n 'tui.reader.status.remaining': '预计剩余 {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[全部章节]',\n 'tui.nav.tab.bookmarks': '[我的书签]',\n 'tui.nav.tips': 'Enter 跳转 · Tab 切换 · Esc/q 关闭',\n 'tui.nav.empty': '没有记录',\n 'tui.nav.page': '第 {0} / {1} 页',\n};\n","import type { LocaleDictionary } from './types.js';\n\nconst en: LocaleDictionary = {\n // Common\n 'common.yes': 'Yes',\n 'common.no': 'No',\n 'common.confirm': 'Confirm',\n 'common.cancel': 'Cancel',\n 'common.quit': 'Press q to quit',\n\n // CLI\n 'cli.import.desc': 'Import local file or directory to library',\n 'cli.import.help': 'File or directory path (supports .txt / .epub)',\n 'cli.import.success': '✓ Imported:',\n 'cli.import.fail': 'Import failed:',\n 'cli.import.not_found': 'Path not found:',\n 'cli.import.unsupported': 'Unsupported file format. Currently supports: .txt, .epub',\n 'cli.import.scan_dir': 'Scanning directory',\n 'cli.import.found_files': 'Found following books:',\n 'cli.import.confirm_batch': 'Confirm importing these {0} books? (y/N)',\n 'cli.import.canceled': '✓ Import canceled',\n\n 'cli.resume.desc': 'Resume last reading',\n 'cli.resume.none': '✗ No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.',\n\n 'cli.open.desc': 'Open specific book',\n 'cli.open.help': 'Book ID or title (fuzzy match)',\n 'cli.open.not_found': '✗ Book not found:',\n\n 'cli.library.desc': 'View library',\n 'cli.library.help': 'Optional keyword search',\n 'cli.library.none': '✗ Library is empty. Use `novel import <file>` to import your first book.',\n 'cli.library.search_none': '📚 No books found matching \"{0}\".',\n 'cli.library.search_result': '📚 Search results ({0} books):\\n',\n\n 'cli.remove.desc': 'Remove book from library (only deletes records, not source file)',\n 'cli.remove.help': 'Book ID or title (fuzzy match)',\n 'cli.remove.not_found': '✗ Book not found:',\n 'cli.remove.success': '✓ Book removed:',\n 'cli.remove.fail': 'Failed to remove book:',\n\n 'cli.lang.desc': 'Switch language',\n 'cli.lang.help': 'Language code: zh | en',\n 'cli.lang.success': '✓ Language switched to: {0}',\n 'cli.lang.unsupported': '✗ Unsupported language: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 Loading library...',\n 'tui.lib.empty.title': '📚 Library',\n 'tui.lib.empty.desc': 'Library is empty. Use `novel import <file>` to import your first book.',\n 'tui.lib.title': '📚 Library ({0} books)',\n 'tui.lib.tips': ' ↑↓/jk select · Enter open · d/x delete · q quit',\n\n // TUI - Reader\n 'tui.reader.loading': 'Loading...',\n 'tui.reader.bookmark_add': '✓ Bookmark added: {0}',\n 'tui.reader.status.remaining': 'Est. remaining {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[All Chapters]',\n 'tui.nav.tab.bookmarks': '[My Bookmarks]',\n 'tui.nav.tips': 'Enter jump · Tab switch · Esc/q close',\n 'tui.nav.empty': 'No records',\n 'tui.nav.page': 'Page {0} / {1}',\n};\n\nexport default en;\n","/**\n * 应用配置管理\n * 读写 ~/.config/readshell/config.json\n */\n\nimport Conf from 'conf';\n\ninterface ReadShellConfig {\n /** 每页显示行数(0 = 自适应终端高度) */\n linesPerPage: number;\n /** 是否显示状态栏 */\n showStatusBar: boolean;\n /** 阅读模式: 'page' | 'scroll' */\n readingMode: 'page' | 'scroll';\n /** 界面语言 */\n language: 'zh' | 'en';\n}\n\nconst defaults: ReadShellConfig = {\n linesPerPage: 0,\n showStatusBar: true,\n readingMode: 'page',\n language: 'zh',\n};\n\nconst config = new Conf<ReadShellConfig>({\n projectName: 'readshell',\n defaults,\n});\n\nexport function getConfig(): ReadShellConfig {\n return {\n linesPerPage: config.get('linesPerPage'),\n showStatusBar: config.get('showStatusBar'),\n readingMode: config.get('readingMode'),\n language: config.get('language'),\n };\n}\n\nexport function setConfig<K extends keyof ReadShellConfig>(key: K, value: ReadShellConfig[K]): void {\n config.set(key, value);\n}\n\nexport { config };\n","import zh from './zh.js';\nimport en from './en.js';\nimport type { LocaleDictionary, LocaleKeys } from './types.js';\nimport { getConfig } from '../config/AppConfig.js';\n\nconst dictionaries: Record<'zh' | 'en', LocaleDictionary> = {\n zh,\n en,\n};\n\nlet currentLang: 'zh' | 'en' = 'zh';\nlet currentDict: LocaleDictionary = dictionaries.zh;\n\n/**\n * 初始化并加载当前配置的语言\n */\nexport function initI18n() {\n const config = getConfig();\n currentLang = config.language || 'zh';\n currentDict = dictionaries[currentLang] || dictionaries.zh;\n}\n\n/**\n * 切换语言字典(运行时热切换支持)\n */\nexport function setLanguage(lang: 'zh' | 'en') {\n currentLang = lang;\n currentDict = dictionaries[lang] || dictionaries.zh;\n}\n\n/**\n * 获取翻译词条\n * 支持占位符 {0}, {1} 替换\n */\nexport function t(key: LocaleKeys, ...args: (string | number)[]): string {\n let template = currentDict[key];\n if (!template) {\n return key;\n }\n \n if (args.length > 0) {\n args.forEach((arg, index) => {\n template = template.replace(`{${index}}`, String(arg));\n });\n }\n return template;\n}\n","/**\n * novel import <file> — 导入本地文件\n * 支持 txt,基础去重,导入后自动入书架\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { statSync, readdirSync } from 'node:fs';\nimport { resolve, join, extname } from 'node:path';\nimport * as readline from 'node:readline/promises';\n\nexport interface ImportArgs {\n file: string;\n}\n\n// 递归扫描目录文件\nfunction scanDirectory(dir: string): string[] {\n let results: string[] = [];\n try {\n const list = readdirSync(dir);\n for (const file of list) {\n const fullPath = join(dir, file);\n const stat = statSync(fullPath);\n if (stat.isDirectory()) {\n results = results.concat(scanDirectory(fullPath));\n } else {\n const ext = extname(fullPath).toLowerCase();\n if (ext === '.txt' || ext === '.epub') {\n results.push(fullPath);\n }\n }\n }\n } catch (err) {\n logger.error('Scan error:', err);\n }\n return results;\n}\n\nexport const importCommand: CommandModule<object, ImportArgs> = {\n command: 'import <file>',\n describe: t('cli.import.desc'),\n builder: (yargs) => {\n return yargs.positional('file', {\n describe: t('cli.import.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const targetPath = resolve(argv.file);\n let stat;\n try {\n stat = statSync(targetPath);\n } catch (e) {\n console.log(`${t('cli.import.not_found')} ${targetPath}`);\n process.exit(1);\n }\n\n const bookService = new BookService();\n\n if (stat.isDirectory()) {\n console.log(`${t('cli.import.scan_dir')} ${targetPath}...`);\n const files = scanDirectory(targetPath);\n\n if (files.length === 0) {\n console.log(`✗ ` + t('cli.import.unsupported'));\n return;\n }\n\n console.log(t('cli.import.found_files'));\n files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const answer = await rl.question(t('cli.import.confirm_batch', files.length) + ' ');\n rl.close();\n\n if (answer.toLowerCase() === 'y') {\n for (const file of files) {\n try {\n const book = await bookService.importBook(file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n } catch (err) {\n console.log(`${t('cli.import.fail')} ${file} - ${err}`);\n }\n }\n } else {\n console.log(t('cli.import.canceled'));\n }\n } else {\n // 单文件导入\n const book = await bookService.importBook(argv.file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n }\n } catch (error) {\n console.log(`${t('cli.import.fail')} ${error}`);\n process.exit(1);\n }\n },\n};\n","/**\n * 阅读进度业务逻辑\n * 进度读写、resume 核心逻辑\n */\n\nimport { ProgressModel, type ProgressRecord } from '../db/models/Progress.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { logger } from '../utils/logger.js';\n\nexport class ProgressService {\n private progressModel = new ProgressModel();\n private recentModel = new RecentModel();\n\n /**\n * 保存阅读进度(退出时调用)\n */\n saveProgress(bookId: string, chapterNo: number, byteOffset: number, percent: number): void {\n const now = Date.now();\n\n this.progressModel.upsert({\n book_id: bookId,\n chapter_no: chapterNo,\n byte_offset: byteOffset,\n percent,\n updated_at: now,\n opened_at: now,\n });\n\n // 同时更新最近阅读记录\n this.recentModel.recordOpen(bookId);\n\n logger.debug(`进度已保存: book=${bookId}, chapter=${chapterNo}, offset=${byteOffset}, ${(percent * 100).toFixed(1)}%`);\n }\n\n /**\n * 获取指定书籍的阅读进度(resume 时调用)\n */\n getProgress(bookId: string): ProgressRecord | undefined {\n return this.progressModel.findByBookId(bookId);\n }\n\n /**\n * 获取最近打开的书籍进度(启动时调用,决定 resume 哪本书)\n */\n getLastOpenedBook(): ProgressRecord | undefined {\n return this.progressModel.getLastOpened();\n }\n}\n","/**\n * Ink TUI 渲染启动函数\n * 封装 ink.render(<App />) 的统一入口\n */\n\nimport React from 'react';\nimport { render } from 'ink';\nimport { App, type PageRoute } from './App.js';\n\ninterface RenderOptions {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\n/**\n * 启动 Ink TUI 应用\n */\nexport function renderApp(options: RenderOptions = {}): void {\n const { initialPage = 'resume', bookId, initialByteOffset } = options;\n\n const { waitUntilExit } = render(\n React.createElement(App, {\n initialPage,\n bookId,\n initialByteOffset,\n }),\n );\n\n waitUntilExit().catch(() => {\n // Ink 退出时的清理\n process.exit(0);\n });\n}\n","/**\n * App 根组件\n * 管理页面路由状态\n */\n\nimport React, { useState } from 'react';\nimport { Box, Text } from 'ink';\nimport { ResumePage } from './pages/ResumePage.js';\nimport { LibraryPage } from './pages/LibraryPage.js';\nimport { ReaderPage } from './pages/ReaderPage.js';\n\nexport type PageRoute = 'resume' | 'library' | 'reader';\n\ninterface AppProps {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\nexport function App({ initialPage = 'resume', bookId, initialByteOffset }: AppProps) {\n const [currentPage, setCurrentPage] = useState<PageRoute>(initialPage);\n const [currentBookId, setCurrentBookId] = useState<string | undefined>(bookId);\n const [currentByteOffset, setCurrentByteOffset] = useState<number | undefined>(initialByteOffset);\n\n const navigateTo = (page: PageRoute, targetBookId?: string, byteOffset?: number) => {\n setCurrentPage(page);\n if (targetBookId) setCurrentBookId(targetBookId);\n if (byteOffset !== undefined) setCurrentByteOffset(byteOffset);\n };\n\n return (\n <Box flexDirection=\"column\" width=\"100%\">\n {currentPage === 'resume' && (\n <ResumePage onNavigate={navigateTo} />\n )}\n {currentPage === 'library' && (\n <LibraryPage onNavigate={navigateTo} />\n )}\n {currentPage === 'reader' && currentBookId && (\n <ReaderPage\n bookId={currentBookId}\n initialByteOffset={currentByteOffset}\n onNavigate={navigateTo}\n />\n )}\n {currentPage === 'reader' && !currentBookId && (\n <Box>\n <Text color=\"red\">错误: 未指定书籍</Text>\n </Box>\n )}\n </Box>\n );\n}\n","/**\n * 继续阅读页\n * 启动默认页,自动查询最近阅读并跳转到阅读器\n */\n\nimport React, { useEffect, useState } from 'react';\nimport { Box, Text, useApp } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\n\ninterface ResumePageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function ResumePage({ onNavigate }: ResumePageProps) {\n const { exit } = useApp();\n const [checking, setChecking] = useState(true);\n\n useEffect(() => {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n setChecking(false);\n return;\n }\n\n // 验证书籍存在\n const bookModel = new BookModel();\n const book = bookModel.findById(lastProgress.book_id);\n\n if (!book) {\n setChecking(false);\n return;\n }\n\n // 自动跳转到阅读器\n onNavigate('reader', book.id, lastProgress.byte_offset);\n }, [onNavigate]);\n\n if (checking) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 检查阅读记录...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">\n 📖 ReadShell — 终端内轻阅读\n </Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>暂无阅读记录。</Text>\n <Text dimColor>使用 novel import <file> 导入一本书开始阅读。</Text>\n <Box marginTop={1}>\n <Text dimColor>按 q 退出</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n","/**\n * 书架页\n * 交互式书架列表,上下键选择,Enter 打开阅读\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport type { BookRecord } from '../../db/models/Book.js';\nimport { t } from '../../locales/index.js';\n\ninterface LibraryPageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function LibraryPage({ onNavigate }: LibraryPageProps) {\n const { exit } = useApp();\n const [books, setBooks] = useState<BookRecord[]>([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [loading, setLoading] = useState(true);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useEffect(() => {\n const recentService = new RecentService();\n const recentBooks = recentService.getRecentBooks();\n\n if (recentBooks.length > 0) {\n setBooks(recentBooks);\n } else {\n const bookService = new BookService();\n setBooks(bookService.getAllBooks());\n }\n setLoading(false);\n }, []);\n\n useInput((input, key) => {\n if (input === 'q') {\n exit();\n return;\n }\n\n if (books.length === 0) return;\n\n // 上下导航\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n }\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(prev + 1, books.length - 1));\n }\n\n // 删除选中的书 (backspace 键或 d 键)\n if (key.backspace || key.delete || input === 'd' || input === 'x') {\n const selected = books[selectedIndex];\n if (selected) {\n const bookService = new BookService();\n bookService.deleteBook(selected.id);\n \n // 更新列表\n setBooks((prev) => {\n const next = prev.filter((b) => b.id !== selected.id);\n // 调整选中游标,防止越界\n if (selectedIndex >= next.length) {\n setSelectedIndex(Math.max(0, next.length - 1));\n }\n return next;\n });\n }\n return;\n }\n\n // Enter 打开选中的书\n if (key.return) {\n const selected = books[selectedIndex];\n if (selected) {\n const progressService = new ProgressService();\n const progress = progressService.getProgress(selected.id);\n onNavigate('reader', selected.id, progress?.byte_offset ?? 0);\n }\n }\n }, { isActive: isRawModeSupported });\n\n if (loading) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">{t('tui.lib.loading')}</Text>\n </Box>\n );\n }\n\n if (books.length === 0) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.empty.title')}</Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>{t('tui.lib.empty.desc')}</Text>\n <Box marginTop={1}>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.title', books.length)}</Text>\n <Text dimColor>{t('tui.lib.tips')}</Text>\n <Box flexDirection=\"column\" marginTop={1}>\n {books.map((book, index) => {\n const isSelected = index === selectedIndex;\n \n return (\n <Box key={book.id} paddingX={1} justifyContent=\"space-between\">\n <Box>\n <Text\n color={isSelected ? 'cyan' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▸ ' : ' '}\n {book.title}\n </Text>\n <Text dimColor> ({book.format})</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n </Box>\n );\n}\n","/**\n * 最近阅读业务逻辑\n */\n\nimport { RecentModel } from '../db/models/Recent.js';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\n\nexport class RecentService {\n private recentModel = new RecentModel();\n private bookModel = new BookModel();\n\n /**\n * 获取最近阅读的书籍列表(包含书籍详情)\n */\n getRecentBooks(limit: number = 20): BookRecord[] {\n const recentRecords = this.recentModel.getRecent(limit);\n\n return recentRecords\n .map((record) => this.bookModel.findById(record.book_id))\n .filter((book): book is BookRecord => book !== undefined);\n }\n\n /**\n * 记录打开事件\n */\n recordOpen(bookId: string): void {\n this.recentModel.recordOpen(bookId);\n }\n}\n","/**\n * 阅读器页 — 核心阅读体验\n *\n * 功能:\n * - 加载书籍文件内容,按终端尺寸分页\n * - 键盘翻页(空格/j/↓ 下一页, k/↑ 上一页)\n * - 状态栏显示书名 + 进度百分比\n * - 退出时(q)自动保存进度\n * - resume 进入时根据 byte_offset 定位到对应页\n */\n\nimport React, { useState, useEffect, useRef, useMemo } from 'react';\nimport { Box, Text, useApp, useStdout } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { TextRenderer } from '../components/TextRenderer.js';\nimport { StatusBar } from '../components/StatusBar.js';\nimport { ChapterNav } from '../components/ChapterNav.js';\nimport { useReader } from '../hooks/useReader.js';\nimport { useKeyboard } from '../hooks/useKeyboard.js';\nimport { paginate, type Page } from '../../utils/paginate.js';\nimport { readFileWithEncoding } from '../../utils/encoding.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { ChapterService } from '../../services/ChapterService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport { BookmarkService } from '../../services/BookmarkService.js';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { triggerBossKey } from '../../utils/bossKey.js';\nimport { estimateReadingTime, formatReadingTime } from '../../utils/time.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\ninterface ReaderPageProps {\n bookId: string;\n initialByteOffset?: number;\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\n/**\n * 阅读器内部组件 — 在 pages 准备好后渲染\n */\nfunction ReaderContent({\n book,\n bookId,\n pages,\n initialByteOffset,\n termHeight,\n contentHeight,\n}: {\n book: BookRecord;\n bookId: string;\n pages: Page[];\n initialByteOffset?: number;\n termHeight: number;\n contentHeight: number;\n}) {\n const { exit } = useApp();\n const [chapterTitle, setChapterTitle] = useState<string | undefined>();\n const [currentChapter, setCurrentChapter] = useState<ChapterRecord | undefined>();\n const [showChapterNav, setShowChapterNav] = useState(false);\n const [allChapters, setAllChapters] = useState<ChapterRecord[]>([]);\n const [allBookmarks, setAllBookmarks] = useState<BookmarkRecord[]>([]);\n const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n const progressServiceRef = useRef(new ProgressService());\n const chapterServiceRef = useRef(new ChapterService());\n const bookmarkServiceRef = useRef(new BookmarkService());\n\n const reader = useReader(pages, initialByteOffset);\n\n // 加载并更新当前章节信息\n useEffect(() => {\n const currentOffset = reader.getCurrentOffset();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, currentOffset);\n setCurrentChapter(chapter ?? undefined);\n setChapterTitle(chapter?.title ?? undefined);\n }, [reader.currentPage, bookId]);\n\n // 获取该书全部章节及书签用于渲染导航\n useEffect(() => {\n const chaptersList = chapterServiceRef.current.getChaptersByBookId(bookId);\n setAllChapters(chaptersList);\n \n if (showChapterNav) {\n setAllBookmarks(bookmarkServiceRef.current.getBookmarksByBookId(bookId));\n }\n }, [bookId, showChapterNav]);\n\n /**\n * 取当前页首行生成书签名\n */\n const handleAddBookmark = () => {\n const currentPageInfo = reader.getCurrentPage();\n if (!currentPageInfo) return;\n\n let markTitle = '无标题书签';\n for (const line of currentPageInfo.lines) {\n const stripped = line.trim();\n if (stripped.length > 0) {\n markTitle = stripped.slice(0, 15) + (stripped.length > 15 ? '...' : '');\n break;\n }\n }\n\n const currentOffset = reader.getCurrentOffset();\n bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);\n \n setToastMessage(t('tui.reader.bookmark_add', markTitle));\n setTimeout(() => setToastMessage(null), 2000);\n };\n\n /**\n * 自动在组件卸载时保存进度\n */\n useEffect(() => {\n return () => {\n const offset = reader.getCurrentOffset();\n const percent = reader.getPercent();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);\n const chapterNo = chapter?.chapter_no ?? 0;\n\n progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);\n logger.debug(`进度已保存: offset=${offset}, ${(percent * 100).toFixed(1)}%`);\n };\n }, [bookId, reader]);\n\n // 拦截全局键盘事件\n useKeyboard(\n {\n onNext: () => reader.nextPage(),\n onPrev: () => reader.prevPage(),\n onQuit: () => exit(),\n onChapterList: () => setShowChapterNav(true),\n onBossKey: () => triggerBossKey(),\n onBookmarkAdd: handleAddBookmark,\n },\n !showChapterNav, // 如果浮层显示,则停止普通的阅读快捷键\n );\n\n const currentPage = reader.getCurrentPage();\n const currentLines = currentPage?.lines ?? [];\n // The contentHeight prop is already passed, but the instruction redefines it.\n // Assuming the user intends to use this new calculation for contentHeight within ReaderContent.\n const calculatedContentHeight = Math.max(1, termHeight - 2);\n\n // 计算剩余阅读时间:总字符近似于字节数的 1/3 (utf-8 场景下),中文字符占绝大部分\n const totalChars = (book.file_size ?? 0) / 3;\n const remainingChars = Math.max(0, totalChars * (1 - reader.getPercent()));\n const remainingMinutes = estimateReadingTime(remainingChars, true);\n const remainingTimeStr = formatReadingTime(remainingMinutes);\n\n return (\n <Box flexDirection=\"column\" height={termHeight}>\n {/* 相对定位于容器中,使用 flex 布局进行展现。章节模式下隐藏正文 */}\n {!showChapterNav ? (\n <>\n <Box flexDirection=\"column\" flexGrow={1} paddingX={1}>\n <TextRenderer lines={currentLines} height={calculatedContentHeight} />\n </Box>\n <StatusBar\n bookTitle={book.title}\n percent={reader.getPercent()}\n chapterTitle={chapterTitle}\n currentPage={reader.currentPage + 1}\n totalPages={reader.totalPages}\n remainingTime={remainingTimeStr}\n />\n {toastMessage && (\n <Box alignSelf=\"flex-end\" marginTop={-2} marginRight={1} borderStyle=\"round\" borderColor=\"green\" paddingX={1}>\n <Text color=\"green\">{toastMessage}</Text>\n </Box>\n )}\n </>\n ) : (\n <ChapterNav\n chapters={allChapters}\n bookmarks={allBookmarks}\n currentChapterId={currentChapter?.id}\n termHeight={termHeight}\n onSelect={(offset) => {\n reader.goToOffset(offset);\n setShowChapterNav(false);\n }}\n onClose={() => setShowChapterNav(false)}\n />\n )}\n </Box>\n );\n}\n\nexport function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }: ReaderPageProps) {\n const { exit } = useApp();\n const { stdout } = useStdout();\n\n const [book, setBook] = useState<BookRecord | null>(null);\n const [pages, setPages] = useState<Page[] | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const termWidth = stdout?.columns ?? 80;\n const termHeight = stdout?.rows ?? 24;\n const contentHeight = Math.max(termHeight - 3, 5);\n\n // 加载书籍内容\n useEffect(() => {\n try {\n const bookModel = new BookModel();\n const bookRecord = bookModel.findById(bookId);\n\n if (!bookRecord) {\n setError(`书籍不存在: ${bookId}`);\n return;\n }\n\n setBook(bookRecord);\n\n // 记录打开事件\n const recentService = new RecentService();\n recentService.recordOpen(bookId);\n\n // 读取文件内容\n const content = readFileWithEncoding(bookRecord.file_path);\n\n // 分页\n const paginatedPages = paginate(content, termWidth - 2, contentHeight);\n setPages(paginatedPages);\n\n logger.debug(`加载完成: ${bookRecord.title}, ${paginatedPages.length} 页`);\n } catch (err) {\n setError(`加载失败: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, [bookId, termWidth, contentHeight]);\n\n // 非 TTY 下支持 q 退出\n useKeyboard({\n onQuit: () => exit(),\n });\n\n // 错误\n if (error) {\n return (\n <Box padding={1} flexDirection=\"column\">\n <Text color=\"red\">✗ {error}</Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n );\n }\n\n // 加载中\n if (!book || !pages) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 {t('tui.reader.loading')}</Text>\n </Box>\n );\n }\n\n // pages 准备好后渲染阅读器内容\n return (\n <ReaderContent\n book={book}\n bookId={bookId}\n pages={pages}\n initialByteOffset={initialByteOffset}\n termHeight={termHeight}\n contentHeight={contentHeight}\n />\n );\n}\n","/**\n * 正文渲染组件\n * 显示分页后的文本行,并支持智能高亮\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\n\ninterface TextRendererProps {\n lines: string[];\n height?: number;\n}\n\n/**\n * 处理单行内的智能高亮匹配\n * 例如将 「XXX」或 “XXX” 进行暗化或着色,提升大段文字沉浸感\n */\nfunction renderLineWithHighlight(line: string) {\n if (!line) return <Text> </Text>; // 保留空行\n\n // 匹配对话大纲的正则 (包括中英文常见方括号/双引号)\n // 此处拆分为:对话段落与非对话段落\n // (「.*?」|“.*?”|『.*?』|《.*?》)\n const regex = /(「.*?」|“.*?”|『.*?』|《.*?》)/g;\n const parts = line.split(regex);\n\n return (\n <Text>\n {parts.map((part, index) => {\n // 如果是正则匹配出的组(即被高亮的部分)\n if (regex.test(part)) {\n // 由于 Regex 的全局状态,test 之后要注意\n // 但其实 split 后,奇数项是捕获组的内容\n }\n \n // 简单判定:如果是奇数项,就是捕获组\n const isHighlight = index % 2 === 1;\n\n if (isHighlight) {\n // 对话颜色应用 dimColor,让环境和描述语句突出,或者是相反。\n // 这里将对话变暗 (dimColor),减轻视觉疲劳\n return <Text key={index} dimColor>{part}</Text>;\n }\n\n // 常规文本\n return <Text key={index}>{part}</Text>;\n })}\n </Text>\n );\n}\n\nexport function TextRenderer({ lines, height }: TextRendererProps) {\n // 补齐空行以占满屏幕高度(避免内容跳动)\n const displayLines = [...lines];\n if (height && displayLines.length < height) {\n const padding = height - displayLines.length;\n for (let i = 0; i < padding; i++) {\n displayLines.push('');\n }\n }\n\n return (\n <Box flexDirection=\"column\">\n {displayLines.map((line, index) => (\n <Box key={index}>\n {renderLineWithHighlight(line)}\n </Box>\n ))}\n </Box>\n );\n}\n","/**\n * 底部状态栏组件\n * 显示书名、章节名、页码和进度百分比\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\nimport { t } from '../../locales/index.js';\n\ninterface StatusBarProps {\n bookTitle: string;\n chapterTitle?: string;\n percent: number;\n currentPage: number;\n totalPages: number;\n remainingTime?: string;\n}\n\nexport function StatusBar({\n bookTitle,\n chapterTitle,\n percent,\n currentPage,\n totalPages,\n remainingTime,\n}: StatusBarProps) {\n const displayPercent = (percent * 100).toFixed(1);\n const titleDisplay = chapterTitle ? `${bookTitle} · ${chapterTitle}` : bookTitle;\n\n return (\n <Box flexDirection=\"row\" justifyContent=\"space-between\" borderStyle=\"single\" borderTop={false} borderLeft={false} borderRight={false} paddingX={1}>\n <Box>\n <Text color=\"gray\">📖 {titleDisplay}</Text>\n </Box>\n\n <Box>\n {remainingTime && (\n <Text dimColor>{t('tui.reader.status.remaining', remainingTime)} </Text>\n )}\n <Text color=\"gray\">\n {currentPage}/{totalPages} \n </Text>\n <Text color=\"gray\">\n {displayPercent}% \n </Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 章节导航浮层组件\n * 允许在阅读器内唤起章节列表,并支持翻页与选择跳转\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { t } from '../../locales/index.js';\n\ninterface ChapterNavProps {\n chapters: ChapterRecord[];\n bookmarks: BookmarkRecord[];\n currentChapterId?: number;\n termHeight: number;\n onSelect: (byteOffset: number) => void;\n onClose: () => void;\n}\n\nexport function ChapterNav({\n chapters,\n bookmarks,\n currentChapterId,\n termHeight,\n onSelect,\n onClose,\n}: ChapterNavProps) {\n const [activeTab, setActiveTab] = useState<'chapters' | 'bookmarks'>('chapters');\n const isBookmarks = activeTab === 'bookmarks';\n const currentList = isBookmarks ? bookmarks : chapters;\n\n // 找到当前章节的初始索引\n const initialIndex = currentChapterId && !isBookmarks\n ? Math.max(\n 0,\n chapters.findIndex((c) => c.id === currentChapterId),\n )\n : 0;\n\n const [selectedIndex, setSelectedIndex] = useState(initialIndex);\n\n // 切换 tab 时重置索引\n useEffect(() => {\n setSelectedIndex(isBookmarks ? 0 : initialIndex);\n }, [activeTab, initialIndex, isBookmarks]);\n\n // 一页展示多少个项(留出上下边距)\n const pageSize = Math.max(5, termHeight - 6);\n\n // 计算当前可视窗口的起始和结束索引\n const windowStart = Math.max(0, Math.floor(selectedIndex / pageSize) * pageSize);\n const visibleItems = currentList.slice(windowStart, windowStart + pageSize);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useInput(\n (input, key) => {\n // 退出\n if (key.escape || input === 'q') {\n onClose();\n return;\n }\n \n // 切换视图\n if (key.tab) {\n setActiveTab(prev => prev === 'chapters' ? 'bookmarks' : 'chapters');\n return;\n }\n\n // 确认\n if (key.return) {\n if (currentList[selectedIndex]) {\n onSelect(currentList[selectedIndex].byte_offset);\n }\n return;\n }\n\n // 上移\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(0, prev - 1));\n }\n\n // 下移\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(currentList.length - 1, prev + 1));\n }\n },\n { isActive: isRawModeSupported },\n );\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor=\"green\"\n paddingX={2}\n paddingY={1}\n width=\"80%\"\n alignSelf=\"center\"\n marginTop={2}\n >\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Box>\n <Text bold color={activeTab === 'chapters' ? 'green' : 'gray'}>{t('tui.nav.tab.chapters')} </Text>\n <Text bold color={activeTab === 'bookmarks' ? 'green' : 'gray'}>{t('tui.nav.tab.bookmarks')}</Text>\n </Box>\n <Text dimColor>{t('tui.nav.tips')}</Text>\n </Box>\n\n {visibleItems.length === 0 ? (\n <Text dimColor>{t('tui.nav.empty')}</Text>\n ) : (\n visibleItems.map((item, idx) => {\n const actualIndex = windowStart + idx;\n const isSelected = actualIndex === selectedIndex;\n \n return (\n <Text\n key={item.id}\n color={isSelected ? 'green' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▶ ' : ' '}\n {item.title}\n </Text>\n );\n })\n )}\n\n <Box marginTop={1} justifyContent=\"flex-end\">\n <Text dimColor>\n {t('tui.nav.page', Math.floor(selectedIndex / pageSize) + 1, Math.ceil(currentList.length / pageSize) || 1)}\n </Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器核心状态 Hook\n * 管理当前页面、offset、分页等状态\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport type { Page } from '../../utils/paginate.js';\n\ninterface ReaderState {\n currentPage: number;\n totalPages: number;\n}\n\nexport function useReader(pages: Page[], initialByteOffset?: number) {\n // 根据 initialByteOffset 找到初始页\n const initialPage = useMemo(() => {\n if (!initialByteOffset || pages.length === 0) return 0;\n\n // 找到 byte_offset <= initialByteOffset 的最后一页\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= initialByteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n return targetPage;\n }, [pages, initialByteOffset]);\n\n const [state, setState] = useState<ReaderState>({\n currentPage: initialPage,\n totalPages: pages.length,\n });\n\n const nextPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.min(prev.currentPage + 1, prev.totalPages - 1),\n }));\n }, []);\n\n const prevPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(prev.currentPage - 1, 0),\n }));\n }, []);\n\n const goToPage = useCallback((pageNum: number) => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(0, Math.min(pageNum, prev.totalPages - 1)),\n }));\n }, []);\n\n /**\n * 根据 byte_offset 跳转到对应页\n */\n const goToOffset = useCallback((byteOffset: number) => {\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= byteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n setState((prev) => ({\n ...prev,\n currentPage: targetPage,\n }));\n }, [pages]);\n\n const getCurrentPage = useCallback((): Page | undefined => {\n return pages[state.currentPage];\n }, [state.currentPage, pages]);\n\n /**\n * 获取当前页的 byte_offset(用于保存进度)\n */\n const getCurrentOffset = useCallback((): number => {\n return pages[state.currentPage]?.byteOffset ?? 0;\n }, [state.currentPage, pages]);\n\n const getPercent = useCallback((): number => {\n if (state.totalPages === 0) return 0;\n return (state.currentPage + 1) / state.totalPages;\n }, [state.currentPage, state.totalPages]);\n\n const isFirstPage = state.currentPage === 0;\n const isLastPage = state.currentPage === state.totalPages - 1;\n\n return {\n ...state,\n nextPage,\n prevPage,\n goToPage,\n goToOffset,\n getCurrentPage,\n getCurrentOffset,\n getPercent,\n isFirstPage,\n isLastPage,\n };\n}\n","/**\n * 键盘事件统一管理 Hook\n * 在非 TTY 环境下安全降级\n */\n\nimport { useInput } from 'ink';\n\ninterface KeyboardHandlers {\n onNext?: () => void;\n onPrev?: () => void;\n onQuit?: () => void;\n onChapterList?: () => void;\n onHelp?: () => void;\n onBossKey?: () => void;\n onBookmarkAdd?: () => void;\n}\n\nexport function useKeyboard(handlers: KeyboardHandlers, isActive: boolean = true) {\n const isRawModeSupported = process.stdin.isTTY ?? false;\n const shouldListen = isRawModeSupported && isActive;\n\n useInput((input, key) => {\n // 翻页:空格 / j / 下箭头 / f → 下一页\n if (input === ' ' || input === 'j' || key.downArrow || input === 'f') {\n handlers.onNext?.();\n }\n\n // 上一页:k / 上箭头 / b\n if (input === 'k' || key.upArrow || input === 'b') {\n handlers.onPrev?.();\n }\n\n // 退出:q\n if (input === 'q') {\n handlers.onQuit?.();\n }\n\n // 章节列表:c\n if (input === 'c') {\n handlers.onChapterList?.();\n }\n\n // 帮助:?\n if (input === '?') {\n handlers.onHelp?.();\n }\n \n // 老板键\n if (handlers.onBossKey && (key.escape || input === 'esc' || input === 'b' || input === 'B')) {\n handlers.onBossKey?.();\n }\n \n // 按 m 或 M 加书签\n if (handlers.onBookmarkAdd && (input === 'm' || input === 'M')) {\n handlers.onBookmarkAdd?.();\n }\n }, { isActive: shouldListen });\n}\n","/**\n * 字符串宽度计算\n * 处理中文字符宽度为 2\n */\n\n/**\n * 获取字符串在终端中的显示宽度\n */\nexport function getStringWidth(str: string): number {\n let width = 0;\n for (const char of str) {\n width += isFullWidth(char) ? 2 : 1;\n }\n return width;\n}\n\n/**\n * 判断字符是否为全角字符\n * CJK 统一表意字符 + 全角标点\n */\nfunction isFullWidth(char: string): boolean {\n const code = char.codePointAt(0);\n if (code === undefined) return false;\n\n return (\n // CJK 统一表意字符\n (code >= 0x4e00 && code <= 0x9fff) ||\n // CJK 统一表意字符扩展 A\n (code >= 0x3400 && code <= 0x4dbf) ||\n // CJK 统一表意字符扩展 B\n (code >= 0x20000 && code <= 0x2a6df) ||\n // CJK 兼容表意字符\n (code >= 0xf900 && code <= 0xfaff) ||\n // 全角 ASCII、全角标点\n (code >= 0xff01 && code <= 0xff60) ||\n (code >= 0xffe0 && code <= 0xffe6) ||\n // CJK 标点符号\n (code >= 0x3000 && code <= 0x303f) ||\n // 日文平假名/片假名\n (code >= 0x3040 && code <= 0x30ff) ||\n // 韩文音节\n (code >= 0xac00 && code <= 0xd7af)\n );\n}\n","/**\n * 文本分页算法\n * 按终端宽高切割文本为页面\n */\n\nimport { getStringWidth } from './stringWidth.js';\n\nexport interface Page {\n /** 页面内容行 */\n lines: string[];\n /** 此页在原始文本中的 byte offset 起始位置 */\n byteOffset: number;\n}\n\n/**\n * 将文本按终端尺寸分页\n * @param text 原始文本\n * @param width 终端宽度(列数)\n * @param height 可用行数(扣除状态栏后)\n * @returns 分页结果\n */\nexport function paginate(text: string, width: number, height: number): Page[] {\n const pages: Page[] = [];\n const rawLines = text.split('\\n');\n\n // 将原始文本行按终端宽度折行\n const wrappedLines: { text: string; byteOffset: number }[] = [];\n let currentOffset = 0;\n\n for (const rawLine of rawLines) {\n const wrapped = wrapLine(rawLine, width);\n for (const line of wrapped) {\n wrappedLines.push({ text: line, byteOffset: currentOffset });\n }\n currentOffset += Buffer.byteLength(rawLine + '\\n', 'utf-8');\n }\n\n // 按高度切割为页\n for (let i = 0; i < wrappedLines.length; i += height) {\n const pageLines = wrappedLines.slice(i, i + height);\n pages.push({\n lines: pageLines.map((l) => l.text),\n byteOffset: pageLines[0]?.byteOffset ?? 0,\n });\n }\n\n return pages;\n}\n\n/**\n * 将一行文本按终端宽度折行\n * 处理中英文混排(中文字符宽度为 2)\n */\nexport function wrapLine(line: string, width: number): string[] {\n if (line.length === 0) return [''];\n\n const result: string[] = [];\n let currentLine = '';\n let currentWidth = 0;\n\n for (const char of line) {\n const charWidth = getStringWidth(char);\n\n if (currentWidth + charWidth > width) {\n result.push(currentLine);\n currentLine = char;\n currentWidth = charWidth;\n } else {\n currentLine += char;\n currentWidth += charWidth;\n }\n }\n\n if (currentLine.length > 0) {\n result.push(currentLine);\n }\n\n return result.length > 0 ? result : [''];\n}\n","/**\n * GBK/UTF-8 自动检测\n */\n\nimport { readFileSync } from 'fs';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\n\n/**\n * 检测文件编码并返回 UTF-8 字符串\n */\nexport function readFileWithEncoding(filePath: string): string {\n const buffer = readFileSync(filePath);\n const encoding = detect(buffer) || 'utf-8';\n\n if (encoding.toLowerCase() === 'utf-8' || encoding.toLowerCase() === 'ascii') {\n return buffer.toString('utf-8');\n }\n\n return iconv.decode(buffer, encoding);\n}\n\n/**\n * 检测文件编码\n */\nexport function detectEncoding(filePath: string): string {\n const buffer = readFileSync(filePath);\n return detect(buffer) || 'utf-8';\n}\n","/**\n * 章节索引业务逻辑\n * 章节索引构建与查询\n */\n\nimport { ChapterModel, type ChapterRecord } from '../db/models/Chapter.js';\n\nexport class ChapterService {\n private chapterModel = new ChapterModel();\n\n /**\n * 获取指定书籍的所有章节\n */\n getChapters(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 获取指定章节信息\n */\n getChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n return this.chapterModel.findChapter(bookId, chapterNo);\n }\n\n /**\n * 获取章节总数\n */\n getChapterCount(bookId: string): number {\n return this.chapterModel.getChapterCount(bookId);\n }\n\n /**\n * 获取指定书籍下的所有章节\n */\n getChaptersByBookId(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 根据 offset 查询当前所属章节(用于高亮当前所在章)\n */\n getChapterByOffset(bookId: string, byteOffset: number): ChapterRecord | undefined {\n const chapters = this.chapterModel.findByBookId(bookId);\n if (chapters.length === 0) return undefined;\n\n // 找到 offset 所在的章节(最后一个 byte_offset <= 给定 offset 的章节)\n let current: ChapterRecord | undefined;\n for (const chapter of chapters) {\n if (chapter.byte_offset <= byteOffset) {\n current = chapter;\n } else {\n break;\n }\n }\n return current;\n }\n}\n","/**\n * bookmarks 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookmarkRecord {\n id?: number;\n book_id: string;\n title: string;\n byte_offset: number;\n created_at: number;\n}\n\nexport class BookmarkModel {\n /**\n * 插入书签\n */\n insert(bookmark: Omit<BookmarkRecord, 'id'>): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO bookmarks (book_id, title, byte_offset, created_at)\n VALUES (?, ?, ?, ?)\n `).run(bookmark.book_id, bookmark.title, bookmark.byte_offset, bookmark.created_at);\n }\n\n /**\n * 获取指定书籍的所有书签\n */\n findByBookId(bookId: string): BookmarkRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? ORDER BY created_at DESC').all(bookId) as BookmarkRecord[];\n }\n\n /**\n * 获取指定书签\n */\n findById(id: number): BookmarkRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE id = ?').get(id) as BookmarkRecord | undefined;\n }\n\n /**\n * 获取书籍书签总数\n */\n getCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM bookmarks WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除书签\n */\n delete(id: number): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE id = ?').run(id);\n }\n\n /**\n * 移除整本书的书签 (配合彻底清理书籍使用)\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * 书签管理服务\n * 处理针对某一位置打标记录以及后续的跳转\n */\n\nimport { BookmarkModel, type BookmarkRecord } from '../db/models/Bookmark.js';\n\nexport class BookmarkService {\n private bookmarkModel: BookmarkModel;\n\n constructor() {\n this.bookmarkModel = new BookmarkModel();\n }\n\n /**\n * 增加一条书签\n * @param title 该书签展现给用户的文案(一句话大纲)\n */\n addBookmark(bookId: string, title: string, byteOffset: number): void {\n this.bookmarkModel.insert({\n book_id: bookId,\n title,\n byte_offset: byteOffset,\n created_at: Date.now(),\n });\n }\n\n /**\n * 罗列该书全部的书签\n */\n getBookmarksByBookId(bookId: string): BookmarkRecord[] {\n return this.bookmarkModel.findByBookId(bookId);\n }\n\n /**\n * 删掉对应书签\n */\n removeBookmark(id: number): void {\n this.bookmarkModel.delete(id);\n }\n}\n","/**\n * 防打断老板键 (Boss Key)\n * 瞬间退出并用伪装日志覆盖屏幕\n */\n\nexport function triggerBossKey(): void {\n // 1. 清空屏幕\n console.clear();\n\n // 2. 打印极度逼真的伪装日志(例如 Vite 启动成功日志)\n const fakeLog = `\n VITE v5.2.8 ready in 213 ms\n\n ➜ Local: http://localhost:5173/\n ➜ Network: use --host to expose\n ➜ press h + enter to show help\n`;\n\n console.log(fakeLog);\n\n // 3. 强制安静终止应用,不留痕迹\n process.exit(0);\n}\n","/**\n * 阅读时间估算\n */\n\n/**\n * 估算阅读时间(分钟)\n * @param charCount 字符数\n * @param isChinese 是否中文内容\n * @returns 估计阅读分钟数\n */\nexport function estimateReadingTime(charCount: number, isChinese: boolean = true): number {\n // 中文平均阅读速度约 500 字/分钟\n // 英文平均阅读速度约 250 词/分钟(约 1250 字符/分钟)\n const charsPerMinute = isChinese ? 500 : 1250;\n return Math.ceil(charCount / charsPerMinute);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatReadingTime(minutes: number): string {\n if (minutes < 60) {\n return `${minutes} 分钟`;\n }\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n return mins > 0 ? `${hours} 小时 ${mins} 分钟` : `${hours} 小时`;\n}\n","/**\n * novel resume — 恢复上次阅读\n * 启动后默认入口,精确恢复到上次 offset,零摩擦\n */\n\nimport type { CommandModule } from 'yargs';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookService } from '../../services/BookService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport const resumeCommand: CommandModule = {\n command: 'resume',\n describe: t('cli.resume.desc'),\n handler: async () => {\n try {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n // 验证书籍仍然存在\n const bookService = new BookService();\n const book = bookService.findBook(lastProgress.book_id);\n if (!book) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n logger.debug(`恢复阅读: ${book.title}, offset=${lastProgress.byte_offset}`);\n\n // 启动 Ink TUI 阅读器,恢复到上次 offset\n renderApp({\n initialPage: 'reader',\n bookId: lastProgress.book_id,\n initialByteOffset: lastProgress.byte_offset,\n });\n } catch (error) {\n logger.error('恢复阅读失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel open <id|name> — 打开指定书\n * 支持 book-id 或模糊匹配书名\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface OpenArgs {\n target: string;\n}\n\nexport const openCommand: CommandModule<object, OpenArgs> = {\n command: 'open <target>',\n describe: t('cli.open.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.open.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.open.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n // 检查是否有之前的阅读进度\n const progressService = new ProgressService();\n const progress = progressService.getProgress(book.id);\n const byteOffset = progress?.byte_offset ?? 0;\n\n logger.debug(`打开: ${book.title}, offset=${byteOffset}`);\n\n // 启动 Ink TUI 阅读器\n renderApp({\n initialPage: 'reader',\n bookId: book.id,\n initialByteOffset: byteOffset,\n });\n } catch (error) {\n logger.error('打开失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel library — 书架列表\n * 含最近阅读排序 + 搜索\n */\n\nimport type { CommandModule } from 'yargs';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface LibraryArgs {\n search?: string;\n}\n\nexport const libraryCommand: CommandModule<object, LibraryArgs> = {\n command: 'library',\n describe: t('cli.library.desc'),\n builder: (yargs) => {\n return yargs.option('search', {\n alias: 's',\n describe: t('cli.library.help'),\n type: 'string',\n });\n },\n handler: async (argv) => {\n try {\n // 如果有搜索参数,以非交互模式输出\n if (argv.search) {\n const bookService = new BookService();\n const books = bookService.searchBooks(argv.search);\n\n if (books.length === 0) {\n console.log(t('cli.library.search_none', argv.search));\n return;\n }\n\n console.log(t('cli.library.search_result', books.length));\n books.forEach((book, index) => {\n console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);\n });\n return;\n }\n\n // 无搜索参数,启动交互式书架\n renderApp({ initialPage: 'library' });\n } catch (error) {\n logger.error('获取书架失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel remove <id|name> — 移除书籍\n * 删除相关所有数据,包括记录和本地进度的 DB 项\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface RemoveArgs {\n target: string;\n}\n\nexport const removeCommand: CommandModule<object, RemoveArgs> = {\n command: 'remove <target>',\n describe: t('cli.remove.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.remove.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.remove.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n bookService.deleteBook(book.id);\n console.log(`${t('cli.remove.success')} ${book.title}`);\n } catch (error) {\n logger.error('移除书籍失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel lang <zh|en> — 切换语言\n */\n\nimport type { CommandModule } from 'yargs';\nimport { setConfig } from '../../config/AppConfig.js';\nimport { t, setLanguage } from '../../locales/index.js';\n\nexport interface LangArgs {\n target: 'zh' | 'en';\n}\n\nexport const langCommand: CommandModule<object, LangArgs> = {\n command: 'lang <target>',\n describe: t('cli.lang.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.lang.help'),\n type: 'string',\n choices: ['zh', 'en'] as const,\n demandOption: true,\n });\n },\n handler: (argv) => {\n const lang = argv.target;\n if (lang === 'zh' || lang === 'en') {\n setConfig('language', lang);\n setLanguage(lang);\n console.log(t('cli.lang.success', lang));\n } else {\n console.log(t('cli.lang.unsupported', lang));\n process.exit(1);\n }\n },\n};\n","/**\n * 数据库迁移逻辑\n * 程序启动时检查并执行建表/迁移\n */\n\nimport { getDb } from './client.js';\nimport { logger } from '../utils/logger.js';\n\nconst SCHEMA_VERSION = 2;\n\n/**\n * 初始化数据库(建表 + 版本管理)\n */\nexport function initDatabase(): void {\n const db = getDb();\n\n // 创建版本管理表\n db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY\n );\n `);\n\n const row = db.prepare('SELECT version FROM schema_version LIMIT 1').get() as\n | { version: number }\n | undefined;\n const currentVersion = row?.version ?? 0;\n\n if (currentVersion < SCHEMA_VERSION) {\n logger.debug(`数据库迁移: v${currentVersion} → v${SCHEMA_VERSION}`);\n migrate(db, currentVersion);\n }\n}\n\nfunction migrate(db: ReturnType<typeof getDb>, fromVersion: number): void {\n const migrations: Record<number, string> = {\n 1: `\n -- 书籍元数据\n CREATE TABLE IF NOT EXISTS books (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n author TEXT,\n file_path TEXT NOT NULL,\n format TEXT NOT NULL,\n file_hash TEXT NOT NULL,\n file_size INTEGER,\n created_at INTEGER NOT NULL\n );\n\n -- 核心状态表\n CREATE TABLE IF NOT EXISTS reading_progress (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n chapter_no INTEGER NOT NULL DEFAULT 0,\n byte_offset INTEGER NOT NULL DEFAULT 0,\n percent REAL NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL,\n opened_at INTEGER NOT NULL\n );\n\n -- 最近阅读排序\n CREATE TABLE IF NOT EXISTS recent_reads (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n opened_at INTEGER NOT NULL,\n open_count INTEGER NOT NULL DEFAULT 1\n );\n\n -- 章节索引\n CREATE TABLE IF NOT EXISTS chapter_index (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n chapter_no INTEGER NOT NULL,\n title TEXT,\n byte_offset INTEGER NOT NULL,\n UNIQUE(book_id, chapter_no)\n );\n\n -- 索引\n CREATE INDEX IF NOT EXISTS idx_chapter_book ON chapter_index(book_id);\n CREATE INDEX IF NOT EXISTS idx_recent_opened ON recent_reads(opened_at DESC);\n `,\n 2: `\n -- 书签管理\n CREATE TABLE IF NOT EXISTS bookmarks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n title TEXT NOT NULL,\n byte_offset INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_bookmarks_book ON bookmarks(book_id);\n `,\n };\n\n db.transaction(() => {\n for (let v = fromVersion + 1; v <= SCHEMA_VERSION; v++) {\n const sql = migrations[v];\n if (sql) {\n db.exec(sql);\n logger.debug(`已执行迁移 v${v}`);\n }\n }\n\n // 更新版本号\n db.prepare('DELETE FROM schema_version').run();\n db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);\n })();\n}\n","#!/usr/bin/env node\n\n/**\n * ReadShell — 终端内低打断轻阅读工具\n * 程序主入口,解析 argv,路由到子命令\n */\n\nimport { createParser } from './cli/parser.js';\nimport { initDatabase } from './db/migrate.js';\nimport { initI18n } from './locales/index.js';\nimport { logger } from './utils/logger.js';\n\nasync function main() {\n try {\n // 初始化数据库(自动建表/迁移)\n initDatabase();\n \n // 初始化多语言本地化模块\n initI18n();\n\n // 解析命令行参数并执行对应命令\n const parser = createParser();\n await parser.parse();\n } catch (error) {\n logger.error('程序启动失败:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["existsSync","db","db","db","db","resolve","readFileSync","existsSync","config","statSync","resolve","join","yargs","React","useState","Box","Text","useState","useEffect","Box","Text","useApp","jsx","jsxs","useApp","useState","useEffect","Box","Text","useState","useEffect","Box","Text","useApp","Box","Text","jsx","Box","Text","jsx","jsxs","Box","Text","useState","useEffect","Box","Text","useInput","jsx","jsxs","useState","useEffect","useInput","Box","Text","useState","useInput","readFileSync","detect","iconv","db","jsx","jsxs","useApp","useState","useEffect","Box","Text","jsx","jsxs","useState","Box","Text","React","yargs","yargs","yargs","yargs","db"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/parser.ts","../src/services/BookService.ts","../src/db/client.ts","../src/config/paths.ts","../src/utils/logger.ts","../src/db/models/Book.ts","../src/db/models/Chapter.ts","../src/db/models/Recent.ts","../src/db/models/Progress.ts","../src/parsers/TxtParser.ts","../src/parsers/EpubParser.ts","../src/parsers/index.ts","../src/utils/hash.ts","../src/locales/zh.ts","../src/locales/en.ts","../src/config/AppConfig.ts","../src/locales/index.ts","../src/cli/commands/import.ts","../src/services/ProgressService.ts","../src/ui/renderApp.ts","../src/ui/App.tsx","../src/ui/pages/ResumePage.tsx","../src/ui/pages/LibraryPage.tsx","../src/services/RecentService.ts","../src/ui/pages/ReaderPage.tsx","../src/ui/components/TextRenderer.tsx","../src/ui/components/StatusBar.tsx","../src/ui/components/ChapterNav.tsx","../src/ui/hooks/useReader.ts","../src/ui/hooks/useKeyboard.ts","../src/utils/stringWidth.ts","../src/utils/paginate.ts","../src/utils/encoding.ts","../src/services/ChapterService.ts","../src/db/models/Bookmark.ts","../src/services/BookmarkService.ts","../src/utils/bossKey.ts","../src/utils/time.ts","../src/cli/commands/resume.ts","../src/cli/commands/open.ts","../src/cli/commands/library.ts","../src/cli/commands/remove.ts","../src/cli/commands/lang.ts","../src/cli/commands/update.ts","../src/db/migrate.ts","../src/index.ts"],"sourcesContent":["/**\n * CLI 参数解析器\n * 使用 yargs 解析子命令\n */\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { importCommand } from './commands/import.js';\nimport { resumeCommand } from './commands/resume.js';\nimport { openCommand } from './commands/open.js';\nimport { libraryCommand } from './commands/library.js';\nimport { removeCommand } from './commands/remove.js';\nimport { langCommand } from './commands/lang.js';\nimport { updateCommand } from './commands/update.js';\n\nexport function createParser() {\n return yargs(hideBin(process.argv))\n .scriptName('novel')\n .usage('$0 <command> [options]')\n .command(importCommand)\n .command(resumeCommand)\n .command(openCommand)\n .command(libraryCommand)\n .command(removeCommand)\n .command(langCommand)\n .command(updateCommand)\n .demandCommand(1, '请指定一个命令。使用 --help 查看可用命令。')\n .strict()\n .alias('h', 'help')\n .alias('v', 'version')\n .version('0.1.0')\n .epilogue('ReadShell — 终端内低打断轻阅读工具');\n}\n","/**\n * 书籍业务逻辑\n * 书籍 CRUD、导入、去重\n */\n\nimport { resolve } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { nanoid } from 'nanoid';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\nimport { ChapterModel } from '../db/models/Chapter.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { ProgressModel } from '../db/models/Progress.js';\nimport { parseFile } from '../parsers/index.js';\nimport { computeFileHash } from '../utils/hash.js';\nimport { logger } from '../utils/logger.js';\n\nexport class BookService {\n private bookModel = new BookModel();\n private chapterModel = new ChapterModel();\n private recentModel = new RecentModel();\n private progressModel = new ProgressModel();\n\n /**\n * 导入书籍文件\n */\n async importBook(filePath: string): Promise<BookRecord> {\n const absPath = resolve(filePath);\n\n // 检查文件存在\n if (!existsSync(absPath)) {\n throw new Error(`文件不存在: ${absPath}`);\n }\n\n // 检测文件格式\n const format = this.detectFormat(absPath);\n if (!format) {\n throw new Error('不支持的文件格式。目前支持: .txt, .epub');\n }\n\n // 计算文件 hash 用于去重\n const fileHash = await computeFileHash(absPath);\n const existing = this.bookModel.findByHash(fileHash);\n if (existing) {\n logger.debug(`文件已存在: ${existing.title} (${existing.id})`);\n return existing;\n }\n\n // 解析文件\n const parsed = await parseFile(absPath, format);\n\n // 获取文件大小\n const stats = statSync(absPath);\n\n // 创建书籍记录\n const book: BookRecord = {\n id: nanoid(),\n title: parsed.title,\n author: parsed.author || null,\n file_path: absPath,\n format,\n file_hash: fileHash,\n file_size: stats.size,\n created_at: Date.now(),\n };\n\n this.bookModel.insert(book);\n\n // 保存章节索引\n if (parsed.chapters.length > 0) {\n this.chapterModel.insertMany(\n parsed.chapters.map((ch, idx) => ({\n book_id: book.id,\n chapter_no: idx,\n title: ch.title,\n byte_offset: ch.byteOffset,\n })),\n );\n }\n\n logger.debug(`导入成功: ${book.title}`);\n return book;\n }\n\n /**\n * 查找书籍(ID 或模糊匹配书名)\n */\n findBook(target: string): BookRecord | undefined {\n // 先尝试精确 ID 匹配\n const byId = this.bookModel.findById(target);\n if (byId) return byId;\n\n // 再尝试书名模糊匹配\n const results = this.bookModel.searchByTitle(target);\n return results[0];\n }\n\n /**\n * 搜索书籍\n */\n searchBooks(keyword: string): BookRecord[] {\n return this.bookModel.searchByTitle(keyword);\n }\n\n /**\n * 获取所有书籍\n */\n getAllBooks(): BookRecord[] {\n return this.bookModel.findAll();\n }\n\n /**\n * 删除书籍及相关数据\n */\n deleteBook(id: string): void {\n this.chapterModel.deleteByBookId(id);\n this.progressModel.delete(id);\n this.recentModel.delete(id);\n this.bookModel.delete(id);\n }\n\n /**\n * 检测文件格式\n */\n private detectFormat(filePath: string): 'txt' | 'epub' | null {\n const ext = filePath.toLowerCase().split('.').pop();\n if (ext === 'txt') return 'txt';\n if (ext === 'epub') return 'epub';\n return null;\n }\n}\n","/**\n * SQLite 数据库连接初始化\n * 使用 better-sqlite3 同步 API\n */\n\nimport Database from 'better-sqlite3';\nimport { getDbPath } from '../config/paths.js';\nimport { logger } from '../utils/logger.js';\n\nlet db: Database.Database | null = null;\n\n/**\n * 获取数据库连接实例(单例模式)\n */\nexport function getDb(): Database.Database {\n if (!db) {\n const dbPath = getDbPath();\n logger.debug(`数据库路径: ${dbPath}`);\n\n db = new Database(dbPath);\n\n // 启用 WAL 模式以提升写入性能\n db.pragma('journal_mode = WAL');\n // 启用外键约束\n db.pragma('foreign_keys = ON');\n }\n\n return db;\n}\n\n/**\n * 关闭数据库连接\n */\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n logger.debug('数据库连接已关闭');\n }\n}\n\n// 进程退出时自动关闭数据库\nprocess.on('exit', () => closeDb());\nprocess.on('SIGINT', () => {\n closeDb();\n process.exit(0);\n});\nprocess.on('SIGTERM', () => {\n closeDb();\n process.exit(0);\n});\n","/**\n * 跨平台路径管理\n * 数据库、配置文件位置\n */\n\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { mkdirSync, existsSync } from 'fs';\n\n/**\n * 获取应用数据目录\n * ~/.config/readshell/ (macOS/Linux)\n */\nexport function getAppDataDir(): string {\n const platform = process.platform;\n let configDir: string;\n\n if (platform === 'darwin') {\n configDir = join(homedir(), 'Library', 'Application Support', 'readshell');\n } else if (platform === 'win32') {\n configDir = join(process.env['APPDATA'] || join(homedir(), 'AppData', 'Roaming'), 'readshell');\n } else {\n // Linux / 其他\n configDir = join(process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config'), 'readshell');\n }\n\n // 确保目录存在\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n return configDir;\n}\n\n/**\n * 获取数据库文件路径\n */\nexport function getDbPath(): string {\n return join(getAppDataDir(), 'readshell.db');\n}\n\n/**\n * 获取配置文件路径\n */\nexport function getConfigPath(): string {\n return join(getAppDataDir(), 'config.json');\n}\n","/**\n * 调试日志\n * 仅 DEBUG=1 时输出\n */\n\nconst isDebug = process.env['DEBUG'] === '1' || process.env['DEBUG'] === 'true';\n\nexport const logger = {\n debug: (...args: unknown[]): void => {\n if (isDebug) {\n console.error('[DEBUG]', ...args);\n }\n },\n\n info: (...args: unknown[]): void => {\n console.error('[INFO]', ...args);\n },\n\n warn: (...args: unknown[]): void => {\n console.error('[WARN]', ...args);\n },\n\n error: (...args: unknown[]): void => {\n console.error('[ERROR]', ...args);\n },\n};\n","/**\n * books 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookRecord {\n id: string;\n title: string;\n author: string | null;\n file_path: string;\n format: 'txt' | 'epub';\n file_hash: string;\n file_size: number | null;\n created_at: number;\n}\n\nexport class BookModel {\n /**\n * 插入新书\n */\n insert(book: BookRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO books (id, title, author, file_path, format, file_hash, file_size, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(book.id, book.title, book.author, book.file_path, book.format, book.file_hash, book.file_size, book.created_at);\n }\n\n /**\n * 通过 ID 获取书籍\n */\n findById(id: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE id = ?').get(id) as BookRecord | undefined;\n }\n\n /**\n * 通过文件 hash 查找(去重用)\n */\n findByHash(hash: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE file_hash = ?').get(hash) as BookRecord | undefined;\n }\n\n /**\n * 模糊搜索书名\n */\n searchByTitle(keyword: string): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE title LIKE ? ORDER BY created_at DESC').all(`%${keyword}%`) as BookRecord[];\n }\n\n /**\n * 获取所有书籍\n */\n findAll(): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books ORDER BY created_at DESC').all() as BookRecord[];\n }\n\n /**\n * 删除书籍\n */\n delete(id: string): void {\n const db = getDb();\n db.prepare('DELETE FROM books WHERE id = ?').run(id);\n }\n}\n","/**\n * chapter_index 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ChapterRecord {\n id?: number;\n book_id: string;\n chapter_no: number;\n title: string | null;\n byte_offset: number;\n}\n\nexport class ChapterModel {\n /**\n * 批量插入章节索引\n */\n insertMany(chapters: Omit<ChapterRecord, 'id'>[]): void {\n const db = getDb();\n const stmt = db.prepare(`\n INSERT OR REPLACE INTO chapter_index (book_id, chapter_no, title, byte_offset)\n VALUES (?, ?, ?, ?)\n `);\n\n db.transaction(() => {\n for (const chapter of chapters) {\n stmt.run(chapter.book_id, chapter.chapter_no, chapter.title, chapter.byte_offset);\n }\n })();\n }\n\n /**\n * 获取指定书籍的所有章节\n */\n findByBookId(bookId: string): ChapterRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? ORDER BY chapter_no').all(bookId) as ChapterRecord[];\n }\n\n /**\n * 获取指定章节\n */\n findChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? AND chapter_no = ?').get(bookId, chapterNo) as ChapterRecord | undefined;\n }\n\n /**\n * 获取书籍章节总数\n */\n getChapterCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM chapter_index WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除指定书籍的章节索引\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM chapter_index WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * recent_reads 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface RecentRecord {\n book_id: string;\n opened_at: number;\n open_count: number;\n}\n\nexport class RecentModel {\n /**\n * 记录打开(插入或更新计数)\n */\n recordOpen(bookId: string): void {\n const db = getDb();\n const now = Date.now();\n db.prepare(`\n INSERT INTO recent_reads (book_id, opened_at, open_count)\n VALUES (?, ?, 1)\n ON CONFLICT(book_id) DO UPDATE SET\n opened_at = excluded.opened_at,\n open_count = open_count + 1\n `).run(bookId, now);\n }\n\n /**\n * 获取最近阅读列表(按时间倒序)\n */\n getRecent(limit: number = 20): RecentRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM recent_reads ORDER BY opened_at DESC LIMIT ?').all(limit) as RecentRecord[];\n }\n\n /**\n * 删除记录\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM recent_reads WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * reading_progress 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ProgressRecord {\n book_id: string;\n chapter_no: number;\n byte_offset: number;\n percent: number;\n updated_at: number;\n opened_at: number;\n}\n\nexport class ProgressModel {\n /**\n * 保存或更新阅读进度\n */\n upsert(progress: ProgressRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO reading_progress (book_id, chapter_no, byte_offset, percent, updated_at, opened_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(book_id) DO UPDATE SET\n chapter_no = excluded.chapter_no,\n byte_offset = excluded.byte_offset,\n percent = excluded.percent,\n updated_at = excluded.updated_at,\n opened_at = excluded.opened_at\n `).run(progress.book_id, progress.chapter_no, progress.byte_offset, progress.percent, progress.updated_at, progress.opened_at);\n }\n\n /**\n * 获取指定书籍的阅读进度\n */\n findByBookId(bookId: string): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress WHERE book_id = ?').get(bookId) as ProgressRecord | undefined;\n }\n\n /**\n * 获取最近打开的书籍进度(用于 resume)\n */\n getLastOpened(): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress ORDER BY opened_at DESC LIMIT 1').get() as ProgressRecord | undefined;\n }\n\n /**\n * 删除指定书籍的进度\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM reading_progress WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * TXT 文件解析器\n * 编码检测、章节切割\n */\n\nimport { readFileSync } from 'fs';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\n\nexport interface ParsedChapter {\n title: string;\n byteOffset: number;\n}\n\nexport interface ParsedBook {\n title: string;\n author: string | null;\n content: string;\n chapters: ParsedChapter[];\n}\n\n// 常见的章节标题正则\nconst CHAPTER_PATTERNS = [\n /^第[零一二三四五六七八九十百千万\\d]+[章节回卷集部篇]/m,\n /^Chapter\\s+\\d+/im,\n /^CHAPTER\\s+\\d+/m,\n /^第\\s*\\d+\\s*[章节回]/m,\n];\n\n/**\n * 解析 TXT 文件\n */\nexport async function parseTxt(filePath: string): Promise<ParsedBook> {\n // 读取原始字节\n const buffer = readFileSync(filePath);\n\n // 自动检测编码\n const encoding = detect(buffer) || 'utf-8';\n logger.debug(`检测到编码: ${encoding}`);\n\n // 转换为 UTF-8 字符串\n const content = encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n // 从文件名提取标题\n const title = extractTitle(filePath);\n\n // 提取章节索引\n const chapters = extractChapters(content);\n\n return {\n title,\n author: null,\n content,\n chapters,\n };\n}\n\n/**\n * 从文件名提取书名\n */\nfunction extractTitle(filePath: string): string {\n const basename = filePath.split('/').pop() || filePath;\n // 去掉扩展名\n return basename.replace(/\\.txt$/i, '').trim() || '未命名';\n}\n\n/**\n * 提取章节索引\n */\nfunction extractChapters(content: string): ParsedChapter[] {\n const chapters: ParsedChapter[] = [];\n const lines = content.split('\\n');\n let byteOffset = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n for (const pattern of CHAPTER_PATTERNS) {\n if (pattern.test(trimmed)) {\n chapters.push({\n title: trimmed,\n byteOffset,\n });\n break;\n }\n }\n\n // 计算 byte offset(UTF-8 编码)\n byteOffset += Buffer.byteLength(line + '\\n', 'utf-8');\n }\n\n logger.debug(`检测到 ${chapters.length} 个章节`);\n return chapters;\n}\n","/**\n * EPUB 文件解析器\n * 元数据 + 章节提取,并转为纯文本阅读格式\n */\n\nimport { EPub } from 'epub2';\nimport { convert } from 'html-to-text';\nimport { logger } from '../utils/logger.js';\nimport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 解析 EPUB 文件\n */\nexport async function parseEpub(filePath: string): Promise<ParsedBook> {\n logger.debug(`开始解析 EPUB: ${filePath}`);\n\n const epub = await openEpub(filePath);\n\n const title = epub.metadata.title || '未知书名';\n const author = epub.metadata.creator || null;\n\n const chapters: ParsedChapter[] = [];\n let fullContent = '';\n let currentByteOffset = 0;\n\n // epub.flow contains the reading order\n for (const chapterRef of epub.flow) {\n if (!chapterRef.id) continue;\n\n try {\n const htmlText = await getChapterText(epub, chapterRef.id);\n \n // 使用 html-to-text 转为纯文本,保持换行,去除无关标签\n const plainText = convert(htmlText, {\n wordwrap: false,\n selectors: [\n // 忽略图片和链接\n { selector: 'img', format: 'skip' },\n { selector: 'a', options: { ignoreHref: true } },\n ],\n });\n\n if (!plainText.trim()) continue; // 跳过空章节\n\n const chapterTitle = chapterRef.title || '无标题章节';\n\n chapters.push({\n title: chapterTitle,\n byteOffset: currentByteOffset,\n });\n\n // 加上章节标题作为正文本页头部\n const chapterContent = `\\n\\n${chapterTitle}\\n\\n${plainText}\\n`;\n fullContent += chapterContent;\n\n currentByteOffset += Buffer.byteLength(chapterContent, 'utf-8');\n } catch (err) {\n logger.debug(`警告: 读取章节 ${chapterRef.id} 失败`, err);\n }\n }\n\n return {\n title,\n author,\n content: fullContent,\n chapters,\n };\n}\n\n/**\n * 提取 EPUB 元数据\n */\nexport async function getEpubMetadata(filePath: string): Promise<{ title: string; author: string | null }> {\n const epub = await openEpub(filePath);\n return {\n title: epub.metadata.title || '未知',\n author: epub.metadata.creator || null,\n };\n}\n\n// ============== 辅助函数 ==============\n\n/**\n * 包装 epub2 基于回调的初始化过程为 Promise\n */\nfunction openEpub(filePath: string): Promise<EPub> {\n return new Promise((resolve, reject) => {\n const epub = new EPub(filePath);\n epub.on('error', (err) => reject(err));\n epub.on('end', () => resolve(epub));\n epub.parse();\n });\n}\n\n/**\n * 包装获取单个章节内容为 Promise\n */\nfunction getChapterText(epub: EPub, chapterId: string): Promise<string> {\n return new Promise((resolve, reject) => {\n epub.getChapter(chapterId, (err, text) => {\n if (err) return reject(err);\n resolve(text);\n });\n });\n}\n\n","/**\n * 解析器分发\n * 按文件格式分发到对应解析器\n */\n\nimport { parseTxt, type ParsedBook } from './TxtParser.js';\nimport { parseEpub } from './EpubParser.js';\n\nexport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 根据格式解析文件\n */\nexport async function parseFile(filePath: string, format: 'txt' | 'epub'): Promise<ParsedBook> {\n switch (format) {\n case 'txt':\n return parseTxt(filePath);\n case 'epub':\n return parseEpub(filePath);\n default:\n throw new Error(`不支持的文件格式: ${format}`);\n }\n}\n","/**\n * 文件 hash 计算(去重用)\n */\n\nimport { createHash } from 'crypto';\nimport { readFileSync } from 'fs';\n\n/**\n * 计算文件的 SHA-256 hash\n */\nexport async function computeFileHash(filePath: string): Promise<string> {\n const buffer = readFileSync(filePath);\n const hash = createHash('sha256');\n hash.update(buffer);\n return hash.digest('hex');\n}\n","export default {\n // Common\n 'common.yes': '是',\n 'common.no': '否',\n 'common.confirm': '确认',\n 'common.cancel': '取消',\n 'common.quit': '按 q 退出',\n\n // CLI\n 'cli.import.desc': '导入本地文件或目录到书架',\n 'cli.import.help': '文件或目录路径(支持 .txt / .epub)',\n 'cli.import.success': '✓ 已导入:',\n 'cli.import.fail': '导入失败:',\n 'cli.import.not_found': '路径不存在:',\n 'cli.import.unsupported': '不支持的文件格式。目前支持: .txt, .epub',\n 'cli.import.scan_dir': '扫描目录',\n 'cli.import.found_files': '找到以下书籍:',\n 'cli.import.confirm_batch': '是否确认导入上述 {0} 本书?(y/N)',\n 'cli.import.canceled': '✓ 导入已取消',\n\n 'cli.resume.desc': '恢复上次阅读',\n 'cli.resume.none': '✗ 没有找到最近阅读记录,请先使用 novel import <file> 或 novel open <book-id> 打开一本书。',\n\n 'cli.open.desc': '打开指定书籍',\n 'cli.open.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.open.not_found': '✗ 未找到匹配书籍:',\n\n 'cli.library.desc': '查看书架列表',\n 'cli.library.help': '可选关键字搜索',\n 'cli.library.none': '✗ 书架为空。使用 novel import <file> 导入你的第一本书。',\n 'cli.library.search_none': '📚 未找到匹配「{0}」的书籍。',\n 'cli.library.search_result': '📚 搜索结果 ({0} 本):\\n',\n\n 'cli.remove.desc': '从书架移除书籍(仅删除记录,不删源文件)',\n 'cli.remove.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.remove.not_found': '✗ 未找到匹配书籍:',\n 'cli.remove.success': '✓ 已移除书籍:',\n 'cli.remove.fail': '移除书籍失败:',\n\n 'cli.lang.desc': '切换语言',\n 'cli.lang.help': '语言代码: zh | en',\n 'cli.lang.success': '✓ 语言已切换为: {0}',\n 'cli.lang.unsupported': '✗ 不支持的语言: {0}',\n\n 'cli.update.desc': '检查最新版本并自动更新',\n 'cli.update.checking': '正在检查更新...',\n 'cli.update.latest': '✓ 当前已是最新版本 (v{0})',\n 'cli.update.updating': '发现新版本 v{0} (当前 v{1}),正在更新...',\n 'cli.update.success': '✓ 升级成功!请重新运行 novel 命令。',\n 'cli.update.fail': '✗ 升级失败: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 加载书架...',\n 'tui.lib.empty.title': '📚 书架',\n 'tui.lib.empty.desc': '书架为空。使用 novel import <file> 导入你的第一本书。',\n 'tui.lib.title': '📚 书架 ({0} 本)',\n 'tui.lib.tips': ' ↑↓/jk 选择 · Enter 打开 · d/x 删除 · q 退出',\n\n // TUI - Reader\n 'tui.reader.loading': '读取中...',\n 'tui.reader.bookmark_add': '✓ 增加书签: {0}',\n 'tui.reader.status.remaining': '预计剩余 {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[全部章节]',\n 'tui.nav.tab.bookmarks': '[我的书签]',\n 'tui.nav.tips': 'Enter 跳转 · Tab 切换 · Esc/q 关闭',\n 'tui.nav.empty': '没有记录',\n 'tui.nav.page': '第 {0} / {1} 页',\n};\n","import type { LocaleDictionary } from './types.js';\n\nconst en: LocaleDictionary = {\n // Common\n 'common.yes': 'Yes',\n 'common.no': 'No',\n 'common.confirm': 'Confirm',\n 'common.cancel': 'Cancel',\n 'common.quit': 'Press q to quit',\n\n // CLI\n 'cli.import.desc': 'Import local file or directory to library',\n 'cli.import.help': 'File or directory path (supports .txt / .epub)',\n 'cli.import.success': '✓ Imported:',\n 'cli.import.fail': 'Import failed:',\n 'cli.import.not_found': 'Path not found:',\n 'cli.import.unsupported': 'Unsupported file format. Currently supports: .txt, .epub',\n 'cli.import.scan_dir': 'Scanning directory',\n 'cli.import.found_files': 'Found following books:',\n 'cli.import.confirm_batch': 'Confirm importing these {0} books? (y/N)',\n 'cli.import.canceled': '✓ Import canceled',\n\n 'cli.resume.desc': 'Resume last reading',\n 'cli.resume.none': '✗ No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.',\n\n 'cli.open.desc': 'Open specific book',\n 'cli.open.help': 'Book ID or title (fuzzy match)',\n 'cli.open.not_found': '✗ Book not found:',\n\n 'cli.library.desc': 'View library',\n 'cli.library.help': 'Optional keyword search',\n 'cli.library.none': '✗ Library is empty. Use `novel import <file>` to import your first book.',\n 'cli.library.search_none': '📚 No books found matching \"{0}\".',\n 'cli.library.search_result': '📚 Search results ({0} books):\\n',\n\n 'cli.remove.desc': 'Remove book from library (only deletes records, not source file)',\n 'cli.remove.help': 'Book ID or title (fuzzy match)',\n 'cli.remove.not_found': '✗ Book not found:',\n 'cli.remove.success': '✓ Book removed:',\n 'cli.remove.fail': 'Failed to remove book:',\n\n 'cli.lang.desc': 'Switch language',\n 'cli.lang.help': 'Language code: zh | en',\n 'cli.lang.success': '✓ Language switched to: {0}',\n 'cli.lang.unsupported': '✗ Unsupported language: {0}',\n\n 'cli.update.desc': 'Check for the latest version and update automatically',\n 'cli.update.checking': 'Checking for updates...',\n 'cli.update.latest': '✓ Already up to date (v{0})',\n 'cli.update.updating': 'New version v{0} found (current v{1}), updating...',\n 'cli.update.success': '✓ Successfully updated! Please restart novel command.',\n 'cli.update.fail': '✗ Update failed: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 Loading library...',\n 'tui.lib.empty.title': '📚 Library',\n 'tui.lib.empty.desc': 'Library is empty. Use `novel import <file>` to import your first book.',\n 'tui.lib.title': '📚 Library ({0} books)',\n 'tui.lib.tips': ' ↑↓/jk select · Enter open · d/x delete · q quit',\n\n // TUI - Reader\n 'tui.reader.loading': 'Loading...',\n 'tui.reader.bookmark_add': '✓ Bookmark added: {0}',\n 'tui.reader.status.remaining': 'Est. remaining {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[All Chapters]',\n 'tui.nav.tab.bookmarks': '[My Bookmarks]',\n 'tui.nav.tips': 'Enter jump · Tab switch · Esc/q close',\n 'tui.nav.empty': 'No records',\n 'tui.nav.page': 'Page {0} / {1}',\n};\n\nexport default en;\n","/**\n * 应用配置管理\n * 读写 ~/.config/readshell/config.json\n */\n\nimport Conf from 'conf';\n\ninterface ReadShellConfig {\n /** 每页显示行数(0 = 自适应终端高度) */\n linesPerPage: number;\n /** 是否显示状态栏 */\n showStatusBar: boolean;\n /** 阅读模式: 'page' | 'scroll' */\n readingMode: 'page' | 'scroll';\n /** 界面语言 */\n language: 'zh' | 'en';\n}\n\nconst defaults: ReadShellConfig = {\n linesPerPage: 0,\n showStatusBar: true,\n readingMode: 'page',\n language: 'zh',\n};\n\nconst config = new Conf<ReadShellConfig>({\n projectName: 'readshell',\n defaults,\n});\n\nexport function getConfig(): ReadShellConfig {\n return {\n linesPerPage: config.get('linesPerPage'),\n showStatusBar: config.get('showStatusBar'),\n readingMode: config.get('readingMode'),\n language: config.get('language'),\n };\n}\n\nexport function setConfig<K extends keyof ReadShellConfig>(key: K, value: ReadShellConfig[K]): void {\n config.set(key, value);\n}\n\nexport { config };\n","import zh from './zh.js';\nimport en from './en.js';\nimport type { LocaleDictionary, LocaleKeys } from './types.js';\nimport { getConfig } from '../config/AppConfig.js';\n\nconst dictionaries: Record<'zh' | 'en', LocaleDictionary> = {\n zh,\n en,\n};\n\nlet currentLang: 'zh' | 'en' = 'zh';\nlet currentDict: LocaleDictionary = dictionaries.zh;\n\n/**\n * 初始化并加载当前配置的语言\n */\nexport function initI18n() {\n const config = getConfig();\n currentLang = config.language || 'zh';\n currentDict = dictionaries[currentLang] || dictionaries.zh;\n}\n\n/**\n * 切换语言字典(运行时热切换支持)\n */\nexport function setLanguage(lang: 'zh' | 'en') {\n currentLang = lang;\n currentDict = dictionaries[lang] || dictionaries.zh;\n}\n\n/**\n * 获取翻译词条\n * 支持占位符 {0}, {1} 替换\n */\nexport function t(key: LocaleKeys, ...args: (string | number)[]): string {\n let template = currentDict[key];\n if (!template) {\n return key;\n }\n \n if (args.length > 0) {\n args.forEach((arg, index) => {\n template = template.replace(`{${index}}`, String(arg));\n });\n }\n return template;\n}\n","/**\n * novel import <file> — 导入本地文件\n * 支持 txt,基础去重,导入后自动入书架\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { statSync, readdirSync } from 'node:fs';\nimport { resolve, join, extname } from 'node:path';\nimport * as readline from 'node:readline/promises';\n\nexport interface ImportArgs {\n file: string;\n}\n\n// 递归扫描目录文件\nfunction scanDirectory(dir: string): string[] {\n let results: string[] = [];\n try {\n const list = readdirSync(dir);\n for (const file of list) {\n const fullPath = join(dir, file);\n const stat = statSync(fullPath);\n if (stat.isDirectory()) {\n results = results.concat(scanDirectory(fullPath));\n } else {\n const ext = extname(fullPath).toLowerCase();\n if (ext === '.txt' || ext === '.epub') {\n results.push(fullPath);\n }\n }\n }\n } catch (err) {\n logger.error('Scan error:', err);\n }\n return results;\n}\n\nexport const importCommand: CommandModule<object, ImportArgs> = {\n command: 'import <file>',\n describe: t('cli.import.desc'),\n builder: (yargs) => {\n return yargs.positional('file', {\n describe: t('cli.import.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const targetPath = resolve(argv.file);\n let stat;\n try {\n stat = statSync(targetPath);\n } catch (e) {\n console.log(`${t('cli.import.not_found')} ${targetPath}`);\n process.exit(1);\n }\n\n const bookService = new BookService();\n\n if (stat.isDirectory()) {\n console.log(`${t('cli.import.scan_dir')} ${targetPath}...`);\n const files = scanDirectory(targetPath);\n\n if (files.length === 0) {\n console.log(`✗ ` + t('cli.import.unsupported'));\n return;\n }\n\n console.log(t('cli.import.found_files'));\n files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const answer = await rl.question(t('cli.import.confirm_batch', files.length) + ' ');\n rl.close();\n\n if (answer.toLowerCase() === 'y') {\n for (const file of files) {\n try {\n const book = await bookService.importBook(file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n } catch (err) {\n console.log(`${t('cli.import.fail')} ${file} - ${err}`);\n }\n }\n } else {\n console.log(t('cli.import.canceled'));\n }\n } else {\n // 单文件导入\n const book = await bookService.importBook(argv.file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n }\n } catch (error) {\n console.log(`${t('cli.import.fail')} ${error}`);\n process.exit(1);\n }\n },\n};\n","/**\n * 阅读进度业务逻辑\n * 进度读写、resume 核心逻辑\n */\n\nimport { ProgressModel, type ProgressRecord } from '../db/models/Progress.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { logger } from '../utils/logger.js';\n\nexport class ProgressService {\n private progressModel = new ProgressModel();\n private recentModel = new RecentModel();\n\n /**\n * 保存阅读进度(退出时调用)\n */\n saveProgress(bookId: string, chapterNo: number, byteOffset: number, percent: number): void {\n const now = Date.now();\n\n this.progressModel.upsert({\n book_id: bookId,\n chapter_no: chapterNo,\n byte_offset: byteOffset,\n percent,\n updated_at: now,\n opened_at: now,\n });\n\n // 同时更新最近阅读记录\n this.recentModel.recordOpen(bookId);\n\n logger.debug(`进度已保存: book=${bookId}, chapter=${chapterNo}, offset=${byteOffset}, ${(percent * 100).toFixed(1)}%`);\n }\n\n /**\n * 获取指定书籍的阅读进度(resume 时调用)\n */\n getProgress(bookId: string): ProgressRecord | undefined {\n return this.progressModel.findByBookId(bookId);\n }\n\n /**\n * 获取最近打开的书籍进度(启动时调用,决定 resume 哪本书)\n */\n getLastOpenedBook(): ProgressRecord | undefined {\n return this.progressModel.getLastOpened();\n }\n}\n","/**\n * Ink TUI 渲染启动函数\n * 封装 ink.render(<App />) 的统一入口\n */\n\nimport React from 'react';\nimport { render } from 'ink';\nimport { App, type PageRoute } from './App.js';\n\ninterface RenderOptions {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\n/**\n * 启动 Ink TUI 应用\n */\nexport function renderApp(options: RenderOptions = {}): void {\n const { initialPage = 'resume', bookId, initialByteOffset } = options;\n\n const { waitUntilExit } = render(\n React.createElement(App, {\n initialPage,\n bookId,\n initialByteOffset,\n }),\n );\n\n waitUntilExit().catch(() => {\n // Ink 退出时的清理\n process.exit(0);\n });\n}\n","/**\n * App 根组件\n * 管理页面路由状态\n */\n\nimport React, { useState } from 'react';\nimport { Box, Text } from 'ink';\nimport { ResumePage } from './pages/ResumePage.js';\nimport { LibraryPage } from './pages/LibraryPage.js';\nimport { ReaderPage } from './pages/ReaderPage.js';\n\nexport type PageRoute = 'resume' | 'library' | 'reader';\n\ninterface AppProps {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\nexport function App({ initialPage = 'resume', bookId, initialByteOffset }: AppProps) {\n const [currentPage, setCurrentPage] = useState<PageRoute>(initialPage);\n const [currentBookId, setCurrentBookId] = useState<string | undefined>(bookId);\n const [currentByteOffset, setCurrentByteOffset] = useState<number | undefined>(initialByteOffset);\n\n const navigateTo = (page: PageRoute, targetBookId?: string, byteOffset?: number) => {\n setCurrentPage(page);\n if (targetBookId) setCurrentBookId(targetBookId);\n if (byteOffset !== undefined) setCurrentByteOffset(byteOffset);\n };\n\n return (\n <Box flexDirection=\"column\" width=\"100%\">\n {currentPage === 'resume' && (\n <ResumePage onNavigate={navigateTo} />\n )}\n {currentPage === 'library' && (\n <LibraryPage onNavigate={navigateTo} />\n )}\n {currentPage === 'reader' && currentBookId && (\n <ReaderPage\n bookId={currentBookId}\n initialByteOffset={currentByteOffset}\n onNavigate={navigateTo}\n />\n )}\n {currentPage === 'reader' && !currentBookId && (\n <Box>\n <Text color=\"red\">错误: 未指定书籍</Text>\n </Box>\n )}\n </Box>\n );\n}\n","/**\n * 继续阅读页\n * 启动默认页,自动查询最近阅读并跳转到阅读器\n */\n\nimport React, { useEffect, useState } from 'react';\nimport { Box, Text, useApp } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\n\ninterface ResumePageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function ResumePage({ onNavigate }: ResumePageProps) {\n const { exit } = useApp();\n const [checking, setChecking] = useState(true);\n\n useEffect(() => {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n setChecking(false);\n return;\n }\n\n // 验证书籍存在\n const bookModel = new BookModel();\n const book = bookModel.findById(lastProgress.book_id);\n\n if (!book) {\n setChecking(false);\n return;\n }\n\n // 自动跳转到阅读器\n onNavigate('reader', book.id, lastProgress.byte_offset);\n }, [onNavigate]);\n\n if (checking) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 检查阅读记录...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">\n 📖 ReadShell — 终端内轻阅读\n </Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>暂无阅读记录。</Text>\n <Text dimColor>使用 novel import <file> 导入一本书开始阅读。</Text>\n <Box marginTop={1}>\n <Text dimColor>按 q 退出</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n","/**\n * 书架页\n * 交互式书架列表,上下键选择,Enter 打开阅读\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport type { BookRecord } from '../../db/models/Book.js';\nimport { t } from '../../locales/index.js';\n\ninterface LibraryPageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function LibraryPage({ onNavigate }: LibraryPageProps) {\n const { exit } = useApp();\n const [books, setBooks] = useState<BookRecord[]>([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [loading, setLoading] = useState(true);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useEffect(() => {\n const recentService = new RecentService();\n const recentBooks = recentService.getRecentBooks();\n\n if (recentBooks.length > 0) {\n setBooks(recentBooks);\n } else {\n const bookService = new BookService();\n setBooks(bookService.getAllBooks());\n }\n setLoading(false);\n }, []);\n\n useInput((input, key) => {\n if (input === 'q') {\n exit();\n return;\n }\n\n if (books.length === 0) return;\n\n // 上下导航\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n }\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(prev + 1, books.length - 1));\n }\n\n // 删除选中的书 (backspace 键或 d 键)\n if (key.backspace || key.delete || input === 'd' || input === 'x') {\n const selected = books[selectedIndex];\n if (selected) {\n const bookService = new BookService();\n bookService.deleteBook(selected.id);\n \n // 更新列表\n setBooks((prev) => {\n const next = prev.filter((b) => b.id !== selected.id);\n // 调整选中游标,防止越界\n if (selectedIndex >= next.length) {\n setSelectedIndex(Math.max(0, next.length - 1));\n }\n return next;\n });\n }\n return;\n }\n\n // Enter 打开选中的书\n if (key.return) {\n const selected = books[selectedIndex];\n if (selected) {\n const progressService = new ProgressService();\n const progress = progressService.getProgress(selected.id);\n onNavigate('reader', selected.id, progress?.byte_offset ?? 0);\n }\n }\n }, { isActive: isRawModeSupported });\n\n if (loading) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">{t('tui.lib.loading')}</Text>\n </Box>\n );\n }\n\n if (books.length === 0) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.empty.title')}</Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>{t('tui.lib.empty.desc')}</Text>\n <Box marginTop={1}>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.title', books.length)}</Text>\n <Text dimColor>{t('tui.lib.tips')}</Text>\n <Box flexDirection=\"column\" marginTop={1}>\n {books.map((book, index) => {\n const isSelected = index === selectedIndex;\n \n return (\n <Box key={book.id} paddingX={1} justifyContent=\"space-between\">\n <Box>\n <Text\n color={isSelected ? 'cyan' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▸ ' : ' '}\n {book.title}\n </Text>\n <Text dimColor> ({book.format})</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n </Box>\n );\n}\n","/**\n * 最近阅读业务逻辑\n */\n\nimport { RecentModel } from '../db/models/Recent.js';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\n\nexport class RecentService {\n private recentModel = new RecentModel();\n private bookModel = new BookModel();\n\n /**\n * 获取最近阅读的书籍列表(包含书籍详情)\n */\n getRecentBooks(limit: number = 20): BookRecord[] {\n const recentRecords = this.recentModel.getRecent(limit);\n\n return recentRecords\n .map((record) => this.bookModel.findById(record.book_id))\n .filter((book): book is BookRecord => book !== undefined);\n }\n\n /**\n * 记录打开事件\n */\n recordOpen(bookId: string): void {\n this.recentModel.recordOpen(bookId);\n }\n}\n","/**\n * 阅读器页 — 核心阅读体验\n *\n * 功能:\n * - 加载书籍文件内容,按终端尺寸分页\n * - 键盘翻页(空格/j/↓ 下一页, k/↑ 上一页)\n * - 状态栏显示书名 + 进度百分比\n * - 退出时(q)自动保存进度\n * - resume 进入时根据 byte_offset 定位到对应页\n */\n\nimport React, { useState, useEffect, useRef, useMemo } from 'react';\nimport { Box, Text, useApp, useStdout } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { TextRenderer } from '../components/TextRenderer.js';\nimport { StatusBar } from '../components/StatusBar.js';\nimport { ChapterNav } from '../components/ChapterNav.js';\nimport { useReader } from '../hooks/useReader.js';\nimport { useKeyboard } from '../hooks/useKeyboard.js';\nimport { paginate, type Page } from '../../utils/paginate.js';\nimport { readFileWithEncoding } from '../../utils/encoding.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { ChapterService } from '../../services/ChapterService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport { BookmarkService } from '../../services/BookmarkService.js';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { triggerBossKey } from '../../utils/bossKey.js';\nimport { estimateReadingTime, formatReadingTime } from '../../utils/time.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\ninterface ReaderPageProps {\n bookId: string;\n initialByteOffset?: number;\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\n/**\n * 阅读器内部组件 — 在 pages 准备好后渲染\n */\nfunction ReaderContent({\n book,\n bookId,\n pages,\n initialByteOffset,\n termHeight,\n contentHeight,\n}: {\n book: BookRecord;\n bookId: string;\n pages: Page[];\n initialByteOffset?: number;\n termHeight: number;\n contentHeight: number;\n}) {\n const { exit } = useApp();\n const [chapterTitle, setChapterTitle] = useState<string | undefined>();\n const [currentChapter, setCurrentChapter] = useState<ChapterRecord | undefined>();\n const [showChapterNav, setShowChapterNav] = useState(false);\n const [allChapters, setAllChapters] = useState<ChapterRecord[]>([]);\n const [allBookmarks, setAllBookmarks] = useState<BookmarkRecord[]>([]);\n const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n const progressServiceRef = useRef(new ProgressService());\n const chapterServiceRef = useRef(new ChapterService());\n const bookmarkServiceRef = useRef(new BookmarkService());\n\n const reader = useReader(pages, initialByteOffset);\n\n // 加载并更新当前章节信息\n useEffect(() => {\n const currentOffset = reader.getCurrentOffset();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, currentOffset);\n setCurrentChapter(chapter ?? undefined);\n setChapterTitle(chapter?.title ?? undefined);\n }, [reader.currentPage, bookId]);\n\n // 获取该书全部章节及书签用于渲染导航\n useEffect(() => {\n const chaptersList = chapterServiceRef.current.getChaptersByBookId(bookId);\n setAllChapters(chaptersList);\n \n if (showChapterNav) {\n setAllBookmarks(bookmarkServiceRef.current.getBookmarksByBookId(bookId));\n }\n }, [bookId, showChapterNav]);\n\n /**\n * 取当前页首行生成书签名\n */\n const handleAddBookmark = () => {\n const currentPageInfo = reader.getCurrentPage();\n if (!currentPageInfo) return;\n\n let markTitle = '无标题书签';\n for (const line of currentPageInfo.lines) {\n const stripped = line.trim();\n if (stripped.length > 0) {\n markTitle = stripped.slice(0, 15) + (stripped.length > 15 ? '...' : '');\n break;\n }\n }\n\n const currentOffset = reader.getCurrentOffset();\n bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);\n \n setToastMessage(t('tui.reader.bookmark_add', markTitle));\n setTimeout(() => setToastMessage(null), 2000);\n };\n\n /**\n * 自动在组件卸载时保存进度\n */\n useEffect(() => {\n return () => {\n const offset = reader.getCurrentOffset();\n const percent = reader.getPercent();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);\n const chapterNo = chapter?.chapter_no ?? 0;\n\n progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);\n logger.debug(`进度已保存: offset=${offset}, ${(percent * 100).toFixed(1)}%`);\n };\n }, [bookId, reader]);\n\n // 拦截全局键盘事件\n useKeyboard(\n {\n onNext: () => reader.nextPage(),\n onPrev: () => reader.prevPage(),\n onQuit: () => exit(),\n onChapterList: () => setShowChapterNav(true),\n onBossKey: () => triggerBossKey(),\n onBookmarkAdd: handleAddBookmark,\n },\n !showChapterNav, // 如果浮层显示,则停止普通的阅读快捷键\n );\n\n const currentPage = reader.getCurrentPage();\n const currentLines = currentPage?.lines ?? [];\n // The contentHeight prop is already passed, but the instruction redefines it.\n // Assuming the user intends to use this new calculation for contentHeight within ReaderContent.\n const calculatedContentHeight = Math.max(1, termHeight - 2);\n\n // 计算剩余阅读时间:总字符近似于字节数的 1/3 (utf-8 场景下),中文字符占绝大部分\n const totalChars = (book.file_size ?? 0) / 3;\n const remainingChars = Math.max(0, totalChars * (1 - reader.getPercent()));\n const remainingMinutes = estimateReadingTime(remainingChars, true);\n const remainingTimeStr = formatReadingTime(remainingMinutes);\n\n return (\n <Box flexDirection=\"column\" height={termHeight}>\n {/* 相对定位于容器中,使用 flex 布局进行展现。章节模式下隐藏正文 */}\n {!showChapterNav ? (\n <>\n <Box flexDirection=\"column\" flexGrow={1} paddingX={1}>\n <TextRenderer lines={currentLines} height={calculatedContentHeight} />\n </Box>\n <StatusBar\n bookTitle={book.title}\n percent={reader.getPercent()}\n chapterTitle={chapterTitle}\n currentPage={reader.currentPage + 1}\n totalPages={reader.totalPages}\n remainingTime={remainingTimeStr}\n />\n {toastMessage && (\n <Box alignSelf=\"flex-end\" marginTop={-2} marginRight={1} borderStyle=\"round\" borderColor=\"green\" paddingX={1}>\n <Text color=\"green\">{toastMessage}</Text>\n </Box>\n )}\n </>\n ) : (\n <ChapterNav\n chapters={allChapters}\n bookmarks={allBookmarks}\n currentChapterId={currentChapter?.id}\n termHeight={termHeight}\n onSelect={(offset) => {\n reader.goToOffset(offset);\n setShowChapterNav(false);\n }}\n onClose={() => setShowChapterNav(false)}\n />\n )}\n </Box>\n );\n}\n\nexport function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }: ReaderPageProps) {\n const { exit } = useApp();\n const { stdout } = useStdout();\n\n const [book, setBook] = useState<BookRecord | null>(null);\n const [pages, setPages] = useState<Page[] | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const termWidth = stdout?.columns ?? 80;\n const termHeight = stdout?.rows ?? 24;\n const contentHeight = Math.max(termHeight - 3, 5);\n\n // 加载书籍内容\n useEffect(() => {\n try {\n const bookModel = new BookModel();\n const bookRecord = bookModel.findById(bookId);\n\n if (!bookRecord) {\n setError(`书籍不存在: ${bookId}`);\n return;\n }\n\n setBook(bookRecord);\n\n // 记录打开事件\n const recentService = new RecentService();\n recentService.recordOpen(bookId);\n\n // 读取文件内容\n const content = readFileWithEncoding(bookRecord.file_path);\n\n // 分页\n const paginatedPages = paginate(content, termWidth - 2, contentHeight);\n setPages(paginatedPages);\n\n logger.debug(`加载完成: ${bookRecord.title}, ${paginatedPages.length} 页`);\n } catch (err) {\n setError(`加载失败: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, [bookId, termWidth, contentHeight]);\n\n // 非 TTY 下支持 q 退出\n useKeyboard({\n onQuit: () => exit(),\n });\n\n // 错误\n if (error) {\n return (\n <Box padding={1} flexDirection=\"column\">\n <Text color=\"red\">✗ {error}</Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n );\n }\n\n // 加载中\n if (!book || !pages) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 {t('tui.reader.loading')}</Text>\n </Box>\n );\n }\n\n // pages 准备好后渲染阅读器内容\n return (\n <ReaderContent\n book={book}\n bookId={bookId}\n pages={pages}\n initialByteOffset={initialByteOffset}\n termHeight={termHeight}\n contentHeight={contentHeight}\n />\n );\n}\n","/**\n * 正文渲染组件\n * 显示分页后的文本行,并支持智能高亮\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\n\ninterface TextRendererProps {\n lines: string[];\n height?: number;\n}\n\n/**\n * 处理单行内的智能高亮匹配\n * 例如将 「XXX」或 “XXX” 进行暗化或着色,提升大段文字沉浸感\n */\nfunction renderLineWithHighlight(line: string) {\n if (!line) return <Text> </Text>; // 保留空行\n\n // 匹配对话大纲的正则 (包括中英文常见方括号/双引号)\n // 此处拆分为:对话段落与非对话段落\n // (「.*?」|“.*?”|『.*?』|《.*?》)\n const regex = /(「.*?」|“.*?”|『.*?』|《.*?》)/g;\n const parts = line.split(regex);\n\n return (\n <Text>\n {parts.map((part, index) => {\n // 如果是正则匹配出的组(即被高亮的部分)\n if (regex.test(part)) {\n // 由于 Regex 的全局状态,test 之后要注意\n // 但其实 split 后,奇数项是捕获组的内容\n }\n \n // 简单判定:如果是奇数项,就是捕获组\n const isHighlight = index % 2 === 1;\n\n if (isHighlight) {\n // 对话颜色应用 dimColor,让环境和描述语句突出,或者是相反。\n // 这里将对话变暗 (dimColor),减轻视觉疲劳\n return <Text key={index} dimColor>{part}</Text>;\n }\n\n // 常规文本\n return <Text key={index}>{part}</Text>;\n })}\n </Text>\n );\n}\n\nexport function TextRenderer({ lines, height }: TextRendererProps) {\n // 补齐空行以占满屏幕高度(避免内容跳动)\n const displayLines = [...lines];\n if (height && displayLines.length < height) {\n const padding = height - displayLines.length;\n for (let i = 0; i < padding; i++) {\n displayLines.push('');\n }\n }\n\n return (\n <Box flexDirection=\"column\">\n {displayLines.map((line, index) => (\n <Box key={index}>\n {renderLineWithHighlight(line)}\n </Box>\n ))}\n </Box>\n );\n}\n","/**\n * 底部状态栏组件\n * 显示书名、章节名、页码和进度百分比\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\nimport { t } from '../../locales/index.js';\n\ninterface StatusBarProps {\n bookTitle: string;\n chapterTitle?: string;\n percent: number;\n currentPage: number;\n totalPages: number;\n remainingTime?: string;\n}\n\nexport function StatusBar({\n bookTitle,\n chapterTitle,\n percent,\n currentPage,\n totalPages,\n remainingTime,\n}: StatusBarProps) {\n const displayPercent = (percent * 100).toFixed(1);\n const titleDisplay = chapterTitle ? `${bookTitle} · ${chapterTitle}` : bookTitle;\n\n return (\n <Box flexDirection=\"row\" justifyContent=\"space-between\" borderStyle=\"single\" borderTop={false} borderLeft={false} borderRight={false} paddingX={1}>\n <Box>\n <Text color=\"gray\">📖 {titleDisplay}</Text>\n </Box>\n\n <Box>\n {remainingTime && (\n <Text dimColor>{t('tui.reader.status.remaining', remainingTime)} </Text>\n )}\n <Text color=\"gray\">\n {currentPage}/{totalPages} \n </Text>\n <Text color=\"gray\">\n {displayPercent}% \n </Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 章节导航浮层组件\n * 允许在阅读器内唤起章节列表,并支持翻页与选择跳转\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { t } from '../../locales/index.js';\n\ninterface ChapterNavProps {\n chapters: ChapterRecord[];\n bookmarks: BookmarkRecord[];\n currentChapterId?: number;\n termHeight: number;\n onSelect: (byteOffset: number) => void;\n onClose: () => void;\n}\n\nexport function ChapterNav({\n chapters,\n bookmarks,\n currentChapterId,\n termHeight,\n onSelect,\n onClose,\n}: ChapterNavProps) {\n const [activeTab, setActiveTab] = useState<'chapters' | 'bookmarks'>('chapters');\n const isBookmarks = activeTab === 'bookmarks';\n const currentList = isBookmarks ? bookmarks : chapters;\n\n // 找到当前章节的初始索引\n const initialIndex = currentChapterId && !isBookmarks\n ? Math.max(\n 0,\n chapters.findIndex((c) => c.id === currentChapterId),\n )\n : 0;\n\n const [selectedIndex, setSelectedIndex] = useState(initialIndex);\n\n // 切换 tab 时重置索引\n useEffect(() => {\n setSelectedIndex(isBookmarks ? 0 : initialIndex);\n }, [activeTab, initialIndex, isBookmarks]);\n\n // 一页展示多少个项(留出上下边距)\n const pageSize = Math.max(5, termHeight - 6);\n\n // 计算当前可视窗口的起始和结束索引\n const windowStart = Math.max(0, Math.floor(selectedIndex / pageSize) * pageSize);\n const visibleItems = currentList.slice(windowStart, windowStart + pageSize);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useInput(\n (input, key) => {\n // 退出\n if (key.escape || input === 'q') {\n onClose();\n return;\n }\n \n // 切换视图\n if (key.tab) {\n setActiveTab(prev => prev === 'chapters' ? 'bookmarks' : 'chapters');\n return;\n }\n\n // 确认\n if (key.return) {\n if (currentList[selectedIndex]) {\n onSelect(currentList[selectedIndex].byte_offset);\n }\n return;\n }\n\n // 上移\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(0, prev - 1));\n }\n\n // 下移\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(currentList.length - 1, prev + 1));\n }\n },\n { isActive: isRawModeSupported },\n );\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor=\"green\"\n paddingX={2}\n paddingY={1}\n width=\"80%\"\n alignSelf=\"center\"\n marginTop={2}\n >\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Box>\n <Text bold color={activeTab === 'chapters' ? 'green' : 'gray'}>{t('tui.nav.tab.chapters')} </Text>\n <Text bold color={activeTab === 'bookmarks' ? 'green' : 'gray'}>{t('tui.nav.tab.bookmarks')}</Text>\n </Box>\n <Text dimColor>{t('tui.nav.tips')}</Text>\n </Box>\n\n {visibleItems.length === 0 ? (\n <Text dimColor>{t('tui.nav.empty')}</Text>\n ) : (\n visibleItems.map((item, idx) => {\n const actualIndex = windowStart + idx;\n const isSelected = actualIndex === selectedIndex;\n \n return (\n <Text\n key={item.id}\n color={isSelected ? 'green' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▶ ' : ' '}\n {item.title}\n </Text>\n );\n })\n )}\n\n <Box marginTop={1} justifyContent=\"flex-end\">\n <Text dimColor>\n {t('tui.nav.page', Math.floor(selectedIndex / pageSize) + 1, Math.ceil(currentList.length / pageSize) || 1)}\n </Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器核心状态 Hook\n * 管理当前页面、offset、分页等状态\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport type { Page } from '../../utils/paginate.js';\n\ninterface ReaderState {\n currentPage: number;\n totalPages: number;\n}\n\nexport function useReader(pages: Page[], initialByteOffset?: number) {\n // 根据 initialByteOffset 找到初始页\n const initialPage = useMemo(() => {\n if (!initialByteOffset || pages.length === 0) return 0;\n\n // 找到 byte_offset <= initialByteOffset 的最后一页\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= initialByteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n return targetPage;\n }, [pages, initialByteOffset]);\n\n const [state, setState] = useState<ReaderState>({\n currentPage: initialPage,\n totalPages: pages.length,\n });\n\n const nextPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.min(prev.currentPage + 1, prev.totalPages - 1),\n }));\n }, []);\n\n const prevPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(prev.currentPage - 1, 0),\n }));\n }, []);\n\n const goToPage = useCallback((pageNum: number) => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(0, Math.min(pageNum, prev.totalPages - 1)),\n }));\n }, []);\n\n /**\n * 根据 byte_offset 跳转到对应页\n */\n const goToOffset = useCallback((byteOffset: number) => {\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= byteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n setState((prev) => ({\n ...prev,\n currentPage: targetPage,\n }));\n }, [pages]);\n\n const getCurrentPage = useCallback((): Page | undefined => {\n return pages[state.currentPage];\n }, [state.currentPage, pages]);\n\n /**\n * 获取当前页的 byte_offset(用于保存进度)\n */\n const getCurrentOffset = useCallback((): number => {\n return pages[state.currentPage]?.byteOffset ?? 0;\n }, [state.currentPage, pages]);\n\n const getPercent = useCallback((): number => {\n if (state.totalPages === 0) return 0;\n return (state.currentPage + 1) / state.totalPages;\n }, [state.currentPage, state.totalPages]);\n\n const isFirstPage = state.currentPage === 0;\n const isLastPage = state.currentPage === state.totalPages - 1;\n\n return {\n ...state,\n nextPage,\n prevPage,\n goToPage,\n goToOffset,\n getCurrentPage,\n getCurrentOffset,\n getPercent,\n isFirstPage,\n isLastPage,\n };\n}\n","/**\n * 键盘事件统一管理 Hook\n * 在非 TTY 环境下安全降级\n */\n\nimport { useInput } from 'ink';\n\ninterface KeyboardHandlers {\n onNext?: () => void;\n onPrev?: () => void;\n onQuit?: () => void;\n onChapterList?: () => void;\n onHelp?: () => void;\n onBossKey?: () => void;\n onBookmarkAdd?: () => void;\n}\n\nexport function useKeyboard(handlers: KeyboardHandlers, isActive: boolean = true) {\n const isRawModeSupported = process.stdin.isTTY ?? false;\n const shouldListen = isRawModeSupported && isActive;\n\n useInput((input, key) => {\n // 翻页:空格 / j / 下箭头 / f → 下一页\n if (input === ' ' || input === 'j' || key.downArrow || input === 'f') {\n handlers.onNext?.();\n }\n\n // 上一页:k / 上箭头 / b\n if (input === 'k' || key.upArrow || input === 'b') {\n handlers.onPrev?.();\n }\n\n // 退出:q\n if (input === 'q') {\n handlers.onQuit?.();\n }\n\n // 章节列表:c\n if (input === 'c') {\n handlers.onChapterList?.();\n }\n\n // 帮助:?\n if (input === '?') {\n handlers.onHelp?.();\n }\n \n // 老板键\n if (handlers.onBossKey && (key.escape || input === 'esc' || input === 'b' || input === 'B')) {\n handlers.onBossKey?.();\n }\n \n // 按 m 或 M 加书签\n if (handlers.onBookmarkAdd && (input === 'm' || input === 'M')) {\n handlers.onBookmarkAdd?.();\n }\n }, { isActive: shouldListen });\n}\n","/**\n * 字符串宽度计算\n * 处理中文字符宽度为 2\n */\n\n/**\n * 获取字符串在终端中的显示宽度\n */\nexport function getStringWidth(str: string): number {\n let width = 0;\n for (const char of str) {\n width += isFullWidth(char) ? 2 : 1;\n }\n return width;\n}\n\n/**\n * 判断字符是否为全角字符\n * CJK 统一表意字符 + 全角标点\n */\nfunction isFullWidth(char: string): boolean {\n const code = char.codePointAt(0);\n if (code === undefined) return false;\n\n return (\n // CJK 统一表意字符\n (code >= 0x4e00 && code <= 0x9fff) ||\n // CJK 统一表意字符扩展 A\n (code >= 0x3400 && code <= 0x4dbf) ||\n // CJK 统一表意字符扩展 B\n (code >= 0x20000 && code <= 0x2a6df) ||\n // CJK 兼容表意字符\n (code >= 0xf900 && code <= 0xfaff) ||\n // 全角 ASCII、全角标点\n (code >= 0xff01 && code <= 0xff60) ||\n (code >= 0xffe0 && code <= 0xffe6) ||\n // CJK 标点符号\n (code >= 0x3000 && code <= 0x303f) ||\n // 日文平假名/片假名\n (code >= 0x3040 && code <= 0x30ff) ||\n // 韩文音节\n (code >= 0xac00 && code <= 0xd7af)\n );\n}\n","/**\n * 文本分页算法\n * 按终端宽高切割文本为页面\n */\n\nimport { getStringWidth } from './stringWidth.js';\n\nexport interface Page {\n /** 页面内容行 */\n lines: string[];\n /** 此页在原始文本中的 byte offset 起始位置 */\n byteOffset: number;\n}\n\n/**\n * 将文本按终端尺寸分页\n * @param text 原始文本\n * @param width 终端宽度(列数)\n * @param height 可用行数(扣除状态栏后)\n * @returns 分页结果\n */\nexport function paginate(text: string, width: number, height: number): Page[] {\n const pages: Page[] = [];\n const rawLines = text.split('\\n');\n\n // 将原始文本行按终端宽度折行\n const wrappedLines: { text: string; byteOffset: number }[] = [];\n let currentOffset = 0;\n\n for (const rawLine of rawLines) {\n const wrapped = wrapLine(rawLine, width);\n for (const line of wrapped) {\n wrappedLines.push({ text: line, byteOffset: currentOffset });\n }\n currentOffset += Buffer.byteLength(rawLine + '\\n', 'utf-8');\n }\n\n // 按高度切割为页\n for (let i = 0; i < wrappedLines.length; i += height) {\n const pageLines = wrappedLines.slice(i, i + height);\n pages.push({\n lines: pageLines.map((l) => l.text),\n byteOffset: pageLines[0]?.byteOffset ?? 0,\n });\n }\n\n return pages;\n}\n\n/**\n * 将一行文本按终端宽度折行\n * 处理中英文混排(中文字符宽度为 2)\n */\nexport function wrapLine(line: string, width: number): string[] {\n if (line.length === 0) return [''];\n\n const result: string[] = [];\n let currentLine = '';\n let currentWidth = 0;\n\n for (const char of line) {\n const charWidth = getStringWidth(char);\n\n if (currentWidth + charWidth > width) {\n result.push(currentLine);\n currentLine = char;\n currentWidth = charWidth;\n } else {\n currentLine += char;\n currentWidth += charWidth;\n }\n }\n\n if (currentLine.length > 0) {\n result.push(currentLine);\n }\n\n return result.length > 0 ? result : [''];\n}\n","/**\n * GBK/UTF-8 自动检测\n */\n\nimport { readFileSync } from 'fs';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\n\n/**\n * 检测文件编码并返回 UTF-8 字符串\n */\nexport function readFileWithEncoding(filePath: string): string {\n const buffer = readFileSync(filePath);\n const encoding = detect(buffer) || 'utf-8';\n\n if (encoding.toLowerCase() === 'utf-8' || encoding.toLowerCase() === 'ascii') {\n return buffer.toString('utf-8');\n }\n\n return iconv.decode(buffer, encoding);\n}\n\n/**\n * 检测文件编码\n */\nexport function detectEncoding(filePath: string): string {\n const buffer = readFileSync(filePath);\n return detect(buffer) || 'utf-8';\n}\n","/**\n * 章节索引业务逻辑\n * 章节索引构建与查询\n */\n\nimport { ChapterModel, type ChapterRecord } from '../db/models/Chapter.js';\n\nexport class ChapterService {\n private chapterModel = new ChapterModel();\n\n /**\n * 获取指定书籍的所有章节\n */\n getChapters(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 获取指定章节信息\n */\n getChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n return this.chapterModel.findChapter(bookId, chapterNo);\n }\n\n /**\n * 获取章节总数\n */\n getChapterCount(bookId: string): number {\n return this.chapterModel.getChapterCount(bookId);\n }\n\n /**\n * 获取指定书籍下的所有章节\n */\n getChaptersByBookId(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 根据 offset 查询当前所属章节(用于高亮当前所在章)\n */\n getChapterByOffset(bookId: string, byteOffset: number): ChapterRecord | undefined {\n const chapters = this.chapterModel.findByBookId(bookId);\n if (chapters.length === 0) return undefined;\n\n // 找到 offset 所在的章节(最后一个 byte_offset <= 给定 offset 的章节)\n let current: ChapterRecord | undefined;\n for (const chapter of chapters) {\n if (chapter.byte_offset <= byteOffset) {\n current = chapter;\n } else {\n break;\n }\n }\n return current;\n }\n}\n","/**\n * bookmarks 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookmarkRecord {\n id?: number;\n book_id: string;\n title: string;\n byte_offset: number;\n created_at: number;\n}\n\nexport class BookmarkModel {\n /**\n * 插入书签\n */\n insert(bookmark: Omit<BookmarkRecord, 'id'>): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO bookmarks (book_id, title, byte_offset, created_at)\n VALUES (?, ?, ?, ?)\n `).run(bookmark.book_id, bookmark.title, bookmark.byte_offset, bookmark.created_at);\n }\n\n /**\n * 获取指定书籍的所有书签\n */\n findByBookId(bookId: string): BookmarkRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? ORDER BY created_at DESC').all(bookId) as BookmarkRecord[];\n }\n\n /**\n * 获取指定书签\n */\n findById(id: number): BookmarkRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE id = ?').get(id) as BookmarkRecord | undefined;\n }\n\n /**\n * 获取书籍书签总数\n */\n getCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM bookmarks WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除书签\n */\n delete(id: number): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE id = ?').run(id);\n }\n\n /**\n * 移除整本书的书签 (配合彻底清理书籍使用)\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * 书签管理服务\n * 处理针对某一位置打标记录以及后续的跳转\n */\n\nimport { BookmarkModel, type BookmarkRecord } from '../db/models/Bookmark.js';\n\nexport class BookmarkService {\n private bookmarkModel: BookmarkModel;\n\n constructor() {\n this.bookmarkModel = new BookmarkModel();\n }\n\n /**\n * 增加一条书签\n * @param title 该书签展现给用户的文案(一句话大纲)\n */\n addBookmark(bookId: string, title: string, byteOffset: number): void {\n this.bookmarkModel.insert({\n book_id: bookId,\n title,\n byte_offset: byteOffset,\n created_at: Date.now(),\n });\n }\n\n /**\n * 罗列该书全部的书签\n */\n getBookmarksByBookId(bookId: string): BookmarkRecord[] {\n return this.bookmarkModel.findByBookId(bookId);\n }\n\n /**\n * 删掉对应书签\n */\n removeBookmark(id: number): void {\n this.bookmarkModel.delete(id);\n }\n}\n","/**\n * 防打断老板键 (Boss Key)\n * 瞬间退出并用伪装日志覆盖屏幕\n */\n\nexport function triggerBossKey(): void {\n // 1. 清空屏幕\n console.clear();\n\n // 2. 打印极度逼真的伪装日志(例如 Vite 启动成功日志)\n const fakeLog = `\n VITE v5.2.8 ready in 213 ms\n\n ➜ Local: http://localhost:5173/\n ➜ Network: use --host to expose\n ➜ press h + enter to show help\n`;\n\n console.log(fakeLog);\n\n // 3. 强制安静终止应用,不留痕迹\n process.exit(0);\n}\n","/**\n * 阅读时间估算\n */\n\n/**\n * 估算阅读时间(分钟)\n * @param charCount 字符数\n * @param isChinese 是否中文内容\n * @returns 估计阅读分钟数\n */\nexport function estimateReadingTime(charCount: number, isChinese: boolean = true): number {\n // 中文平均阅读速度约 500 字/分钟\n // 英文平均阅读速度约 250 词/分钟(约 1250 字符/分钟)\n const charsPerMinute = isChinese ? 500 : 1250;\n return Math.ceil(charCount / charsPerMinute);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatReadingTime(minutes: number): string {\n if (minutes < 60) {\n return `${minutes} 分钟`;\n }\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n return mins > 0 ? `${hours} 小时 ${mins} 分钟` : `${hours} 小时`;\n}\n","/**\n * novel resume — 恢复上次阅读\n * 启动后默认入口,精确恢复到上次 offset,零摩擦\n */\n\nimport type { CommandModule } from 'yargs';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookService } from '../../services/BookService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport const resumeCommand: CommandModule = {\n command: 'resume',\n describe: t('cli.resume.desc'),\n handler: async () => {\n try {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n // 验证书籍仍然存在\n const bookService = new BookService();\n const book = bookService.findBook(lastProgress.book_id);\n if (!book) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n logger.debug(`恢复阅读: ${book.title}, offset=${lastProgress.byte_offset}`);\n\n // 启动 Ink TUI 阅读器,恢复到上次 offset\n renderApp({\n initialPage: 'reader',\n bookId: lastProgress.book_id,\n initialByteOffset: lastProgress.byte_offset,\n });\n } catch (error) {\n logger.error('恢复阅读失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel open <id|name> — 打开指定书\n * 支持 book-id 或模糊匹配书名\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface OpenArgs {\n target: string;\n}\n\nexport const openCommand: CommandModule<object, OpenArgs> = {\n command: 'open <target>',\n describe: t('cli.open.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.open.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.open.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n // 检查是否有之前的阅读进度\n const progressService = new ProgressService();\n const progress = progressService.getProgress(book.id);\n const byteOffset = progress?.byte_offset ?? 0;\n\n logger.debug(`打开: ${book.title}, offset=${byteOffset}`);\n\n // 启动 Ink TUI 阅读器\n renderApp({\n initialPage: 'reader',\n bookId: book.id,\n initialByteOffset: byteOffset,\n });\n } catch (error) {\n logger.error('打开失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel library — 书架列表\n * 含最近阅读排序 + 搜索\n */\n\nimport type { CommandModule } from 'yargs';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface LibraryArgs {\n search?: string;\n}\n\nexport const libraryCommand: CommandModule<object, LibraryArgs> = {\n command: 'library',\n describe: t('cli.library.desc'),\n builder: (yargs) => {\n return yargs.option('search', {\n alias: 's',\n describe: t('cli.library.help'),\n type: 'string',\n });\n },\n handler: async (argv) => {\n try {\n // 如果有搜索参数,以非交互模式输出\n if (argv.search) {\n const bookService = new BookService();\n const books = bookService.searchBooks(argv.search);\n\n if (books.length === 0) {\n console.log(t('cli.library.search_none', argv.search));\n return;\n }\n\n console.log(t('cli.library.search_result', books.length));\n books.forEach((book, index) => {\n console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);\n });\n return;\n }\n\n // 无搜索参数,启动交互式书架\n renderApp({ initialPage: 'library' });\n } catch (error) {\n logger.error('获取书架失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel remove <id|name> — 移除书籍\n * 删除相关所有数据,包括记录和本地进度的 DB 项\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface RemoveArgs {\n target: string;\n}\n\nexport const removeCommand: CommandModule<object, RemoveArgs> = {\n command: 'remove <target>',\n describe: t('cli.remove.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.remove.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.remove.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n bookService.deleteBook(book.id);\n console.log(`${t('cli.remove.success')} ${book.title}`);\n } catch (error) {\n logger.error('移除书籍失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel lang <zh|en> — 切换语言\n */\n\nimport type { CommandModule } from 'yargs';\nimport { setConfig } from '../../config/AppConfig.js';\nimport { t, setLanguage } from '../../locales/index.js';\n\nexport interface LangArgs {\n target: 'zh' | 'en';\n}\n\nexport const langCommand: CommandModule<object, LangArgs> = {\n command: 'lang <target>',\n describe: t('cli.lang.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.lang.help'),\n type: 'string',\n choices: ['zh', 'en'] as const,\n demandOption: true,\n });\n },\n handler: (argv) => {\n const lang = argv.target;\n if (lang === 'zh' || lang === 'en') {\n setConfig('language', lang);\n setLanguage(lang);\n console.log(t('cli.lang.success', lang));\n } else {\n console.log(t('cli.lang.unsupported', lang));\n process.exit(1);\n }\n },\n};\n","import type { CommandModule } from 'yargs';\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { t } from '../../locales/index.js';\nimport { logger } from '../../utils/logger.js';\n\nexport const updateCommand: CommandModule = {\n command: 'update',\n describe: t('cli.update.desc'),\n handler: async () => {\n try {\n console.log(t('cli.update.checking'));\n\n // 提取本地 package.json 版本号\n let localVersion = '0.0.0';\n try {\n const pkgUrl = new URL('../../package.json', import.meta.url);\n const pkg = JSON.parse(readFileSync(pkgUrl, 'utf-8'));\n localVersion = pkg.version;\n } catch (err) {\n // Fallback for DEV mode\n localVersion = '0.2.0';\n }\n\n // 获取 NPM 最新版本\n const npmOutput = execSync('npm view readshell version', { encoding: 'utf-8' });\n const latestVersion = npmOutput.trim();\n\n if (!latestVersion) {\n throw new Error('Could not fetch npm version');\n }\n\n if (latestVersion === localVersion) {\n console.log(t('cli.update.latest', localVersion));\n return;\n }\n\n console.log(t('cli.update.updating', latestVersion, localVersion));\n \n // 执行升级指令\n execSync('npm install -g readshell@latest', { stdio: 'inherit' });\n \n console.log(t('cli.update.success'));\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n console.log(t('cli.update.fail', msg));\n logger.error('更新失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * 数据库迁移逻辑\n * 程序启动时检查并执行建表/迁移\n */\n\nimport { getDb } from './client.js';\nimport { logger } from '../utils/logger.js';\n\nconst SCHEMA_VERSION = 2;\n\n/**\n * 初始化数据库(建表 + 版本管理)\n */\nexport function initDatabase(): void {\n const db = getDb();\n\n // 创建版本管理表\n db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY\n );\n `);\n\n const row = db.prepare('SELECT version FROM schema_version LIMIT 1').get() as\n | { version: number }\n | undefined;\n const currentVersion = row?.version ?? 0;\n\n if (currentVersion < SCHEMA_VERSION) {\n logger.debug(`数据库迁移: v${currentVersion} → v${SCHEMA_VERSION}`);\n migrate(db, currentVersion);\n }\n}\n\nfunction migrate(db: ReturnType<typeof getDb>, fromVersion: number): void {\n const migrations: Record<number, string> = {\n 1: `\n -- 书籍元数据\n CREATE TABLE IF NOT EXISTS books (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n author TEXT,\n file_path TEXT NOT NULL,\n format TEXT NOT NULL,\n file_hash TEXT NOT NULL,\n file_size INTEGER,\n created_at INTEGER NOT NULL\n );\n\n -- 核心状态表\n CREATE TABLE IF NOT EXISTS reading_progress (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n chapter_no INTEGER NOT NULL DEFAULT 0,\n byte_offset INTEGER NOT NULL DEFAULT 0,\n percent REAL NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL,\n opened_at INTEGER NOT NULL\n );\n\n -- 最近阅读排序\n CREATE TABLE IF NOT EXISTS recent_reads (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n opened_at INTEGER NOT NULL,\n open_count INTEGER NOT NULL DEFAULT 1\n );\n\n -- 章节索引\n CREATE TABLE IF NOT EXISTS chapter_index (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n chapter_no INTEGER NOT NULL,\n title TEXT,\n byte_offset INTEGER NOT NULL,\n UNIQUE(book_id, chapter_no)\n );\n\n -- 索引\n CREATE INDEX IF NOT EXISTS idx_chapter_book ON chapter_index(book_id);\n CREATE INDEX IF NOT EXISTS idx_recent_opened ON recent_reads(opened_at DESC);\n `,\n 2: `\n -- 书签管理\n CREATE TABLE IF NOT EXISTS bookmarks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n title TEXT NOT NULL,\n byte_offset INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_bookmarks_book ON bookmarks(book_id);\n `,\n };\n\n db.transaction(() => {\n for (let v = fromVersion + 1; v <= SCHEMA_VERSION; v++) {\n const sql = migrations[v];\n if (sql) {\n db.exec(sql);\n logger.debug(`已执行迁移 v${v}`);\n }\n }\n\n // 更新版本号\n db.prepare('DELETE FROM schema_version').run();\n db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);\n })();\n}\n","#!/usr/bin/env node\n\n/**\n * ReadShell — 终端内低打断轻阅读工具\n * 程序主入口,解析 argv,路由到子命令\n */\n\nimport { createParser } from './cli/parser.js';\nimport { initDatabase } from './db/migrate.js';\nimport { initI18n } from './locales/index.js';\nimport { logger } from './utils/logger.js';\n\nasync function main() {\n try {\n // 初始化数据库(自动建表/迁移)\n initDatabase();\n \n // 初始化多语言本地化模块\n initI18n();\n\n // 解析命令行参数并执行对应命令\n const parser = createParser();\n await parser.parse();\n } catch (error) {\n logger.error('程序启动失败:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["existsSync","db","db","db","db","resolve","readFileSync","existsSync","config","statSync","resolve","join","yargs","React","useState","Box","Text","useState","useEffect","Box","Text","useApp","jsx","jsxs","useApp","useState","useEffect","Box","Text","useState","useEffect","Box","Text","useApp","Box","Text","jsx","Box","Text","jsx","jsxs","Box","Text","useState","useEffect","Box","Text","useInput","jsx","jsxs","useState","useEffect","useInput","Box","Text","useState","useInput","readFileSync","detect","iconv","db","jsx","jsxs","useApp","useState","useEffect","Box","Text","jsx","jsxs","useState","Box","Text","React","yargs","yargs","yargs","yargs","readFileSync","readFileSync","db"]}
|