readshell 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -64
- package/dist/index.js +349 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -351,18 +351,18 @@ ${plainText}
|
|
|
351
351
|
};
|
|
352
352
|
}
|
|
353
353
|
function openEpub(filePath) {
|
|
354
|
-
return new Promise((
|
|
354
|
+
return new Promise((resolve3, reject) => {
|
|
355
355
|
const epub = new EPub(filePath);
|
|
356
356
|
epub.on("error", (err) => reject(err));
|
|
357
|
-
epub.on("end", () =>
|
|
357
|
+
epub.on("end", () => resolve3(epub));
|
|
358
358
|
epub.parse();
|
|
359
359
|
});
|
|
360
360
|
}
|
|
361
361
|
function getChapterText(epub, chapterId) {
|
|
362
|
-
return new Promise((
|
|
362
|
+
return new Promise((resolve3, reject) => {
|
|
363
363
|
epub.getChapter(chapterId, (err, text) => {
|
|
364
364
|
if (err) return reject(err);
|
|
365
|
-
|
|
365
|
+
resolve3(text);
|
|
366
366
|
});
|
|
367
367
|
});
|
|
368
368
|
}
|
|
@@ -480,24 +480,263 @@ var BookService = class {
|
|
|
480
480
|
}
|
|
481
481
|
};
|
|
482
482
|
|
|
483
|
+
// src/locales/zh.ts
|
|
484
|
+
var zh_default = {
|
|
485
|
+
// Common
|
|
486
|
+
"common.yes": "\u662F",
|
|
487
|
+
"common.no": "\u5426",
|
|
488
|
+
"common.confirm": "\u786E\u8BA4",
|
|
489
|
+
"common.cancel": "\u53D6\u6D88",
|
|
490
|
+
"common.quit": "\u6309 q \u9000\u51FA",
|
|
491
|
+
// CLI
|
|
492
|
+
"cli.import.desc": "\u5BFC\u5165\u672C\u5730\u6587\u4EF6\u6216\u76EE\u5F55\u5230\u4E66\u67B6",
|
|
493
|
+
"cli.import.help": "\u6587\u4EF6\u6216\u76EE\u5F55\u8DEF\u5F84\uFF08\u652F\u6301 .txt / .epub\uFF09",
|
|
494
|
+
"cli.import.success": "\u2713 \u5DF2\u5BFC\u5165:",
|
|
495
|
+
"cli.import.fail": "\u5BFC\u5165\u5931\u8D25:",
|
|
496
|
+
"cli.import.not_found": "\u8DEF\u5F84\u4E0D\u5B58\u5728:",
|
|
497
|
+
"cli.import.unsupported": "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u683C\u5F0F\u3002\u76EE\u524D\u652F\u6301: .txt, .epub",
|
|
498
|
+
"cli.import.scan_dir": "\u626B\u63CF\u76EE\u5F55",
|
|
499
|
+
"cli.import.found_files": "\u627E\u5230\u4EE5\u4E0B\u4E66\u7C4D\uFF1A",
|
|
500
|
+
"cli.import.confirm_batch": "\u662F\u5426\u786E\u8BA4\u5BFC\u5165\u4E0A\u8FF0 {0} \u672C\u4E66\uFF1F(y/N)",
|
|
501
|
+
"cli.import.canceled": "\u2713 \u5BFC\u5165\u5DF2\u53D6\u6D88",
|
|
502
|
+
"cli.resume.desc": "\u6062\u590D\u4E0A\u6B21\u9605\u8BFB",
|
|
503
|
+
"cli.resume.none": "\u2717 \u6CA1\u6709\u627E\u5230\u6700\u8FD1\u9605\u8BFB\u8BB0\u5F55\uFF0C\u8BF7\u5148\u4F7F\u7528 novel import <file> \u6216 novel open <book-id> \u6253\u5F00\u4E00\u672C\u4E66\u3002",
|
|
504
|
+
"cli.open.desc": "\u6253\u5F00\u6307\u5B9A\u4E66\u7C4D",
|
|
505
|
+
"cli.open.help": "\u4E66\u7C4D ID \u6216\u4E66\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09",
|
|
506
|
+
"cli.open.not_found": "\u2717 \u672A\u627E\u5230\u5339\u914D\u4E66\u7C4D:",
|
|
507
|
+
"cli.library.desc": "\u67E5\u770B\u4E66\u67B6\u5217\u8868",
|
|
508
|
+
"cli.library.help": "\u53EF\u9009\u5173\u952E\u5B57\u641C\u7D22",
|
|
509
|
+
"cli.library.none": "\u2717 \u4E66\u67B6\u4E3A\u7A7A\u3002\u4F7F\u7528 novel import <file> \u5BFC\u5165\u4F60\u7684\u7B2C\u4E00\u672C\u4E66\u3002",
|
|
510
|
+
"cli.library.search_none": "\u{1F4DA} \u672A\u627E\u5230\u5339\u914D\u300C{0}\u300D\u7684\u4E66\u7C4D\u3002",
|
|
511
|
+
"cli.library.search_result": "\u{1F4DA} \u641C\u7D22\u7ED3\u679C ({0} \u672C):\n",
|
|
512
|
+
"cli.remove.desc": "\u4ECE\u4E66\u67B6\u79FB\u9664\u4E66\u7C4D\uFF08\u4EC5\u5220\u9664\u8BB0\u5F55\uFF0C\u4E0D\u5220\u6E90\u6587\u4EF6\uFF09",
|
|
513
|
+
"cli.remove.help": "\u4E66\u7C4D ID \u6216\u4E66\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09",
|
|
514
|
+
"cli.remove.not_found": "\u2717 \u672A\u627E\u5230\u5339\u914D\u4E66\u7C4D:",
|
|
515
|
+
"cli.remove.success": "\u2713 \u5DF2\u79FB\u9664\u4E66\u7C4D:",
|
|
516
|
+
"cli.remove.fail": "\u79FB\u9664\u4E66\u7C4D\u5931\u8D25:",
|
|
517
|
+
"cli.lang.desc": "\u5207\u6362\u8BED\u8A00",
|
|
518
|
+
"cli.lang.help": "\u8BED\u8A00\u4EE3\u7801: zh | en",
|
|
519
|
+
"cli.lang.success": "\u2713 \u8BED\u8A00\u5DF2\u5207\u6362\u4E3A: {0}",
|
|
520
|
+
"cli.lang.unsupported": "\u2717 \u4E0D\u652F\u6301\u7684\u8BED\u8A00: {0}",
|
|
521
|
+
"cli.update.desc": "\u68C0\u67E5\u6700\u65B0\u7248\u672C\u5E76\u81EA\u52A8\u66F4\u65B0",
|
|
522
|
+
"cli.update.checking": "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...",
|
|
523
|
+
"cli.update.latest": "\u2713 \u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C (v{0})",
|
|
524
|
+
"cli.update.updating": "\u53D1\u73B0\u65B0\u7248\u672C v{0} (\u5F53\u524D v{1})\uFF0C\u6B63\u5728\u66F4\u65B0...",
|
|
525
|
+
"cli.update.success": "\u2713 \u5347\u7EA7\u6210\u529F\uFF01\u8BF7\u91CD\u65B0\u8FD0\u884C novel \u547D\u4EE4\u3002",
|
|
526
|
+
"cli.update.fail": "\u2717 \u5347\u7EA7\u5931\u8D25: {0}",
|
|
527
|
+
// TUI - Library
|
|
528
|
+
"tui.lib.loading": "\u{1F4DA} \u52A0\u8F7D\u4E66\u67B6...",
|
|
529
|
+
"tui.lib.empty.title": "\u{1F4DA} \u4E66\u67B6",
|
|
530
|
+
"tui.lib.empty.desc": "\u4E66\u67B6\u4E3A\u7A7A\u3002\u4F7F\u7528 novel import <file> \u5BFC\u5165\u4F60\u7684\u7B2C\u4E00\u672C\u4E66\u3002",
|
|
531
|
+
"tui.lib.title": "\u{1F4DA} \u4E66\u67B6 ({0} \u672C)",
|
|
532
|
+
"tui.lib.tips": " \u2191\u2193/jk \u9009\u62E9 \xB7 Enter \u6253\u5F00 \xB7 d/x \u5220\u9664 \xB7 q \u9000\u51FA",
|
|
533
|
+
// TUI - Reader
|
|
534
|
+
"tui.reader.loading": "\u8BFB\u53D6\u4E2D...",
|
|
535
|
+
"tui.reader.bookmark_add": "\u2713 \u589E\u52A0\u4E66\u7B7E: {0}",
|
|
536
|
+
"tui.reader.status.remaining": "\u9884\u8BA1\u5269\u4F59 {0}",
|
|
537
|
+
// TUI - ChapterNav
|
|
538
|
+
"tui.nav.tab.chapters": "[\u5168\u90E8\u7AE0\u8282]",
|
|
539
|
+
"tui.nav.tab.bookmarks": "[\u6211\u7684\u4E66\u7B7E]",
|
|
540
|
+
"tui.nav.tips": "Enter \u8DF3\u8F6C \xB7 Tab \u5207\u6362 \xB7 Esc/q \u5173\u95ED",
|
|
541
|
+
"tui.nav.empty": "\u6CA1\u6709\u8BB0\u5F55",
|
|
542
|
+
"tui.nav.page": "\u7B2C {0} / {1} \u9875"
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// src/locales/en.ts
|
|
546
|
+
var en = {
|
|
547
|
+
// Common
|
|
548
|
+
"common.yes": "Yes",
|
|
549
|
+
"common.no": "No",
|
|
550
|
+
"common.confirm": "Confirm",
|
|
551
|
+
"common.cancel": "Cancel",
|
|
552
|
+
"common.quit": "Press q to quit",
|
|
553
|
+
// CLI
|
|
554
|
+
"cli.import.desc": "Import local file or directory to library",
|
|
555
|
+
"cli.import.help": "File or directory path (supports .txt / .epub)",
|
|
556
|
+
"cli.import.success": "\u2713 Imported:",
|
|
557
|
+
"cli.import.fail": "Import failed:",
|
|
558
|
+
"cli.import.not_found": "Path not found:",
|
|
559
|
+
"cli.import.unsupported": "Unsupported file format. Currently supports: .txt, .epub",
|
|
560
|
+
"cli.import.scan_dir": "Scanning directory",
|
|
561
|
+
"cli.import.found_files": "Found following books:",
|
|
562
|
+
"cli.import.confirm_batch": "Confirm importing these {0} books? (y/N)",
|
|
563
|
+
"cli.import.canceled": "\u2713 Import canceled",
|
|
564
|
+
"cli.resume.desc": "Resume last reading",
|
|
565
|
+
"cli.resume.none": "\u2717 No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.",
|
|
566
|
+
"cli.open.desc": "Open specific book",
|
|
567
|
+
"cli.open.help": "Book ID or title (fuzzy match)",
|
|
568
|
+
"cli.open.not_found": "\u2717 Book not found:",
|
|
569
|
+
"cli.library.desc": "View library",
|
|
570
|
+
"cli.library.help": "Optional keyword search",
|
|
571
|
+
"cli.library.none": "\u2717 Library is empty. Use `novel import <file>` to import your first book.",
|
|
572
|
+
"cli.library.search_none": '\u{1F4DA} No books found matching "{0}".',
|
|
573
|
+
"cli.library.search_result": "\u{1F4DA} Search results ({0} books):\n",
|
|
574
|
+
"cli.remove.desc": "Remove book from library (only deletes records, not source file)",
|
|
575
|
+
"cli.remove.help": "Book ID or title (fuzzy match)",
|
|
576
|
+
"cli.remove.not_found": "\u2717 Book not found:",
|
|
577
|
+
"cli.remove.success": "\u2713 Book removed:",
|
|
578
|
+
"cli.remove.fail": "Failed to remove book:",
|
|
579
|
+
"cli.lang.desc": "Switch language",
|
|
580
|
+
"cli.lang.help": "Language code: zh | en",
|
|
581
|
+
"cli.lang.success": "\u2713 Language switched to: {0}",
|
|
582
|
+
"cli.lang.unsupported": "\u2717 Unsupported language: {0}",
|
|
583
|
+
"cli.update.desc": "Check for the latest version and update automatically",
|
|
584
|
+
"cli.update.checking": "Checking for updates...",
|
|
585
|
+
"cli.update.latest": "\u2713 Already up to date (v{0})",
|
|
586
|
+
"cli.update.updating": "New version v{0} found (current v{1}), updating...",
|
|
587
|
+
"cli.update.success": "\u2713 Successfully updated! Please restart novel command.",
|
|
588
|
+
"cli.update.fail": "\u2717 Update failed: {0}",
|
|
589
|
+
// TUI - Library
|
|
590
|
+
"tui.lib.loading": "\u{1F4DA} Loading library...",
|
|
591
|
+
"tui.lib.empty.title": "\u{1F4DA} Library",
|
|
592
|
+
"tui.lib.empty.desc": "Library is empty. Use `novel import <file>` to import your first book.",
|
|
593
|
+
"tui.lib.title": "\u{1F4DA} Library ({0} books)",
|
|
594
|
+
"tui.lib.tips": " \u2191\u2193/jk select \xB7 Enter open \xB7 d/x delete \xB7 q quit",
|
|
595
|
+
// TUI - Reader
|
|
596
|
+
"tui.reader.loading": "Loading...",
|
|
597
|
+
"tui.reader.bookmark_add": "\u2713 Bookmark added: {0}",
|
|
598
|
+
"tui.reader.status.remaining": "Est. remaining {0}",
|
|
599
|
+
// TUI - ChapterNav
|
|
600
|
+
"tui.nav.tab.chapters": "[All Chapters]",
|
|
601
|
+
"tui.nav.tab.bookmarks": "[My Bookmarks]",
|
|
602
|
+
"tui.nav.tips": "Enter jump \xB7 Tab switch \xB7 Esc/q close",
|
|
603
|
+
"tui.nav.empty": "No records",
|
|
604
|
+
"tui.nav.page": "Page {0} / {1}"
|
|
605
|
+
};
|
|
606
|
+
var en_default = en;
|
|
607
|
+
|
|
608
|
+
// src/config/AppConfig.ts
|
|
609
|
+
import Conf from "conf";
|
|
610
|
+
var defaults = {
|
|
611
|
+
linesPerPage: 0,
|
|
612
|
+
showStatusBar: true,
|
|
613
|
+
readingMode: "page",
|
|
614
|
+
language: "zh"
|
|
615
|
+
};
|
|
616
|
+
var config = new Conf({
|
|
617
|
+
projectName: "readshell",
|
|
618
|
+
defaults
|
|
619
|
+
});
|
|
620
|
+
function getConfig() {
|
|
621
|
+
return {
|
|
622
|
+
linesPerPage: config.get("linesPerPage"),
|
|
623
|
+
showStatusBar: config.get("showStatusBar"),
|
|
624
|
+
readingMode: config.get("readingMode"),
|
|
625
|
+
language: config.get("language")
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function setConfig(key, value) {
|
|
629
|
+
config.set(key, value);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/locales/index.ts
|
|
633
|
+
var dictionaries = {
|
|
634
|
+
zh: zh_default,
|
|
635
|
+
en: en_default
|
|
636
|
+
};
|
|
637
|
+
var currentLang = "zh";
|
|
638
|
+
var currentDict = dictionaries.zh;
|
|
639
|
+
function initI18n() {
|
|
640
|
+
const config2 = getConfig();
|
|
641
|
+
currentLang = config2.language || "zh";
|
|
642
|
+
currentDict = dictionaries[currentLang] || dictionaries.zh;
|
|
643
|
+
}
|
|
644
|
+
function setLanguage(lang) {
|
|
645
|
+
currentLang = lang;
|
|
646
|
+
currentDict = dictionaries[lang] || dictionaries.zh;
|
|
647
|
+
}
|
|
648
|
+
function t(key, ...args) {
|
|
649
|
+
let template = currentDict[key];
|
|
650
|
+
if (!template) {
|
|
651
|
+
return key;
|
|
652
|
+
}
|
|
653
|
+
if (args.length > 0) {
|
|
654
|
+
args.forEach((arg, index) => {
|
|
655
|
+
template = template.replace(`{${index}}`, String(arg));
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
return template;
|
|
659
|
+
}
|
|
660
|
+
|
|
483
661
|
// src/cli/commands/import.ts
|
|
662
|
+
import { statSync as statSync2, readdirSync } from "fs";
|
|
663
|
+
import { resolve as resolve2, join as join2, extname } from "path";
|
|
664
|
+
import * as readline from "readline/promises";
|
|
665
|
+
function scanDirectory(dir) {
|
|
666
|
+
let results = [];
|
|
667
|
+
try {
|
|
668
|
+
const list = readdirSync(dir);
|
|
669
|
+
for (const file of list) {
|
|
670
|
+
const fullPath = join2(dir, file);
|
|
671
|
+
const stat = statSync2(fullPath);
|
|
672
|
+
if (stat.isDirectory()) {
|
|
673
|
+
results = results.concat(scanDirectory(fullPath));
|
|
674
|
+
} else {
|
|
675
|
+
const ext = extname(fullPath).toLowerCase();
|
|
676
|
+
if (ext === ".txt" || ext === ".epub") {
|
|
677
|
+
results.push(fullPath);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
} catch (err) {
|
|
682
|
+
logger.error("Scan error:", err);
|
|
683
|
+
}
|
|
684
|
+
return results;
|
|
685
|
+
}
|
|
484
686
|
var importCommand = {
|
|
485
687
|
command: "import <file>",
|
|
486
|
-
describe: "
|
|
688
|
+
describe: t("cli.import.desc"),
|
|
487
689
|
builder: (yargs2) => {
|
|
488
690
|
return yargs2.positional("file", {
|
|
489
|
-
describe: "
|
|
691
|
+
describe: t("cli.import.help"),
|
|
490
692
|
type: "string",
|
|
491
693
|
demandOption: true
|
|
492
694
|
});
|
|
493
695
|
},
|
|
494
696
|
handler: async (argv) => {
|
|
495
697
|
try {
|
|
698
|
+
const targetPath = resolve2(argv.file);
|
|
699
|
+
let stat;
|
|
700
|
+
try {
|
|
701
|
+
stat = statSync2(targetPath);
|
|
702
|
+
} catch (e) {
|
|
703
|
+
console.log(`${t("cli.import.not_found")} ${targetPath}`);
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
496
706
|
const bookService = new BookService();
|
|
497
|
-
|
|
498
|
-
|
|
707
|
+
if (stat.isDirectory()) {
|
|
708
|
+
console.log(`${t("cli.import.scan_dir")} ${targetPath}...`);
|
|
709
|
+
const files = scanDirectory(targetPath);
|
|
710
|
+
if (files.length === 0) {
|
|
711
|
+
console.log(`\u2717 ` + t("cli.import.unsupported"));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
console.log(t("cli.import.found_files"));
|
|
715
|
+
files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));
|
|
716
|
+
const rl = readline.createInterface({
|
|
717
|
+
input: process.stdin,
|
|
718
|
+
output: process.stdout
|
|
719
|
+
});
|
|
720
|
+
const answer = await rl.question(t("cli.import.confirm_batch", files.length) + " ");
|
|
721
|
+
rl.close();
|
|
722
|
+
if (answer.toLowerCase() === "y") {
|
|
723
|
+
for (const file of files) {
|
|
724
|
+
try {
|
|
725
|
+
const book = await bookService.importBook(file);
|
|
726
|
+
console.log(`${t("cli.import.success")} ${book.title} (${book.id})`);
|
|
727
|
+
} catch (err) {
|
|
728
|
+
console.log(`${t("cli.import.fail")} ${file} - ${err}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
} else {
|
|
732
|
+
console.log(t("cli.import.canceled"));
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
const book = await bookService.importBook(argv.file);
|
|
736
|
+
console.log(`${t("cli.import.success")} ${book.title} (${book.id})`);
|
|
737
|
+
}
|
|
499
738
|
} catch (error) {
|
|
500
|
-
|
|
739
|
+
console.log(`${t("cli.import.fail")} ${error}`);
|
|
501
740
|
process.exit(1);
|
|
502
741
|
}
|
|
503
742
|
}
|
|
@@ -659,24 +898,20 @@ function LibraryPage({ onNavigate }) {
|
|
|
659
898
|
}
|
|
660
899
|
}, { isActive: isRawModeSupported });
|
|
661
900
|
if (loading) {
|
|
662
|
-
return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "
|
|
901
|
+
return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: t("tui.lib.loading") }) });
|
|
663
902
|
}
|
|
664
903
|
if (books.length === 0) {
|
|
665
904
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", padding: 1, children: [
|
|
666
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: "
|
|
905
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("tui.lib.empty.title") }),
|
|
667
906
|
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
668
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "
|
|
669
|
-
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "
|
|
907
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("tui.lib.empty.desc") }),
|
|
908
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("common.quit") }) })
|
|
670
909
|
] })
|
|
671
910
|
] });
|
|
672
911
|
}
|
|
673
912
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", padding: 1, children: [
|
|
674
|
-
/* @__PURE__ */
|
|
675
|
-
|
|
676
|
-
books.length,
|
|
677
|
-
" \u672C)"
|
|
678
|
-
] }),
|
|
679
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2191\u2193/jk \u9009\u62E9 \xB7 Enter \u6253\u5F00 \xB7 d/x \u5220\u9664 \xB7 q \u9000\u51FA" }),
|
|
913
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("tui.lib.title", books.length) }),
|
|
914
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("tui.lib.tips") }),
|
|
680
915
|
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: books.map((book, index) => {
|
|
681
916
|
const isSelected = index === selectedIndex;
|
|
682
917
|
return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, justifyContent: "space-between", children: /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
@@ -753,8 +988,7 @@ function StatusBar({
|
|
|
753
988
|
] }) }),
|
|
754
989
|
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
755
990
|
remainingTime && /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
|
|
756
|
-
"
|
|
757
|
-
remainingTime,
|
|
991
|
+
t("tui.reader.status.remaining", remainingTime),
|
|
758
992
|
" "
|
|
759
993
|
] }),
|
|
760
994
|
/* @__PURE__ */ jsxs3(Text4, { color: "gray", children: [
|
|
@@ -766,7 +1000,7 @@ function StatusBar({
|
|
|
766
1000
|
displayPercent,
|
|
767
1001
|
"%"
|
|
768
1002
|
] }),
|
|
769
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "
|
|
1003
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("common.quit") })
|
|
770
1004
|
] })
|
|
771
1005
|
] });
|
|
772
1006
|
}
|
|
@@ -837,12 +1071,15 @@ function ChapterNav({
|
|
|
837
1071
|
children: [
|
|
838
1072
|
/* @__PURE__ */ jsxs4(Box5, { justifyContent: "space-between", marginBottom: 1, children: [
|
|
839
1073
|
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
840
|
-
/* @__PURE__ */
|
|
841
|
-
|
|
1074
|
+
/* @__PURE__ */ jsxs4(Text5, { bold: true, color: activeTab === "chapters" ? "green" : "gray", children: [
|
|
1075
|
+
t("tui.nav.tab.chapters"),
|
|
1076
|
+
" "
|
|
1077
|
+
] }),
|
|
1078
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: activeTab === "bookmarks" ? "green" : "gray", children: t("tui.nav.tab.bookmarks") })
|
|
842
1079
|
] }),
|
|
843
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "
|
|
1080
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("tui.nav.tips") })
|
|
844
1081
|
] }),
|
|
845
|
-
visibleItems.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "
|
|
1082
|
+
visibleItems.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("tui.nav.empty") }) : visibleItems.map((item, idx) => {
|
|
846
1083
|
const actualIndex = windowStart + idx;
|
|
847
1084
|
const isSelected = actualIndex === selectedIndex;
|
|
848
1085
|
return /* @__PURE__ */ jsxs4(
|
|
@@ -858,13 +1095,7 @@ function ChapterNav({
|
|
|
858
1095
|
item.id
|
|
859
1096
|
);
|
|
860
1097
|
}),
|
|
861
|
-
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, justifyContent: "flex-end", children: /* @__PURE__ */
|
|
862
|
-
"\u7B2C ",
|
|
863
|
-
Math.floor(selectedIndex / pageSize) + 1,
|
|
864
|
-
" / ",
|
|
865
|
-
Math.ceil(currentList.length / pageSize) || 1,
|
|
866
|
-
" \u9875"
|
|
867
|
-
] }) })
|
|
1098
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: t("tui.nav.page", Math.floor(selectedIndex / pageSize) + 1, Math.ceil(currentList.length / pageSize) || 1) }) })
|
|
868
1099
|
]
|
|
869
1100
|
}
|
|
870
1101
|
);
|
|
@@ -1260,7 +1491,7 @@ function ReaderContent({
|
|
|
1260
1491
|
}
|
|
1261
1492
|
const currentOffset = reader.getCurrentOffset();
|
|
1262
1493
|
bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);
|
|
1263
|
-
setToastMessage(
|
|
1494
|
+
setToastMessage(t("tui.reader.bookmark_add", markTitle));
|
|
1264
1495
|
setTimeout(() => setToastMessage(null), 2e3);
|
|
1265
1496
|
};
|
|
1266
1497
|
useEffect4(() => {
|
|
@@ -1358,11 +1589,14 @@ function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }) {
|
|
|
1358
1589
|
"\u2717 ",
|
|
1359
1590
|
error
|
|
1360
1591
|
] }),
|
|
1361
|
-
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "
|
|
1592
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: t("common.quit") })
|
|
1362
1593
|
] });
|
|
1363
1594
|
}
|
|
1364
1595
|
if (!book || !pages) {
|
|
1365
|
-
return /* @__PURE__ */ jsx6(Box6, { padding: 1, children: /* @__PURE__ */
|
|
1596
|
+
return /* @__PURE__ */ jsx6(Box6, { padding: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
|
|
1597
|
+
"\u{1F4D6} ",
|
|
1598
|
+
t("tui.reader.loading")
|
|
1599
|
+
] }) });
|
|
1366
1600
|
}
|
|
1367
1601
|
return /* @__PURE__ */ jsx6(
|
|
1368
1602
|
ReaderContent,
|
|
@@ -1421,19 +1655,19 @@ function renderApp(options = {}) {
|
|
|
1421
1655
|
// src/cli/commands/resume.ts
|
|
1422
1656
|
var resumeCommand = {
|
|
1423
1657
|
command: "resume",
|
|
1424
|
-
describe: "
|
|
1658
|
+
describe: t("cli.resume.desc"),
|
|
1425
1659
|
handler: async () => {
|
|
1426
1660
|
try {
|
|
1427
1661
|
const progressService = new ProgressService();
|
|
1428
1662
|
const lastProgress = progressService.getLastOpenedBook();
|
|
1429
1663
|
if (!lastProgress) {
|
|
1430
|
-
console.log("
|
|
1664
|
+
console.log(t("cli.resume.none"));
|
|
1431
1665
|
return;
|
|
1432
1666
|
}
|
|
1433
1667
|
const bookService = new BookService();
|
|
1434
1668
|
const book = bookService.findBook(lastProgress.book_id);
|
|
1435
1669
|
if (!book) {
|
|
1436
|
-
console.log("
|
|
1670
|
+
console.log(t("cli.resume.none"));
|
|
1437
1671
|
return;
|
|
1438
1672
|
}
|
|
1439
1673
|
logger.debug(`\u6062\u590D\u9605\u8BFB: ${book.title}, offset=${lastProgress.byte_offset}`);
|
|
@@ -1452,10 +1686,10 @@ var resumeCommand = {
|
|
|
1452
1686
|
// src/cli/commands/open.ts
|
|
1453
1687
|
var openCommand = {
|
|
1454
1688
|
command: "open <target>",
|
|
1455
|
-
describe: "
|
|
1689
|
+
describe: t("cli.open.desc"),
|
|
1456
1690
|
builder: (yargs2) => {
|
|
1457
1691
|
return yargs2.positional("target", {
|
|
1458
|
-
describe: "
|
|
1692
|
+
describe: t("cli.open.help"),
|
|
1459
1693
|
type: "string",
|
|
1460
1694
|
demandOption: true
|
|
1461
1695
|
});
|
|
@@ -1465,8 +1699,7 @@ var openCommand = {
|
|
|
1465
1699
|
const bookService = new BookService();
|
|
1466
1700
|
const book = bookService.findBook(argv.target);
|
|
1467
1701
|
if (!book) {
|
|
1468
|
-
console.log(
|
|
1469
|
-
console.log(" \u4F7F\u7528 novel library \u67E5\u770B\u4E66\u67B6\u4E2D\u7684\u6240\u6709\u4E66\u7C4D\u3002");
|
|
1702
|
+
console.log(`${t("cli.open.not_found")} ${argv.target}`);
|
|
1470
1703
|
process.exit(1);
|
|
1471
1704
|
}
|
|
1472
1705
|
const progressService = new ProgressService();
|
|
@@ -1488,11 +1721,11 @@ var openCommand = {
|
|
|
1488
1721
|
// src/cli/commands/library.ts
|
|
1489
1722
|
var libraryCommand = {
|
|
1490
1723
|
command: "library",
|
|
1491
|
-
describe: "
|
|
1724
|
+
describe: t("cli.library.desc"),
|
|
1492
1725
|
builder: (yargs2) => {
|
|
1493
1726
|
return yargs2.option("search", {
|
|
1494
1727
|
alias: "s",
|
|
1495
|
-
describe: "
|
|
1728
|
+
describe: t("cli.library.help"),
|
|
1496
1729
|
type: "string"
|
|
1497
1730
|
});
|
|
1498
1731
|
},
|
|
@@ -1502,11 +1735,10 @@ var libraryCommand = {
|
|
|
1502
1735
|
const bookService = new BookService();
|
|
1503
1736
|
const books = bookService.searchBooks(argv.search);
|
|
1504
1737
|
if (books.length === 0) {
|
|
1505
|
-
console.log(
|
|
1738
|
+
console.log(t("cli.library.search_none", argv.search));
|
|
1506
1739
|
return;
|
|
1507
1740
|
}
|
|
1508
|
-
console.log(
|
|
1509
|
-
`);
|
|
1741
|
+
console.log(t("cli.library.search_result", books.length));
|
|
1510
1742
|
books.forEach((book, index) => {
|
|
1511
1743
|
console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);
|
|
1512
1744
|
});
|
|
@@ -1523,10 +1755,10 @@ var libraryCommand = {
|
|
|
1523
1755
|
// src/cli/commands/remove.ts
|
|
1524
1756
|
var removeCommand = {
|
|
1525
1757
|
command: "remove <target>",
|
|
1526
|
-
describe: "
|
|
1758
|
+
describe: t("cli.remove.desc"),
|
|
1527
1759
|
builder: (yargs2) => {
|
|
1528
1760
|
return yargs2.positional("target", {
|
|
1529
|
-
describe: "
|
|
1761
|
+
describe: t("cli.remove.help"),
|
|
1530
1762
|
type: "string",
|
|
1531
1763
|
demandOption: true
|
|
1532
1764
|
});
|
|
@@ -1536,11 +1768,11 @@ var removeCommand = {
|
|
|
1536
1768
|
const bookService = new BookService();
|
|
1537
1769
|
const book = bookService.findBook(argv.target);
|
|
1538
1770
|
if (!book) {
|
|
1539
|
-
console.log(
|
|
1771
|
+
console.log(`${t("cli.remove.not_found")} ${argv.target}`);
|
|
1540
1772
|
process.exit(1);
|
|
1541
1773
|
}
|
|
1542
1774
|
bookService.deleteBook(book.id);
|
|
1543
|
-
console.log(
|
|
1775
|
+
console.log(`${t("cli.remove.success")} ${book.title}`);
|
|
1544
1776
|
} catch (error) {
|
|
1545
1777
|
logger.error("\u79FB\u9664\u4E66\u7C4D\u5931\u8D25:", error);
|
|
1546
1778
|
process.exit(1);
|
|
@@ -1548,9 +1780,72 @@ var removeCommand = {
|
|
|
1548
1780
|
}
|
|
1549
1781
|
};
|
|
1550
1782
|
|
|
1783
|
+
// src/cli/commands/lang.ts
|
|
1784
|
+
var langCommand = {
|
|
1785
|
+
command: "lang <target>",
|
|
1786
|
+
describe: t("cli.lang.desc"),
|
|
1787
|
+
builder: (yargs2) => {
|
|
1788
|
+
return yargs2.positional("target", {
|
|
1789
|
+
describe: t("cli.lang.help"),
|
|
1790
|
+
type: "string",
|
|
1791
|
+
choices: ["zh", "en"],
|
|
1792
|
+
demandOption: true
|
|
1793
|
+
});
|
|
1794
|
+
},
|
|
1795
|
+
handler: (argv) => {
|
|
1796
|
+
const lang = argv.target;
|
|
1797
|
+
if (lang === "zh" || lang === "en") {
|
|
1798
|
+
setConfig("language", lang);
|
|
1799
|
+
setLanguage(lang);
|
|
1800
|
+
console.log(t("cli.lang.success", lang));
|
|
1801
|
+
} else {
|
|
1802
|
+
console.log(t("cli.lang.unsupported", lang));
|
|
1803
|
+
process.exit(1);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
|
|
1808
|
+
// src/cli/commands/update.ts
|
|
1809
|
+
import { execSync } from "child_process";
|
|
1810
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1811
|
+
var updateCommand = {
|
|
1812
|
+
command: "update",
|
|
1813
|
+
describe: t("cli.update.desc"),
|
|
1814
|
+
handler: async () => {
|
|
1815
|
+
try {
|
|
1816
|
+
console.log(t("cli.update.checking"));
|
|
1817
|
+
let localVersion = "0.0.0";
|
|
1818
|
+
try {
|
|
1819
|
+
const pkgUrl = new URL("../../package.json", import.meta.url);
|
|
1820
|
+
const pkg = JSON.parse(readFileSync4(pkgUrl, "utf-8"));
|
|
1821
|
+
localVersion = pkg.version;
|
|
1822
|
+
} catch (err) {
|
|
1823
|
+
localVersion = "0.2.0";
|
|
1824
|
+
}
|
|
1825
|
+
const npmOutput = execSync("npm view readshell version", { encoding: "utf-8" });
|
|
1826
|
+
const latestVersion = npmOutput.trim();
|
|
1827
|
+
if (!latestVersion) {
|
|
1828
|
+
throw new Error("Could not fetch npm version");
|
|
1829
|
+
}
|
|
1830
|
+
if (latestVersion === localVersion) {
|
|
1831
|
+
console.log(t("cli.update.latest", localVersion));
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
console.log(t("cli.update.updating", latestVersion, localVersion));
|
|
1835
|
+
execSync("npm install -g readshell@latest", { stdio: "inherit" });
|
|
1836
|
+
console.log(t("cli.update.success"));
|
|
1837
|
+
} catch (error) {
|
|
1838
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1839
|
+
console.log(t("cli.update.fail", msg));
|
|
1840
|
+
logger.error("\u66F4\u65B0\u5931\u8D25:", error);
|
|
1841
|
+
process.exit(1);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1551
1846
|
// src/cli/parser.ts
|
|
1552
1847
|
function createParser() {
|
|
1553
|
-
return yargs(hideBin(process.argv)).scriptName("novel").usage("$0 <command> [options]").command(importCommand).command(resumeCommand).command(openCommand).command(libraryCommand).command(removeCommand).demandCommand(1, "\u8BF7\u6307\u5B9A\u4E00\u4E2A\u547D\u4EE4\u3002\u4F7F\u7528 --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002").strict().alias("h", "help").alias("v", "version").version("0.1.0").epilogue("ReadShell \u2014 \u7EC8\u7AEF\u5185\u4F4E\u6253\u65AD\u8F7B\u9605\u8BFB\u5DE5\u5177");
|
|
1848
|
+
return yargs(hideBin(process.argv)).scriptName("novel").usage("$0 <command> [options]").command(importCommand).command(resumeCommand).command(openCommand).command(libraryCommand).command(removeCommand).command(langCommand).command(updateCommand).demandCommand(1, "\u8BF7\u6307\u5B9A\u4E00\u4E2A\u547D\u4EE4\u3002\u4F7F\u7528 --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002").strict().alias("h", "help").alias("v", "version").version("0.1.0").epilogue("ReadShell \u2014 \u7EC8\u7AEF\u5185\u4F4E\u6253\u65AD\u8F7B\u9605\u8BFB\u5DE5\u5177");
|
|
1554
1849
|
}
|
|
1555
1850
|
|
|
1556
1851
|
// src/db/migrate.ts
|
|
@@ -1645,6 +1940,7 @@ function migrate(db2, fromVersion) {
|
|
|
1645
1940
|
async function main() {
|
|
1646
1941
|
try {
|
|
1647
1942
|
initDatabase();
|
|
1943
|
+
initI18n();
|
|
1648
1944
|
const parser = createParser();
|
|
1649
1945
|
await parser.parse();
|
|
1650
1946
|
} catch (error) {
|