readshell 0.2.5 → 0.3.0

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 CHANGED
@@ -70,6 +70,11 @@ novel resume
70
70
  # Open a specific book
71
71
  novel open <book-id>
72
72
 
73
+ # Configure application (language | line-spacing | reading-mode)
74
+ novel config language en
75
+ novel config line-spacing 1
76
+ novel config reading-mode scroll
77
+
73
78
  # View book list
74
79
  novel list
75
80
 
@@ -82,14 +87,12 @@ novel update
82
87
 
83
88
  ### Reader Shortcuts
84
89
 
85
- | Key | Action |
86
- |---|---|
87
- | `Space` / `j` / `↓` | Next Page |
88
- | `k` / `↑` | Previous Page |
89
- | `c` | Open Chapters and Bookmarks list |
90
+ | `Space` / `j` / `↓` | Next Page (or Scroll half-page in scroll mode) |
91
+ | `k` / `↑` | Previous Page (or Scroll half-page in scroll mode) |
92
+ | `c` | **Open Chapters and Bookmarks list (Quick Navigation)** |
90
93
  | `Tab` | Switch between Chapters/Bookmarks in menu |
91
94
  | `m` / `M` | Add Bookmark at the current page |
92
- | `b` / `Esc` | Boss Key (disguise terminal as error and save) |
95
+ | `b` / `Esc` | Boss Key (instantly disguise terminal and save) |
93
96
  | `q` | Quit and save progress |
94
97
  | `?` | Help |
95
98
 
package/dist/index.js CHANGED
@@ -360,13 +360,24 @@ function openEpub(filePath) {
360
360
  epub.parse();
361
361
  });
362
362
  }
