wp-epub-gen 0.4.2 → 0.6.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 +72 -7
- package/build/index.cjs +317 -108
- package/build/index.d.ts +1 -1
- package/build/index.js +317 -108
- package/build/libs/utils.d.ts +5 -0
- package/build/templates.d.ts +1 -1
- package/build/types.d.ts +15 -0
- package/package.json +6 -2
- package/.prettierrc.json +0 -19
- package/.vscode/settings.json +0 -26
- package/build/parseContent.test.d.ts +0 -1
- package/eslint.config.js +0 -33
- package/templates/epub2/content.opf.ejs +0 -57
- package/templates/epub2/toc.xhtml.ejs +0 -26
- package/templates/epub3/content.opf.ejs +0 -83
- package/templates/epub3/toc.xhtml.ejs +0 -38
- package/templates/template.css +0 -46
- package/templates/toc.ncx.ejs +0 -45
- package/tsconfig.json +0 -40
- package/vite.config.ts +0 -117
- package/vitest.config.ts +0 -15
package/build/index.cjs
CHANGED
|
@@ -4,14 +4,14 @@ const os = require("os");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const url = require("url");
|
|
6
6
|
const uuid = require("uuid");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const util = require("util");
|
|
7
9
|
const cheerio = require("cheerio");
|
|
8
10
|
const diacritics = require("diacritics");
|
|
9
11
|
const uslug = require("uslug");
|
|
10
12
|
const archiver = require("archiver");
|
|
11
13
|
const fs$1 = require("fs-extra");
|
|
12
14
|
const request = require("superagent");
|
|
13
|
-
const fs = require("fs");
|
|
14
|
-
const util = require("util");
|
|
15
15
|
const ejs = require("ejs");
|
|
16
16
|
const entities = require("entities");
|
|
17
17
|
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
@@ -95,6 +95,63 @@ class GlobalLogger {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
const logger = GlobalLogger.getInstance();
|
|
98
|
+
util.promisify(fs.readFile);
|
|
99
|
+
const writeFile = util.promisify(fs.writeFile);
|
|
100
|
+
function normalizeConcurrency(value) {
|
|
101
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) return void 0;
|
|
102
|
+
return Math.floor(value);
|
|
103
|
+
}
|
|
104
|
+
function pLimit(concurrency) {
|
|
105
|
+
if (!Number.isFinite(concurrency) || concurrency < 1) concurrency = 1;
|
|
106
|
+
else concurrency = Math.floor(concurrency);
|
|
107
|
+
let active = 0;
|
|
108
|
+
const queue = [];
|
|
109
|
+
const next = () => {
|
|
110
|
+
active--;
|
|
111
|
+
if (queue.length) queue.shift()();
|
|
112
|
+
};
|
|
113
|
+
return (fn) => new Promise((resolve, reject) => {
|
|
114
|
+
const run = () => {
|
|
115
|
+
active++;
|
|
116
|
+
fn().then(
|
|
117
|
+
(v) => {
|
|
118
|
+
resolve(v);
|
|
119
|
+
next();
|
|
120
|
+
},
|
|
121
|
+
(e) => {
|
|
122
|
+
reject(e);
|
|
123
|
+
next();
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
if (active < concurrency) run();
|
|
128
|
+
else queue.push(run);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function emitProgress(configs, event) {
|
|
132
|
+
if (!configs?.onProgress) return;
|
|
133
|
+
try {
|
|
134
|
+
configs.onProgress(event);
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36";
|
|
139
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
140
|
+
async function fileIsStable(filename, max_wait = 3e4) {
|
|
141
|
+
const start_time = (/* @__PURE__ */ new Date()).getTime();
|
|
142
|
+
let last_size = fs.statSync(filename).size;
|
|
143
|
+
while ((/* @__PURE__ */ new Date()).getTime() - start_time <= max_wait) {
|
|
144
|
+
await wait(1e3);
|
|
145
|
+
const size = fs.statSync(filename).size;
|
|
146
|
+
if (size === last_size) return true;
|
|
147
|
+
last_size = size;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
function simpleMinifier(xhtml) {
|
|
152
|
+
xhtml = xhtml.replace(/\s+<\/a>/gi, "</a>").replace(/\n\s+</g, "\n<").replace(/\n+/g, "\n");
|
|
153
|
+
return xhtml;
|
|
154
|
+
}
|
|
98
155
|
function safeFineName(name) {
|
|
99
156
|
return name.replace(/[/\\?%*:|"<>\t\r\n]/g, "_");
|
|
100
157
|
}
|
|
@@ -362,7 +419,7 @@ function loadAndProcessHtml(data) {
|
|
|
362
419
|
throw new Error("Invalid HTML data: data cannot be empty or whitespace only");
|
|
363
420
|
}
|
|
364
421
|
try {
|
|
365
|
-
|
|
422
|
+
const $ = cheerio__namespace.load(trimmedData, {
|
|
366
423
|
xmlMode: true,
|
|
367
424
|
// @ts-ignore
|
|
368
425
|
decodeEntities: false,
|
|
@@ -372,17 +429,8 @@ function loadAndProcessHtml(data) {
|
|
|
372
429
|
});
|
|
373
430
|
const body = $("body");
|
|
374
431
|
if (body.length) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
$ = cheerio__namespace.load(html, {
|
|
378
|
-
xmlMode: true,
|
|
379
|
-
// @ts-ignore
|
|
380
|
-
decodeEntities: false,
|
|
381
|
-
lowerCaseTags: true,
|
|
382
|
-
recognizeSelfClosing: true,
|
|
383
|
-
lowerCaseAttributeNames: true
|
|
384
|
-
});
|
|
385
|
-
}
|
|
432
|
+
const bodyContents = body.contents();
|
|
433
|
+
$.root().empty().append(bodyContents);
|
|
386
434
|
}
|
|
387
435
|
return $;
|
|
388
436
|
} catch (error) {
|
|
@@ -431,6 +479,10 @@ function processHtmlElements($, allowedAttributes, allowedXhtml11Tags, epubConfi
|
|
|
431
479
|
});
|
|
432
480
|
}
|
|
433
481
|
function processImages($, chapter, epubConfigs) {
|
|
482
|
+
if (!epubConfigs._imagesByUrl) {
|
|
483
|
+
epubConfigs._imagesByUrl = new Map(epubConfigs.images.map((img) => [img.url, img]));
|
|
484
|
+
}
|
|
485
|
+
const imagesByUrl = epubConfigs._imagesByUrl;
|
|
434
486
|
$("img").each((index2, elem) => {
|
|
435
487
|
const url2 = $(elem).attr("src") || "";
|
|
436
488
|
if (!url2 || url2.trim().length === 0) {
|
|
@@ -446,7 +498,7 @@ function processImages($, chapter, epubConfigs) {
|
|
|
446
498
|
} catch (error) {
|
|
447
499
|
logger.error(`Error validating image URL "${trimmedUrl}": ${error}`);
|
|
448
500
|
}
|
|
449
|
-
const image =
|
|
501
|
+
const image = imagesByUrl.get(trimmedUrl);
|
|
450
502
|
let id;
|
|
451
503
|
let extension;
|
|
452
504
|
if (image) {
|
|
@@ -485,6 +537,7 @@ function processImages($, chapter, epubConfigs) {
|
|
|
485
537
|
const dir = chapter.dir || "";
|
|
486
538
|
const img = { id, url: trimmedUrl, dir, mediaType, extension };
|
|
487
539
|
epubConfigs.images.push(img);
|
|
540
|
+
imagesByUrl.set(trimmedUrl, img);
|
|
488
541
|
if (epubConfigs.verbose) {
|
|
489
542
|
logger.info(`Added image: ${trimmedUrl} -> images/${id}.${extension} (${mediaType})`);
|
|
490
543
|
}
|
|
@@ -549,25 +602,6 @@ function parseContent(content, index2, epubConfigs) {
|
|
|
549
602
|
processChildrenChapters(chapter, index2, epubConfigs);
|
|
550
603
|
return chapter;
|
|
551
604
|
}
|
|
552
|
-
util.promisify(fs.readFile);
|
|
553
|
-
const writeFile = util.promisify(fs.writeFile);
|
|
554
|
-
const USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36";
|
|
555
|
-
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
556
|
-
async function fileIsStable(filename, max_wait = 3e4) {
|
|
557
|
-
const start_time = (/* @__PURE__ */ new Date()).getTime();
|
|
558
|
-
let last_size = fs.statSync(filename).size;
|
|
559
|
-
while ((/* @__PURE__ */ new Date()).getTime() - start_time <= max_wait) {
|
|
560
|
-
await wait(1e3);
|
|
561
|
-
const size = fs.statSync(filename).size;
|
|
562
|
-
if (size === last_size) return true;
|
|
563
|
-
last_size = size;
|
|
564
|
-
}
|
|
565
|
-
return false;
|
|
566
|
-
}
|
|
567
|
-
function simpleMinifier(xhtml) {
|
|
568
|
-
xhtml = xhtml.replace(/\s+<\/a>/gi, "</a>").replace(/\n\s+</g, "\n<").replace(/\n+/g, "\n");
|
|
569
|
-
return xhtml;
|
|
570
|
-
}
|
|
571
605
|
const downloadImage = async (epubData, options) => {
|
|
572
606
|
const { url: url2 } = options;
|
|
573
607
|
const { log } = epubData;
|
|
@@ -576,7 +610,6 @@ const downloadImage = async (epubData, options) => {
|
|
|
576
610
|
return;
|
|
577
611
|
}
|
|
578
612
|
const image_dir = path.join(epub_dir, "OEBPS", "images");
|
|
579
|
-
fs$1.ensureDirSync(image_dir);
|
|
580
613
|
const filename = path.join(image_dir, options.id + "." + options.extension);
|
|
581
614
|
if (url2.startsWith("file://") || url2.startsWith("/")) {
|
|
582
615
|
let aux_path = url2.replace(/^file:\/\//i, "");
|
|
@@ -602,35 +635,75 @@ const downloadImage = async (epubData, options) => {
|
|
|
602
635
|
}
|
|
603
636
|
return;
|
|
604
637
|
}
|
|
638
|
+
const writeStream = fs$1.createWriteStream(filename);
|
|
605
639
|
let requestAction;
|
|
606
640
|
if (url2.startsWith("http")) {
|
|
607
641
|
requestAction = request.get(url2).set({ "User-Agent": USER_AGENT });
|
|
608
|
-
requestAction.pipe(fs$1.createWriteStream(filename));
|
|
609
642
|
} else {
|
|
610
643
|
log(`[Copy 2] '${url2}' to '${filename}'`);
|
|
611
644
|
requestAction = fs$1.createReadStream(path.join(options.dir || "", url2));
|
|
612
|
-
requestAction.pipe(fs$1.createWriteStream(filename));
|
|
613
645
|
}
|
|
614
|
-
|
|
646
|
+
requestAction.pipe(writeStream);
|
|
647
|
+
return new Promise((resolve) => {
|
|
648
|
+
let settled = false;
|
|
649
|
+
const finalize = () => {
|
|
650
|
+
if (settled) return;
|
|
651
|
+
settled = true;
|
|
652
|
+
resolve();
|
|
653
|
+
};
|
|
654
|
+
const cleanupFile = () => {
|
|
655
|
+
try {
|
|
656
|
+
fs$1.unlinkSync(filename);
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
const abortSource = () => {
|
|
661
|
+
if (typeof requestAction.abort === "function") requestAction.abort();
|
|
662
|
+
else if (typeof requestAction.destroy === "function") requestAction.destroy();
|
|
663
|
+
};
|
|
615
664
|
requestAction.on("error", (err) => {
|
|
616
665
|
log("[Download Error] Error while downloading: " + url2);
|
|
617
666
|
log(err);
|
|
618
|
-
|
|
619
|
-
|
|
667
|
+
writeStream.destroy();
|
|
668
|
+
cleanupFile();
|
|
669
|
+
finalize();
|
|
670
|
+
});
|
|
671
|
+
writeStream.on("error", (err) => {
|
|
672
|
+
log("[Write Error] Error while writing: " + filename);
|
|
673
|
+
log(err);
|
|
674
|
+
abortSource();
|
|
675
|
+
cleanupFile();
|
|
676
|
+
finalize();
|
|
620
677
|
});
|
|
621
|
-
|
|
678
|
+
writeStream.on("finish", () => {
|
|
622
679
|
log("[Download Success] " + url2);
|
|
623
|
-
|
|
680
|
+
finalize();
|
|
624
681
|
});
|
|
625
682
|
});
|
|
626
683
|
};
|
|
627
684
|
const downloadAllImages = async (epubData) => {
|
|
628
685
|
const { images } = epubData;
|
|
629
686
|
if (images.length === 0) return;
|
|
630
|
-
fs$1.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
687
|
+
await fs$1.ensureDir(path.join(epubData.dir, "OEBPS", "images"));
|
|
688
|
+
const concurrency = epubData._configs?.concurrency ?? 16;
|
|
689
|
+
const limit = pLimit(concurrency);
|
|
690
|
+
const total = images.length;
|
|
691
|
+
let done = 0;
|
|
692
|
+
emitProgress(epubData._configs, { phase: "downloadImage", current: 0, total });
|
|
693
|
+
await Promise.all(
|
|
694
|
+
images.map(
|
|
695
|
+
(image) => limit(async () => {
|
|
696
|
+
await downloadImage(epubData, image);
|
|
697
|
+
done++;
|
|
698
|
+
emitProgress(epubData._configs, {
|
|
699
|
+
phase: "downloadImage",
|
|
700
|
+
current: done,
|
|
701
|
+
total,
|
|
702
|
+
label: image.url
|
|
703
|
+
});
|
|
704
|
+
})
|
|
705
|
+
)
|
|
706
|
+
);
|
|
634
707
|
};
|
|
635
708
|
const epub2_content_opf_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
636
709
|
<package xmlns="http://www.idpf.org/2007/opf"
|
|
@@ -840,52 +913,131 @@ const epub3_toc_xhtml_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
840
913
|
</body>
|
|
841
914
|
</html>
|
|
842
915
|
`;
|
|
843
|
-
const template_css =
|
|
844
|
-
|
|
916
|
+
const template_css = `/* =========================
|
|
917
|
+
EPUB 3 Base Stylesheet
|
|
918
|
+
========================= */
|
|
919
|
+
|
|
920
|
+
/* 全局基础设置 */
|
|
921
|
+
html {
|
|
922
|
+
font-size: 100%;
|
|
845
923
|
}
|
|
846
924
|
|
|
847
|
-
|
|
848
|
-
margin
|
|
925
|
+
body {
|
|
926
|
+
margin: 0;
|
|
927
|
+
padding: 0;
|
|
928
|
+
line-height: 1.6;
|
|
929
|
+
font-family: serif;
|
|
930
|
+
color: #000;
|
|
931
|
+
background-color: transparent;
|
|
849
932
|
}
|
|
850
933
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
934
|
+
/* 段落 */
|
|
935
|
+
p {
|
|
936
|
+
margin: 0;
|
|
937
|
+
padding: 0;
|
|
938
|
+
/*text-indent: 2em;*/
|
|
939
|
+
orphans: 2;
|
|
940
|
+
widows: 2;
|
|
854
941
|
}
|
|
855
942
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
943
|
+
/* 标题 */
|
|
944
|
+
h1,
|
|
945
|
+
h2,
|
|
946
|
+
h3,
|
|
947
|
+
h4,
|
|
948
|
+
h5,
|
|
949
|
+
h6 {
|
|
950
|
+
font-weight: bold;
|
|
951
|
+
line-height: 1.3;
|
|
952
|
+
margin: 1.2em 0 0.6em 0;
|
|
953
|
+
text-indent: 0;
|
|
954
|
+
page-break-after: avoid;
|
|
859
955
|
}
|
|
860
956
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
font-size: 85%;
|
|
864
|
-
display: block;
|
|
957
|
+
h1 {
|
|
958
|
+
font-size: 1.6em;
|
|
865
959
|
}
|
|
866
960
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
border-bottom: 1px solid #dedede;
|
|
870
|
-
margin: 60px 10%;
|
|
961
|
+
h2 {
|
|
962
|
+
font-size: 1.4em;
|
|
871
963
|
}
|
|
872
964
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
965
|
+
h3 {
|
|
966
|
+
font-size: 1.2em;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/* 章节标题常用居中 */
|
|
970
|
+
.chapter-title {
|
|
971
|
+
text-align: center;
|
|
972
|
+
margin-top: 2em;
|
|
973
|
+
margin-bottom: 1.5em;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/* 首段不缩进(常用于章节开头) */
|
|
977
|
+
.no-indent {
|
|
978
|
+
text-indent: 0;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/* 强调 */
|
|
982
|
+
em {
|
|
983
|
+
font-style: italic;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
strong {
|
|
987
|
+
font-weight: bold;
|
|
876
988
|
}
|
|
877
989
|
|
|
878
|
-
|
|
990
|
+
/* 引用 */
|
|
991
|
+
blockquote {
|
|
992
|
+
margin: 1em 1.5em;
|
|
879
993
|
padding: 0;
|
|
880
|
-
margin-left: 2em;
|
|
881
994
|
}
|
|
882
995
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
996
|
+
/* 分隔符(如 ***) */
|
|
997
|
+
hr {
|
|
998
|
+
border: none;
|
|
999
|
+
text-align: center;
|
|
1000
|
+
margin: 1.5em 0;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/* 图片 */
|
|
1004
|
+
img {
|
|
1005
|
+
max-width: 100%;
|
|
1006
|
+
height: auto;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/* 图片居中 */
|
|
1010
|
+
.figure {
|
|
1011
|
+
text-align: center;
|
|
1012
|
+
margin: 1em 0;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/* 列表 */
|
|
1016
|
+
ul,
|
|
1017
|
+
ol {
|
|
1018
|
+
margin: 1em 0 1em 1.5em;
|
|
887
1019
|
padding: 0;
|
|
888
1020
|
}
|
|
1021
|
+
|
|
1022
|
+
li {
|
|
1023
|
+
margin: 0.3em 0;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/* 表格(谨慎使用) */
|
|
1027
|
+
table {
|
|
1028
|
+
border-collapse: collapse;
|
|
1029
|
+
margin: 1em 0;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
td,
|
|
1033
|
+
th {
|
|
1034
|
+
padding: 0.3em 0.5em;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/* 分页控制(EPUB 安全用法) */
|
|
1038
|
+
.page-break {
|
|
1039
|
+
page-break-before: always;
|
|
1040
|
+
}
|
|
889
1041
|
`;
|
|
890
1042
|
const toc_ncx_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
891
1043
|
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
|
|
@@ -935,28 +1087,38 @@ const toc_ncx_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
935
1087
|
`;
|
|
936
1088
|
const generateTempFile = async (epubData) => {
|
|
937
1089
|
const { log } = epubData;
|
|
938
|
-
const
|
|
939
|
-
await fs$1.ensureDir(
|
|
940
|
-
|
|
941
|
-
|
|
1090
|
+
const oebpsDir = path.join(epubData.dir, "OEBPS");
|
|
1091
|
+
await fs$1.ensureDir(oebpsDir);
|
|
1092
|
+
const customCss = epubData.css || "";
|
|
1093
|
+
if (!epubData.noDefaultCss) {
|
|
1094
|
+
epubData.css = template_css;
|
|
1095
|
+
if (customCss) {
|
|
1096
|
+
epubData.css += "\n\n" + customCss;
|
|
1097
|
+
}
|
|
1098
|
+
} else {
|
|
1099
|
+
epubData.css = customCss;
|
|
1100
|
+
}
|
|
1101
|
+
await writeFile(path.join(oebpsDir, "style.css"), epubData.css, "utf-8");
|
|
942
1102
|
if (epubData.fonts?.length) {
|
|
943
|
-
const fonts_dir = path.join(
|
|
1103
|
+
const fonts_dir = path.join(oebpsDir, "fonts");
|
|
944
1104
|
await fs$1.ensureDir(fonts_dir);
|
|
945
|
-
|
|
1105
|
+
const fontFilenames = [];
|
|
1106
|
+
for (const font of epubData.fonts) {
|
|
946
1107
|
const filename = path.basename(font);
|
|
947
1108
|
if (!fs$1.existsSync(font)) {
|
|
948
1109
|
log(`Custom font not found at '${font}'.`);
|
|
949
1110
|
} else {
|
|
950
|
-
fs$1.
|
|
1111
|
+
await fs$1.copy(font, path.join(fonts_dir, filename));
|
|
951
1112
|
}
|
|
952
|
-
|
|
953
|
-
}
|
|
1113
|
+
fontFilenames.push(filename);
|
|
1114
|
+
}
|
|
1115
|
+
epubData.fonts = fontFilenames;
|
|
954
1116
|
}
|
|
955
1117
|
const isAppendTitle = (global_append, local_append) => {
|
|
956
1118
|
if (typeof local_append === "boolean") return local_append;
|
|
957
1119
|
return !!global_append;
|
|
958
1120
|
};
|
|
959
|
-
const
|
|
1121
|
+
const renderChapterHtml = (content) => {
|
|
960
1122
|
const title = entities__namespace.encodeXML(content.title || "");
|
|
961
1123
|
let html = `${epubData.docHeader}
|
|
962
1124
|
<head>
|
|
@@ -973,16 +1135,39 @@ const generateTempFile = async (epubData) => {
|
|
|
973
1135
|
html += content.title && content.url ? `<p class="epub-link"><a href="${content.url}">${content.url}</a></p>` : "";
|
|
974
1136
|
html += `${content.data}`;
|
|
975
1137
|
html += "\n</body>\n</html>";
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1138
|
+
return html;
|
|
1139
|
+
};
|
|
1140
|
+
const flattenChapters = (nodes, out = []) => {
|
|
1141
|
+
for (const c of nodes) {
|
|
1142
|
+
out.push(c);
|
|
1143
|
+
if (Array.isArray(c.children) && c.children.length) flattenChapters(c.children, out);
|
|
980
1144
|
}
|
|
1145
|
+
return out;
|
|
981
1146
|
};
|
|
982
|
-
epubData.content
|
|
1147
|
+
const allChapters = flattenChapters(epubData.content);
|
|
1148
|
+
const totalChapters = allChapters.length;
|
|
1149
|
+
const uniqueDirs = Array.from(new Set(allChapters.map((c) => path.dirname(c.filePath))));
|
|
1150
|
+
for (const d of uniqueDirs) await fs$1.ensureDir(d);
|
|
1151
|
+
const concurrency = epubData._configs?.concurrency ?? 16;
|
|
1152
|
+
const limit = pLimit(concurrency);
|
|
1153
|
+
let written = 0;
|
|
1154
|
+
await Promise.all(
|
|
1155
|
+
allChapters.map(
|
|
1156
|
+
(content) => limit(async () => {
|
|
1157
|
+
await fs$1.writeFile(content.filePath, renderChapterHtml(content), "utf-8");
|
|
1158
|
+
written++;
|
|
1159
|
+
emitProgress(epubData._configs, {
|
|
1160
|
+
phase: "writeChapters",
|
|
1161
|
+
current: written,
|
|
1162
|
+
total: totalChapters,
|
|
1163
|
+
label: content.title
|
|
1164
|
+
});
|
|
1165
|
+
})
|
|
1166
|
+
)
|
|
1167
|
+
);
|
|
983
1168
|
const metainf_dir = path.join(epubData.dir, "META-INF");
|
|
984
|
-
fs$1.
|
|
985
|
-
fs$1.
|
|
1169
|
+
await fs$1.ensureDir(metainf_dir);
|
|
1170
|
+
await fs$1.writeFile(
|
|
986
1171
|
path.join(metainf_dir, "container.xml"),
|
|
987
1172
|
`<?xml version="1.0" encoding="UTF-8" ?>
|
|
988
1173
|
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
|
@@ -992,7 +1177,7 @@ const generateTempFile = async (epubData) => {
|
|
|
992
1177
|
);
|
|
993
1178
|
if (epubData.version === 2) {
|
|
994
1179
|
const fn = path.join(metainf_dir, "com.apple.ibooks.display-options.xml");
|
|
995
|
-
fs$1.
|
|
1180
|
+
await fs$1.writeFile(
|
|
996
1181
|
fn,
|
|
997
1182
|
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
998
1183
|
<display_options>
|
|
@@ -1026,17 +1211,25 @@ const generateTempFile = async (epubData) => {
|
|
|
1026
1211
|
htmlTocTemplate = epubData.version === 2 ? epub2_toc_xhtml_ejs : epub3_toc_xhtml_ejs;
|
|
1027
1212
|
}
|
|
1028
1213
|
const toc_depth = 1;
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1214
|
+
emitProgress(epubData._configs, { phase: "buildToc", current: 0, total: 3 });
|
|
1215
|
+
await Promise.all([
|
|
1216
|
+
fs$1.writeFile(
|
|
1217
|
+
path.join(oebpsDir, "content.opf"),
|
|
1218
|
+
ejs.render(opfTemplate, epubData),
|
|
1219
|
+
"utf-8"
|
|
1220
|
+
),
|
|
1221
|
+
fs$1.writeFile(
|
|
1222
|
+
path.join(oebpsDir, "toc.ncx"),
|
|
1223
|
+
ejs.render(ncxTocTemplate, { ...epubData, toc_depth }),
|
|
1224
|
+
"utf-8"
|
|
1225
|
+
),
|
|
1226
|
+
fs$1.writeFile(
|
|
1227
|
+
path.join(oebpsDir, "toc.xhtml"),
|
|
1228
|
+
simpleMinifier(ejs.render(htmlTocTemplate, epubData)),
|
|
1229
|
+
"utf-8"
|
|
1230
|
+
)
|
|
1231
|
+
]);
|
|
1232
|
+
emitProgress(epubData._configs, { phase: "buildToc", current: 3, total: 3 });
|
|
1040
1233
|
};
|
|
1041
1234
|
async function makeCover(data) {
|
|
1042
1235
|
const { cover, _coverExtension, log } = data;
|
|
@@ -1094,6 +1287,7 @@ async function genEpub(epubData) {
|
|
|
1094
1287
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1095
1288
|
const outputStream = fs$1.createWriteStream(epubData.output);
|
|
1096
1289
|
log("Zipping temp dir to " + output);
|
|
1290
|
+
emitProgress(epubData._configs, { phase: "zip", current: 0, total: 1 });
|
|
1097
1291
|
return new Promise((resolve, reject) => {
|
|
1098
1292
|
archive.on("end", async () => {
|
|
1099
1293
|
log("Done zipping, clearing temp dir...");
|
|
@@ -1103,6 +1297,7 @@ async function genEpub(epubData) {
|
|
|
1103
1297
|
}
|
|
1104
1298
|
try {
|
|
1105
1299
|
fs$1.removeSync(dir);
|
|
1300
|
+
emitProgress(epubData._configs, { phase: "zip", current: 1, total: 1 });
|
|
1106
1301
|
resolve();
|
|
1107
1302
|
} catch (e) {
|
|
1108
1303
|
log("[Error] " + e.message);
|
|
@@ -1160,7 +1355,7 @@ function check(options) {
|
|
|
1160
1355
|
}
|
|
1161
1356
|
return result(true, void 0, options);
|
|
1162
1357
|
}
|
|
1163
|
-
function parseOptions(options) {
|
|
1358
|
+
async function parseOptions(options, configs) {
|
|
1164
1359
|
const tmpDir = options.tmpDir || os.tmpdir();
|
|
1165
1360
|
const id = uuid.v4();
|
|
1166
1361
|
const data = {
|
|
@@ -1185,17 +1380,18 @@ function parseOptions(options) {
|
|
|
1185
1380
|
docHeader: "",
|
|
1186
1381
|
images: [],
|
|
1187
1382
|
content: [],
|
|
1188
|
-
log: (msg) => options.verbose && logger.log(msg)
|
|
1383
|
+
log: (msg) => options.verbose && logger.log(msg),
|
|
1384
|
+
_configs: configs
|
|
1189
1385
|
};
|
|
1190
1386
|
if (data.version === 2) {
|
|
1191
1387
|
data.docHeader = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1192
1388
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
1193
|
-
<html xmlns="http://www.w3.org/1999/xhtml" lang="
|
|
1389
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${data.lang}" lang="${data.lang}">
|
|
1194
1390
|
`;
|
|
1195
1391
|
} else {
|
|
1196
1392
|
data.docHeader = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1197
1393
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
1198
|
-
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="
|
|
1394
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="${data.lang}" lang="${data.lang}">
|
|
1199
1395
|
`;
|
|
1200
1396
|
}
|
|
1201
1397
|
if (Array.isArray(data.author) && data.author.length === 0) {
|
|
@@ -1204,7 +1400,17 @@ function parseOptions(options) {
|
|
|
1204
1400
|
if (typeof data.author === "string") {
|
|
1205
1401
|
data.author = [data.author];
|
|
1206
1402
|
}
|
|
1207
|
-
|
|
1403
|
+
const total = options.content.length;
|
|
1404
|
+
const parsed = [];
|
|
1405
|
+
for (let i = 0; i < total; i++) {
|
|
1406
|
+
parsed.push(parseContent(options.content[i], i, data));
|
|
1407
|
+
if ((i & 31) === 31) {
|
|
1408
|
+
await new Promise((r) => setImmediate(r));
|
|
1409
|
+
emitProgress(configs, { phase: "parseContent", current: i + 1, total });
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
emitProgress(configs, { phase: "parseContent", current: total, total });
|
|
1413
|
+
data.content = parsed;
|
|
1208
1414
|
if (data.cover) {
|
|
1209
1415
|
data._coverMediaType = mime.getType(data.cover) || "";
|
|
1210
1416
|
data._coverExtension = mime.getExtension(data._coverMediaType) || "";
|
|
@@ -1212,6 +1418,9 @@ function parseOptions(options) {
|
|
|
1212
1418
|
return data;
|
|
1213
1419
|
}
|
|
1214
1420
|
async function epubGen(options, configs) {
|
|
1421
|
+
if (configs) {
|
|
1422
|
+
configs = { ...configs, concurrency: normalizeConcurrency(configs.concurrency) };
|
|
1423
|
+
}
|
|
1215
1424
|
if (configs?.logger) {
|
|
1216
1425
|
logger.setLogger(configs.logger);
|
|
1217
1426
|
}
|
|
@@ -1225,7 +1434,7 @@ async function epubGen(options, configs) {
|
|
|
1225
1434
|
}
|
|
1226
1435
|
let t;
|
|
1227
1436
|
try {
|
|
1228
|
-
const data = parseOptions(options);
|
|
1437
|
+
const data = await parseOptions(options, configs);
|
|
1229
1438
|
const timeoutSeconds = data.timeoutSeconds || 0;
|
|
1230
1439
|
if (timeoutSeconds > 0) {
|
|
1231
1440
|
if (verbose) logger.log(`TIMEOUT: ${timeoutSeconds}s`);
|
package/build/index.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ declare const _default: {
|
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
14
|
export default _default;
|
|
15
|
-
export type { IChapter, IChapterData, IEpubData, IEpubGenOptions, IEpubImage, IOut } from './types';
|
|
15
|
+
export type { IChapter, IChapterData, IEpubData, IEpubGenOptions, IEpubImage, IGenConfigs, ILogger, IOut, IProgressEvent, ProgressPhase, } from './types';
|