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/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
- let $ = cheerio__namespace.load(trimmedData, {
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 html = body.html();
376
- if (html) {
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 = epubConfigs.images.find((el) => el.url === trimmedUrl);
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
- return new Promise((resolve, _reject) => {
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
- fs$1.unlinkSync(filename);
619
- resolve();
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
- requestAction.on("end", () => {
678
+ writeStream.on("finish", () => {
622
679
  log("[Download Success] " + url2);
623
- resolve();
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.ensureDirSync(path.join(epubData.dir, "OEBPS", "images"));
631
- for (const image of images) {
632
- await downloadImage(epubData, image);
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 = `.epub-author {
844
- color: #555;
916
+ const template_css = `/* =========================
917
+ EPUB 3 Base Stylesheet
918
+ ========================= */
919
+
920
+ /* 全局基础设置 */
921
+ html {
922
+ font-size: 100%;
845
923
  }
846
924
 
847
- .epub-link {
848
- margin-bottom: 30px;
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
- .epub-link a {
852
- color: #666;
853
- font-size: 90%;
934
+ /* 段落 */
935
+ p {
936
+ margin: 0;
937
+ padding: 0;
938
+ /*text-indent: 2em;*/
939
+ orphans: 2;
940
+ widows: 2;
854
941
  }
855
942
 
856
- .toc-author {
857
- font-size: 90%;
858
- color: #555;
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
- .toc-link {
862
- color: #999;
863
- font-size: 85%;
864
- display: block;
957
+ h1 {
958
+ font-size: 1.6em;
865
959
  }
866
960
 
867
- hr {
868
- border: 0;
869
- border-bottom: 1px solid #dedede;
870
- margin: 60px 10%;
961
+ h2 {
962
+ font-size: 1.4em;
871
963
  }
872
964
 
873
- .TOC > ol {
874
- margin: 0;
875
- padding: 0;
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
- .TOC > ol ol {
990
+ /* 引用 */
991
+ blockquote {
992
+ margin: 1em 1.5em;
879
993
  padding: 0;
880
- margin-left: 2em;
881
994
  }
882
995
 
883
- .TOC li {
884
- font-size: 16px;
885
- list-style: none;
886
- margin: 0 auto;
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 oebps_dir = path.join(epubData.dir, "OEBPS");
939
- await fs$1.ensureDir(oebps_dir);
940
- epubData.css = epubData.css || template_css;
941
- await writeFile(path.join(oebps_dir, "style.css"), epubData.css, "utf-8");
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(oebps_dir, "fonts");
1103
+ const fonts_dir = path.join(oebpsDir, "fonts");
944
1104
  await fs$1.ensureDir(fonts_dir);
945
- epubData.fonts = epubData.fonts.map((font) => {
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.copySync(font, path.join(fonts_dir, filename));
1111
+ await fs$1.copy(font, path.join(fonts_dir, filename));
951
1112
  }
952
- return filename;
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 saveContentToFile = (content) => {
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
- fs$1.ensureDirSync(path.dirname(content.filePath));
977
- fs$1.writeFileSync(content.filePath, html, "utf-8");
978
- if (Array.isArray(content.children)) {
979
- content.children.map(saveContentToFile);
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.map(saveContentToFile);
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.ensureDirSync(metainf_dir);
985
- fs$1.writeFileSync(
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.writeFileSync(
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
- fs$1.writeFileSync(path.join(oebps_dir, "content.opf"), ejs.render(opfTemplate, epubData), "utf-8");
1030
- fs$1.writeFileSync(
1031
- path.join(oebps_dir, "toc.ncx"),
1032
- ejs.render(ncxTocTemplate, { ...epubData, toc_depth }),
1033
- "utf-8"
1034
- );
1035
- fs$1.writeFileSync(
1036
- path.join(oebps_dir, "toc.xhtml"),
1037
- simpleMinifier(ejs.render(htmlTocTemplate, epubData)),
1038
- "utf-8"
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="#{self.options.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="#{self.options.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
- data.content = options.content.map((content, index2) => parseContent(content, index2, data));
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';