363
+ function detectHtmlEncoding(buffer) {
364
+ const head = buffer.slice(0, 1024).toString("latin1");
365
+ const xmlDecl = head.match(/<\?xml[^>]*encoding=["']([^"']+)["']/i);
366
+ if (xmlDecl?.[1]) return xmlDecl[1];
367
+ const metaCharset = head.match(/charset=["']?([A-Za-z0-9_-]+)["']?/i);
368
+ if (metaCharset?.[1] && metaCharset[1].toLowerCase() !== "utf-8") {
369
+ return metaCharset[1];
370
+ }
371
+ const chardetResult = detect2(buffer);
372
+ return chardetResult || "utf-8";
373
+ }
363
374
  function getChapterHtml(epub, chapterId) {
364
375
  return new Promise((resolve3, reject) => {
365
376
  epub.getFile(chapterId, (err, data) => {
366
377
  if (err) return reject(err);
367
378
  const buffer = data;
368
- const encoding = detect2(buffer) || "utf-8";
369
- const htmlText = encoding.toLowerCase() === "utf-8" ? buffer.toString("utf-8") : iconv2.decode(buffer, encoding);
379
+ const encoding = detectHtmlEncoding(buffer);
380
+ const htmlText = encoding.toLowerCase().replace("-", "") === "utf8" || encoding.toLowerCase() === "utf-8" ? buffer.toString("utf-8") : iconv2.decode(buffer, encoding);
370
381
  resolve3(htmlText);
371
382
  });
372
383
  });
@@ -519,10 +530,12 @@ var zh_default = {
519
530
  "cli.remove.not_found": "\u2717 \u672A\u627E\u5230\u5339\u914D\u4E66\u7C4D:",
520
531
  "cli.remove.success": "\u2713 \u5DF2\u79FB\u9664\u4E66\u7C4D:",
521
532
  "cli.remove.fail": "\u79FB\u9664\u4E66\u7C4D\u5931\u8D25:",
522
- "cli.lang.desc": "\u5207\u6362\u8BED\u8A00",
523
- "cli.lang.help": "\u8BED\u8A00\u4EE3\u7801: zh | en",
524
533
  "cli.lang.success": "\u2713 \u8BED\u8A00\u5DF2\u5207\u6362\u4E3A: {0}",
525
534
  "cli.lang.unsupported": "\u2717 \u4E0D\u652F\u6301\u7684\u8BED\u8A00: {0}",
535
+ "cli.config.desc": "\u4FEE\u6539\u5E94\u7528\u914D\u7F6E",
536
+ "cli.config.line_spacing.desc": "\u884C\u95F4\u8DDD: 0 (\u7D27\u51D1) | 1 (\u9002\u4E2D) | 2 (\u5BBD\u677E)",
537
+ "cli.config.line_spacing.success": "\u2713 \u884C\u95F4\u8DDD\u5DF2\u8BBE\u7F6E\u4E3A: {0}",
538
+ "cli.config.reading_mode.desc": "\u9605\u8BFB\u6A21\u5F0F: page (\u7FFB\u9875) | scroll (\u6EDA\u52A8)",
526
539
  "cli.update.desc": "\u68C0\u67E5\u6700\u65B0\u7248\u672C\u5E76\u81EA\u52A8\u66F4\u65B0",
527
540
  "cli.update.checking": "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...",
528
541
  "cli.update.latest": "\u2713 \u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C (v{0})",
@@ -539,10 +552,10 @@ var zh_default = {
539
552
  "tui.reader.loading": "\u8BFB\u53D6\u4E2D...",
540
553
  "tui.reader.bookmark_add": "\u2713 \u589E\u52A0\u4E66\u7B7E: {0}",
541
554
  "tui.reader.status.remaining": "\u9884\u8BA1\u5269\u4F59 {0}",
542
- // TUI - ChapterNav
543
555
  "tui.nav.tab.chapters": "[\u5168\u90E8\u7AE0\u8282]",
544
556
  "tui.nav.tab.bookmarks": "[\u6211\u7684\u4E66\u7B7E]",
545
557
  "tui.nav.tips": "Enter \u8DF3\u8F6C \xB7 Tab \u5207\u6362 \xB7 Esc/q \u5173\u95ED",
558
+ "tui.nav.hint": "\u6309 C \u952E\u5F39\u51FA\u6B64\u6E05\u5355\uFF0C\u6309 Tab \u5207\u6362\u4E66\u7B7E",
546
559
  "tui.nav.empty": "\u6CA1\u6709\u8BB0\u5F55",
547
560
  "tui.nav.page": "\u7B2C {0} / {1} \u9875"
548
561
  };
@@ -581,10 +594,12 @@ var en = {
581
594
  "cli.remove.not_found": "\u2717 Book not found:",
582
595
  "cli.remove.success": "\u2713 Book removed:",
583
596
  "cli.remove.fail": "Failed to remove book:",
584
- "cli.lang.desc": "Switch language",
585
- "cli.lang.help": "Language code: zh | en",
586
597
  "cli.lang.success": "\u2713 Language switched to: {0}",
587
598
  "cli.lang.unsupported": "\u2717 Unsupported language: {0}",
599
+ "cli.config.desc": "Modify application configuration",
600
+ "cli.config.line_spacing.desc": "Line spacing: 0 (tight) | 1 (medium) | 2 (loose)",
601
+ "cli.config.line_spacing.success": "\u2713 Line spacing set to: {0}",
602
+ "cli.config.reading_mode.desc": "Reading mode: page (paging) | scroll (scrolling)",
588
603
  "cli.update.desc": "Check for the latest version and update automatically",
589
604
  "cli.update.checking": "Checking for updates...",
590
605
  "cli.update.latest": "\u2713 Already up to date (v{0})",
@@ -605,6 +620,7 @@ var en = {
605
620
  "tui.nav.tab.chapters": "[All Chapters]",
606
621
  "tui.nav.tab.bookmarks": "[My Bookmarks]",
607
622
  "tui.nav.tips": "Enter jump \xB7 Tab switch \xB7 Esc/q close",
623
+ "tui.nav.hint": "Press C to open this list, press Tab to switch to bookmarks",
608
624
  "tui.nav.empty": "No records",
609
625
  "tui.nav.page": "Page {0} / {1}"
610
626
  };
@@ -616,7 +632,8 @@ var defaults = {
616
632
  linesPerPage: 0,
617
633
  showStatusBar: true,
618
634
  readingMode: "page",
619
- language: "zh"
635
+ language: "zh",
636
+ lineSpacing: 0
620
637
  };
621
638
  var config = new Conf({
622
639
  projectName: "readshell",
@@ -627,7 +644,8 @@ function getConfig() {
627
644
  linesPerPage: config.get("linesPerPage"),
628
645
  showStatusBar: config.get("showStatusBar"),
629
646
  readingMode: config.get("readingMode"),
630
- language: config.get("language")
647
+ language: config.get("language"),
648
+ lineSpacing: config.get("lineSpacing")
631
649
  };
632
650
  }
633
651
  function setConfig(key, value) {
@@ -935,7 +953,7 @@ function renderLineWithHighlight(line) {
935
953
  return /* @__PURE__ */ jsx3(Text3, { children: part }, index);
936
954
  }) });
937
955
  }
938
- function TextRenderer({ lines, height }) {
956
+ function TextRenderer({ lines, height, lineSpacing = 0 }) {
939
957
  const displayLines = [...lines];
940
958
  if (height && displayLines.length < height) {
941
959
  const padding = height - displayLines.length;
@@ -943,7 +961,7 @@ function TextRenderer({ lines, height }) {
943
961
  displayLines.push("");
944
962
  }
945
963
  }
946
- return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: displayLines.map((line, index) => /* @__PURE__ */ jsx3(Box3, { children: renderLineWithHighlight(line) }, index)) });
964
+ return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: displayLines.map((line, index) => /* @__PURE__ */ jsx3(Box3, { marginBottom: lineSpacing, children: renderLineWithHighlight(line) }, index)) });
947
965
  }
948
966
 
949
967
  // src/ui/components/StatusBar.tsx
@@ -1211,9 +1229,10 @@ function isFullWidth(char) {
1211
1229
  }
1212
1230
 
1213
1231
  // src/utils/paginate.ts
1214
- function paginate(text, width, height) {
1232
+ function paginate(text, width, height, step) {
1215
1233
  const pages = [];
1216
1234
  const rawLines = text.split("\n");
1235
+ const actualStep = step || height;
1217
1236
  const wrappedLines = [];
1218
1237
  let currentOffset = 0;
1219
1238
  for (const rawLine of rawLines) {
@@ -1223,12 +1242,14 @@ function paginate(text, width, height) {
1223
1242
  }
1224
1243
  currentOffset += Buffer.byteLength(rawLine + "\n", "utf-8");
1225
1244
  }
1226
- for (let i = 0; i < wrappedLines.length; i += height) {
1245
+ for (let i = 0; i < wrappedLines.length; i += actualStep) {
1227
1246
  const pageLines = wrappedLines.slice(i, i + height);
1247
+ if (pageLines.length === 0) break;
1228
1248
  pages.push({
1229
1249
  lines: pageLines.map((l) => l.text),
1230
1250
  byteOffset: pageLines[0]?.byteOffset ?? 0
1231
1251
  });
1252
+ if (i + actualStep >= wrappedLines.length) break;
1232
1253
  }
1233
1254
  return pages;
1234
1255
  }
@@ -1254,19 +1275,6 @@ function wrapLine(line, width) {
1254
1275
  return result.length > 0 ? result : [""];
1255
1276
  }
1256
1277
 
1257
- // src/utils/encoding.ts
1258
- import { readFileSync as readFileSync3 } from "fs";
1259
- import { detect as detect3 } from "chardet";
1260
- import iconv3 from "iconv-lite";
1261
- function readFileWithEncoding(filePath) {
1262
- const buffer = readFileSync3(filePath);
1263
- const encoding = detect3(buffer) || "utf-8";
1264
- if (encoding.toLowerCase() === "utf-8" || encoding.toLowerCase() === "ascii") {
1265
- return buffer.toString("utf-8");
1266
- }
1267
- return iconv3.decode(buffer, encoding);
1268
- }
1269
-
1270
1278
  // src/services/ChapterService.ts
1271
1279
  var ChapterService = class {
1272
1280
  chapterModel = new ChapterModel();
@@ -1417,11 +1425,17 @@ var BookmarkService = class {
1417
1425
  function triggerBossKey() {
1418
1426
  console.clear();
1419
1427
  const fakeLog = `
1420
- VITE v5.2.8 ready in 213 ms
1428
+ file:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916
1429
+ throw new Error(\`[vite] Failed to resolve module import "./App.vue". Check if the file exists.\`);
1430
+ ^
1421
1431
 
1422
- \u279C Local: http://localhost:5173/
1423
- \u279C Network: use --host to expose
1424
- \u279C press h + enter to show help
1432
+ Error: [vite] Failed to resolve module import "./App.vue". Check if the file exists.
1433
+ at Object.run (file:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916:13)
1434
+ at async file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:722:7
1435
+ at async startVite (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:700:5)
1436
+ at async Object.handler (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:650:1)
1437
+
1438
+ Node.js v20.11.0
1425
1439
  `;
1426
1440
  console.log(fakeLog);
1427
1441
  process.exit(0);
@@ -1449,7 +1463,8 @@ function ReaderContent({
1449
1463
  pages,
1450
1464
  initialByteOffset,
1451
1465
  termHeight,
1452
- contentHeight
1466
+ contentHeight,
1467
+ lineSpacing
1453
1468
  }) {
1454
1469
  const { exit } = useApp3();
1455
1470
  const [chapterTitle, setChapterTitle] = useState5();
@@ -1462,6 +1477,14 @@ function ReaderContent({
1462
1477
  const chapterServiceRef = useRef(new ChapterService());
1463
1478
  const bookmarkServiceRef = useRef(new BookmarkService());
1464
1479
  const reader = useReader(pages, initialByteOffset);
1480
+ const saveReadingProgress = () => {
1481
+ const offset = reader.getCurrentOffset();
1482
+ const percent = reader.getPercent();
1483
+ const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);
1484
+ const chapterNo = chapter?.chapter_no ?? 0;
1485
+ progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);
1486
+ logger.debug(`\u8FDB\u5EA6\u5DF2\u4FDD\u5B58: offset=${offset}, ${(percent * 100).toFixed(1)}%`);
1487
+ };
1465
1488
  useEffect4(() => {
1466
1489
  const currentOffset = reader.getCurrentOffset();
1467
1490
  const chapter = chapterServiceRef.current.getChapterByOffset(bookId, currentOffset);
@@ -1493,12 +1516,7 @@ function ReaderContent({
1493
1516
  };
1494
1517
  useEffect4(() => {
1495
1518
  return () => {
1496
- const offset = reader.getCurrentOffset();
1497
- const percent = reader.getPercent();
1498
- const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);
1499
- const chapterNo = chapter?.chapter_no ?? 0;
1500
- progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);
1501
- logger.debug(`\u8FDB\u5EA6\u5DF2\u4FDD\u5B58: offset=${offset}, ${(percent * 100).toFixed(1)}%`);
1519
+ saveReadingProgress();
1502
1520
  };
1503
1521
  }, [bookId, reader]);
1504
1522
  useKeyboard(
@@ -1507,7 +1525,10 @@ function ReaderContent({
1507
1525
  onPrev: () => reader.prevPage(),
1508
1526
  onQuit: () => exit(),
1509
1527
  onChapterList: () => setShowChapterNav(true),
1510
- onBossKey: () => triggerBossKey(),
1528
+ onBossKey: () => {
1529
+ saveReadingProgress();
1530
+ triggerBossKey();
1531
+ },
1511
1532
  onBookmarkAdd: handleAddBookmark
1512
1533
  },
1513
1534
  !showChapterNav
@@ -1521,7 +1542,7 @@ function ReaderContent({
1521
1542
  const remainingMinutes = estimateReadingTime(remainingChars, true);
1522
1543
  const remainingTimeStr = formatReadingTime(remainingMinutes);
1523
1544
  return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: termHeight, children: !showChapterNav ? /* @__PURE__ */ jsxs5(Fragment, { children: [
1524
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsx6(TextRenderer, { lines: currentLines, height: calculatedContentHeight }) }),
1545
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsx6(TextRenderer, { lines: currentLines, height: calculatedContentHeight, lineSpacing }) }),
1525
1546
  /* @__PURE__ */ jsx6(
1526
1547
  StatusBar,
1527
1548
  {
@@ -1557,7 +1578,9 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
1557
1578
  const [error, setError] = useState5(null);
1558
1579
  const termWidth = stdout?.columns ?? 80;
1559
1580
  const termHeight = stdout?.rows ?? 24;
1560
- const contentHeight = Math.max(termHeight - 3, 5);
1581
+ const appConfig = getConfig();
1582
+ const lineSpacing = appConfig.lineSpacing || 0;
1583
+ const contentHeight = Math.max(Math.floor((termHeight - 3) / (1 + lineSpacing)), 2);
1561
1584
  useEffect4(() => {
1562
1585
  try {
1563
1586
  const bookModel = new BookModel();
@@ -1569,12 +1592,16 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
1569
1592
  setBook(bookRecord);
1570
1593
  const recentService = new RecentService();
1571
1594
  recentService.recordOpen(bookId);
1572
- const content = readFileWithEncoding(bookRecord.file_path);
1573
- const paginatedPages = paginate(content, termWidth - 2, contentHeight);
1574
- setPages(paginatedPages);
1575
- logger.debug(`\u52A0\u8F7D\u5B8C\u6210: ${bookRecord.title}, ${paginatedPages.length} \u9875`);
1595
+ parseFile(bookRecord.file_path, bookRecord.format).then((parsed) => {
1596
+ const stepSize = appConfig.readingMode === "scroll" ? Math.max(1, Math.floor(contentHeight / 2)) : contentHeight;
1597
+ const paginatedPages = paginate(parsed.content, termWidth - 2, contentHeight, stepSize);
1598
+ setPages(paginatedPages);
1599
+ logger.debug(`\u52A0\u8F7D\u5B8C\u6210: ${bookRecord.title}, ${paginatedPages.length} \u9875, \u6A21\u5F0F: ${appConfig.readingMode}`);
1600
+ }).catch((err) => {
1601
+ setError(`\u5185\u5BB9\u89E3\u6790\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
1602
+ });
1576
1603
  } catch (err) {
1577
- setError(`\u52A0\u8F7D\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
1604
+ setError(`\u52A0\u8F7D\u8FC7\u7A0B\u51FA\u9519: ${err instanceof Error ? err.message : String(err)}`);
1578
1605
  }
1579
1606
  }, [bookId, termWidth, contentHeight]);
1580
1607
  useKeyboard({
@@ -1603,7 +1630,8 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
1603
1630
  pages,
1604
1631
  initialByteOffset,
1605
1632
  termHeight,
1606
- contentHeight
1633
+ contentHeight,
1634
+ lineSpacing
1607
1635
  }
1608
1636
  );
1609
1637
  }
@@ -1811,7 +1839,7 @@ var updateCommand = {
1811
1839
  handler: async () => {
1812
1840
  try {
1813
1841
  console.log(t("cli.update.checking"));
1814
- const localVersion = true ? "0.2.5" : "0.2.2";
1842
+ const localVersion = true ? "0.3.0" : "0.2.2";
1815
1843
  const npmOutput = execSync("npm view readshell version", { encoding: "utf-8" });
1816
1844
  const latestVersion = npmOutput.trim();
1817
1845
  if (!latestVersion) {
@@ -1835,7 +1863,7 @@ var updateCommand = {
1835
1863
 
1836
1864
  // src/cli/parser.ts
1837
1865
  function createParser() {
1838
- const version = true ? "0.2.5" : "dev";
1866
+ const version = true ? "0.3.0" : "dev";
1839
1867
  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(version).epilogue("ReadShell \u2014 \u7EC8\u7AEF\u5185\u4F4E\u6253\u65AD\u8F7B\u9605\u8BFB\u5DE5\u5177");
1840
1868
  }
1841
1869
 
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/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/services/RecentService.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 const version = typeof APP_VERSION !== 'undefined' ? APP_VERSION : 'dev';\n\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(version)\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 { detect } from 'chardet';\nimport iconv from 'iconv-lite';\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 getChapterHtml(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 * 获取单个章节的 HTML 原文并自动转码\n * 包装为 Promise\n */\nfunction getChapterHtml(epub: any, chapterId: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // 使用 getFile 获取原始 Buffer,避免 epub2 内部 getChapterRaw 强制转 utf-8 导致的乱码\n epub.getFile(chapterId, (err: Error | null, data: Buffer) => {\n if (err) return reject(err);\n\n const buffer = data;\n // 探测编码(如 GBK, UTF-8)\n const encoding = detect(buffer) || 'utf-8';\n \n const htmlText = encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n resolve(htmlText);\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 book list / 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 &lt;file&gt; 导入一本书开始阅读。</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 bookService = new BookService();\n setBooks(bookService.getAllBooks());\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 * 功能:\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 * 最近阅读业务逻辑\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 * 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: 'list',\n aliases: ['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 // 获取本地版本号 (由 tsup 在构建时注入)\n const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0.2.2';\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","detect","iconv","resolve","detect","iconv","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/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/services/ChapterService.ts","../src/services/RecentService.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 const version = typeof APP_VERSION !== 'undefined' ? APP_VERSION : 'dev';\n\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(version)\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 { detect } from 'chardet';\nimport iconv from 'iconv-lite';\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 getChapterHtml(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 * 从 HTML/XML 原始字节中提取编码声明\n * 优先读取 XML 声明 (<?xml encoding=\"GBK\">) 和 meta charset\n */\nfunction detectHtmlEncoding(buffer: Buffer): string {\n // 用 latin1 无损读取前 1KB,仅为提取编码声明\n const head = buffer.slice(0, 1024).toString('latin1');\n\n // 1. 优先查找 XML 声明:<?xml version=\"1.0\" encoding=\"GBK\"?>\n const xmlDecl = head.match(/<\\?xml[^>]*encoding=[\"']([^\"']+)[\"']/i);\n if (xmlDecl?.[1]) return xmlDecl[1];\n\n // 2. 查找 HTML meta charset:<meta charset=\"GBK\"> 或 Content-Type: charset=GBK\n const metaCharset = head.match(/charset=[\"']?([A-Za-z0-9_-]+)[\"']?/i);\n if (metaCharset?.[1] && metaCharset[1].toLowerCase() !== 'utf-8') {\n return metaCharset[1];\n }\n\n // 3. 回退:使用 chardet 字节模式探测\n const chardetResult = detect(buffer);\n return chardetResult || 'utf-8';\n}\n\n/**\n * 获取单个章节的 HTML 原文并自动转码\n * 包装为 Promise\n */\nfunction getChapterHtml(epub: any, chapterId: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // 使用 getFile 获取原始 Buffer,避免 epub2 内部 getChapterRaw 强制转 utf-8 导致的乱码\n epub.getFile(chapterId, (err: Error | null, data: Buffer) => {\n if (err) return reject(err);\n\n const buffer = data;\n const encoding = detectHtmlEncoding(buffer);\n\n const htmlText = encoding.toLowerCase().replace('-', '') === 'utf8' || encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n resolve(htmlText);\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.success': '✓ 语言已切换为: {0}',\n 'cli.lang.unsupported': '✗ 不支持的语言: {0}',\n\n 'cli.config.desc': '修改应用配置',\n 'cli.config.line_spacing.desc': '行间距: 0 (紧凑) | 1 (适中) | 2 (宽松)',\n 'cli.config.line_spacing.success': '✓ 行间距已设置为: {0}',\n 'cli.config.reading_mode.desc': '阅读模式: page (翻页) | scroll (滚动)',\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.nav.tab.chapters': '[全部章节]',\n 'tui.nav.tab.bookmarks': '[我的书签]',\n 'tui.nav.tips': 'Enter 跳转 · Tab 切换 · Esc/q 关闭',\n 'tui.nav.hint': '按 C 键弹出此清单,按 Tab 切换书签',\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 book list / 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.success': '✓ Language switched to: {0}',\n 'cli.lang.unsupported': '✗ Unsupported language: {0}',\n\n 'cli.config.desc': 'Modify application configuration',\n 'cli.config.line_spacing.desc': 'Line spacing: 0 (tight) | 1 (medium) | 2 (loose)',\n 'cli.config.line_spacing.success': '✓ Line spacing set to: {0}',\n 'cli.config.reading_mode.desc': 'Reading mode: page (paging) | scroll (scrolling)',\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.hint': 'Press C to open this list, press Tab to switch to bookmarks',\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 /** 行间距 (0-2) */\n lineSpacing: number;\n}\n\nconst defaults: ReadShellConfig = {\n linesPerPage: 0,\n showStatusBar: true,\n readingMode: 'page',\n language: 'zh',\n lineSpacing: 0,\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 lineSpacing: config.get('lineSpacing'),\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 &lt;file&gt; 导入一本书开始阅读。</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 bookService = new BookService();\n setBooks(bookService.getAllBooks());\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 * 功能:\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 { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { parseFile, type ParsedBook } from '../../parsers/index.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';\nimport { getConfig } from '../../config/AppConfig.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 lineSpacing,\n}: {\n book: BookRecord;\n bookId: string;\n pages: Page[];\n initialByteOffset?: number;\n termHeight: number;\n contentHeight: number;\n lineSpacing: 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 const saveReadingProgress = () => {\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\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 saveReadingProgress();\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: () => {\n saveReadingProgress(); // 老板来了!先存盘\n triggerBossKey(); // 再跑路\n },\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} lineSpacing={lineSpacing} />\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\n const appConfig = getConfig();\n const lineSpacing = appConfig.lineSpacing || 0;\n \n // 核心优化:如果行间距 > 0,则物理行数会翻倍。为了保证不超出屏幕,分页时的内容高度需要对应缩小。\n const contentHeight = Math.max(Math.floor((termHeight - 3) / (1 + lineSpacing)), 2);\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 // 异步读取并解析文件内容 (支持 TXT 和 EPUB)\n parseFile(bookRecord.file_path, bookRecord.format as 'txt' | 'epub')\n .then((parsed: ParsedBook) => {\n // 核心优化:如果处于滚动模式,步进减半以实现平滑过渡\n const stepSize = appConfig.readingMode === 'scroll' \n ? Math.max(1, Math.floor(contentHeight / 2)) \n : contentHeight;\n\n // 分页\n const paginatedPages = paginate(parsed.content, termWidth - 2, contentHeight, stepSize);\n setPages(paginatedPages);\n logger.debug(`加载完成: ${bookRecord.title}, ${paginatedPages.length} 页, 模式: ${appConfig.readingMode}`);\n })\n .catch((err: Error) => {\n setError(`内容解析失败: ${err instanceof Error ? err.message : String(err)}`);\n });\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 lineSpacing={lineSpacing}\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 lineSpacing?: 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, lineSpacing = 0 }: 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} marginBottom={lineSpacing}>\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 * @param step 步进行数(用于平滑滚动,不传则默认为 height)\n * @returns 分页结果\n */\nexport function paginate(text: string, width: number, height: number, step?: number): Page[] {\n const pages: Page[] = [];\n const rawLines = text.split('\\n');\n const actualStep = step || height;\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 += actualStep) {\n const pageLines = wrappedLines.slice(i, i + height);\n if (pageLines.length === 0) break;\n\n pages.push({\n lines: pageLines.map((l) => l.text),\n byteOffset: pageLines[0]?.byteOffset ?? 0,\n });\n\n // 已经处理完所有行\n if (i + actualStep >= wrappedLines.length) break;\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 * 章节索引业务逻辑\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 * 最近阅读业务逻辑\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 * 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. 打印极度逼真的伪装日志(终端报错风格)\n const fakeLog = `\nfile:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916\n throw new Error(\\`[vite] Failed to resolve module import \"./App.vue\". Check if the file exists.\\`);\n ^\n\nError: [vite] Failed to resolve module import \"./App.vue\". Check if the file exists.\n at Object.run (file:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916:13)\n at async file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:722:7\n at async startVite (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:700:5)\n at async Object.handler (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:650:1)\n\nNode.js v20.11.0\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: 'list',\n aliases: ['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 // 获取本地版本号 (由 tsup 在构建时注入)\n const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0.2.2';\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","detect","iconv","resolve","detect","iconv","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","db","jsx","jsxs","useApp","useState","useEffect","Box","Text","jsx","jsxs","useState","Box","Text","React","yargs","yargs","yargs","yargs","db"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "readshell",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "终端内低打断轻阅读工具 · CLI Light Reading Tool for Developers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",