wp-epub-gen 0.5.0 → 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 +196 -74
- package/build/index.d.ts +1 -1
- package/build/index.js +196 -74
- package/build/libs/utils.d.ts +5 -0
- package/build/types.d.ts +14 -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 -125
- 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/README.md
CHANGED
|
@@ -122,13 +122,13 @@ epubGen(options).then(result => {
|
|
|
122
122
|
|
|
123
123
|
## API 参考
|
|
124
124
|
|
|
125
|
-
### epubGen(options,
|
|
125
|
+
### epubGen(options, configs?)
|
|
126
126
|
|
|
127
127
|
主要的 EPUB 生成函数。
|
|
128
128
|
|
|
129
129
|
**参数:**
|
|
130
|
-
- `options: IEpubGenOptions` -
|
|
131
|
-
- `
|
|
130
|
+
- `options: IEpubGenOptions` - 配置选项对象(标题、作者、封面、章节内容等)
|
|
131
|
+
- `configs?: IGenConfigs` - 可选的运行时回调(logger、onProgress、concurrency),见下文 [IGenConfigs](#igenconfigs-运行时回调)
|
|
132
132
|
|
|
133
133
|
**返回值:**
|
|
134
134
|
- `Promise<IOut>` - 包含生成结果的 Promise
|
|
@@ -233,18 +233,83 @@ interface IOut {
|
|
|
233
233
|
}
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
+
### IGenConfigs 运行时回调
|
|
237
|
+
|
|
238
|
+
`epubGen` 的第二个参数,用于注入宿主侧的回调和并发配置。所有字段都是可选的。
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
interface IGenConfigs {
|
|
242
|
+
logger?: ILogger;
|
|
243
|
+
onProgress?: (e: IProgressEvent) => void;
|
|
244
|
+
concurrency?: number;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### `logger?: ILogger`
|
|
249
|
+
|
|
250
|
+
注入自定义日志记录器(替代默认 `console`)。常用于 Electron 主进程把日志转发到渲染进程。
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
interface ILogger {
|
|
254
|
+
log: (msg: any) => void;
|
|
255
|
+
info: (msg: any) => void;
|
|
256
|
+
warn: (msg: any) => void;
|
|
257
|
+
error: (msg: any) => void;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### `onProgress?: (e: IProgressEvent) => void`
|
|
262
|
+
|
|
263
|
+
进度回调。生成过程会在 5 个阶段中分别推送事件,宿主可据此渲染进度条或转发 IPC 给 UI。
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
type ProgressPhase =
|
|
267
|
+
| 'parseContent' // 解析章节 HTML
|
|
268
|
+
| 'writeChapters' // 写章节临时文件
|
|
269
|
+
| 'buildToc' // 渲染 OPF / NCX / TOC
|
|
270
|
+
| 'downloadImage' // 下载图片(仅当存在图片时)
|
|
271
|
+
| 'zip' // 打包 .epub
|
|
272
|
+
|
|
273
|
+
interface IProgressEvent {
|
|
274
|
+
phase: ProgressPhase;
|
|
275
|
+
current: number; // 已完成数量
|
|
276
|
+
total: number; // 总数量
|
|
277
|
+
label?: string; // 当前条目标签(章节标题 / 图片 URL)
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
最小用法:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
await epubGen(options, {
|
|
285
|
+
onProgress: (e) => {
|
|
286
|
+
console.log(`[${e.phase}] ${e.current}/${e.total}`);
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
回调中抛出的异常会被库静默吞掉,不会中断 EPUB 生成。
|
|
292
|
+
|
|
293
|
+
#### `concurrency?: number`
|
|
294
|
+
|
|
295
|
+
写章节文件和下载图片所共用的并发上限。默认 `16`,机械硬盘或带宽受限场景可调小(如 `4`)。非有限正整数(NaN、负数等)会被自动归一化为默认值。
|
|
296
|
+
|
|
236
297
|
## 导出的类型
|
|
237
298
|
|
|
238
299
|
库导出了所有 TypeScript 类型定义:
|
|
239
300
|
|
|
240
301
|
```typescript
|
|
241
|
-
import type {
|
|
242
|
-
IEpubGenOptions,
|
|
243
|
-
IChapter,
|
|
302
|
+
import type {
|
|
303
|
+
IEpubGenOptions,
|
|
304
|
+
IChapter,
|
|
244
305
|
IChapterData,
|
|
245
306
|
IEpubData,
|
|
246
307
|
IEpubImage,
|
|
247
|
-
|
|
308
|
+
IGenConfigs,
|
|
309
|
+
ILogger,
|
|
310
|
+
IProgressEvent,
|
|
311
|
+
ProgressPhase,
|
|
312
|
+
IOut,
|
|
248
313
|
} from 'wp-epub-gen';
|
|
249
314
|
```
|
|
250
315
|
|
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();
|
|
620
670
|
});
|
|
621
|
-
|
|
671
|
+
writeStream.on("error", (err) => {
|
|
672
|
+
log("[Write Error] Error while writing: " + filename);
|
|
673
|
+
log(err);
|
|
674
|
+
abortSource();
|
|
675
|
+
cleanupFile();
|
|
676
|
+
finalize();
|
|
677
|
+
});
|
|
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"
|
|
@@ -1029,21 +1102,23 @@ const generateTempFile = async (epubData) => {
|
|
|
1029
1102
|
if (epubData.fonts?.length) {
|
|
1030
1103
|
const fonts_dir = path.join(oebpsDir, "fonts");
|
|
1031
1104
|
await fs$1.ensureDir(fonts_dir);
|
|
1032
|
-
|
|
1105
|
+
const fontFilenames = [];
|
|
1106
|
+
for (const font of epubData.fonts) {
|
|
1033
1107
|
const filename = path.basename(font);
|
|
1034
1108
|
if (!fs$1.existsSync(font)) {
|
|
1035
1109
|
log(`Custom font not found at '${font}'.`);
|
|
1036
1110
|
} else {
|
|
1037
|
-
fs$1.
|
|
1111
|
+
await fs$1.copy(font, path.join(fonts_dir, filename));
|
|
1038
1112
|
}
|
|
1039
|
-
|
|
1040
|
-
}
|
|
1113
|
+
fontFilenames.push(filename);
|
|
1114
|
+
}
|
|
1115
|
+
epubData.fonts = fontFilenames;
|
|
1041
1116
|
}
|
|
1042
1117
|
const isAppendTitle = (global_append, local_append) => {
|
|
1043
1118
|
if (typeof local_append === "boolean") return local_append;
|
|
1044
1119
|
return !!global_append;
|
|
1045
1120
|
};
|
|
1046
|
-
const
|
|
1121
|
+
const renderChapterHtml = (content) => {
|
|
1047
1122
|
const title = entities__namespace.encodeXML(content.title || "");
|
|
1048
1123
|
let html = `${epubData.docHeader}
|
|
1049
1124
|
<head>
|
|
@@ -1060,16 +1135,39 @@ const generateTempFile = async (epubData) => {
|
|
|
1060
1135
|
html += content.title && content.url ? `<p class="epub-link"><a href="${content.url}">${content.url}</a></p>` : "";
|
|
1061
1136
|
html += `${content.data}`;
|
|
1062
1137
|
html += "\n</body>\n</html>";
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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);
|
|
1067
1144
|
}
|
|
1145
|
+
return out;
|
|
1068
1146
|
};
|
|
1069
|
-
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
|
+
);
|
|
1070
1168
|
const metainf_dir = path.join(epubData.dir, "META-INF");
|
|
1071
|
-
fs$1.
|
|
1072
|
-
fs$1.
|
|
1169
|
+
await fs$1.ensureDir(metainf_dir);
|
|
1170
|
+
await fs$1.writeFile(
|
|
1073
1171
|
path.join(metainf_dir, "container.xml"),
|
|
1074
1172
|
`<?xml version="1.0" encoding="UTF-8" ?>
|
|
1075
1173
|
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
|
@@ -1079,7 +1177,7 @@ const generateTempFile = async (epubData) => {
|
|
|
1079
1177
|
);
|
|
1080
1178
|
if (epubData.version === 2) {
|
|
1081
1179
|
const fn = path.join(metainf_dir, "com.apple.ibooks.display-options.xml");
|
|
1082
|
-
fs$1.
|
|
1180
|
+
await fs$1.writeFile(
|
|
1083
1181
|
fn,
|
|
1084
1182
|
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1085
1183
|
<display_options>
|
|
@@ -1113,17 +1211,25 @@ const generateTempFile = async (epubData) => {
|
|
|
1113
1211
|
htmlTocTemplate = epubData.version === 2 ? epub2_toc_xhtml_ejs : epub3_toc_xhtml_ejs;
|
|
1114
1212
|
}
|
|
1115
1213
|
const toc_depth = 1;
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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 });
|
|
1127
1233
|
};
|
|
1128
1234
|
async function makeCover(data) {
|
|
1129
1235
|
const { cover, _coverExtension, log } = data;
|
|
@@ -1181,6 +1287,7 @@ async function genEpub(epubData) {
|
|
|
1181
1287
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1182
1288
|
const outputStream = fs$1.createWriteStream(epubData.output);
|
|
1183
1289
|
log("Zipping temp dir to " + output);
|
|
1290
|
+
emitProgress(epubData._configs, { phase: "zip", current: 0, total: 1 });
|
|
1184
1291
|
return new Promise((resolve, reject) => {
|
|
1185
1292
|
archive.on("end", async () => {
|
|
1186
1293
|
log("Done zipping, clearing temp dir...");
|
|
@@ -1190,6 +1297,7 @@ async function genEpub(epubData) {
|
|
|
1190
1297
|
}
|
|
1191
1298
|
try {
|
|
1192
1299
|
fs$1.removeSync(dir);
|
|
1300
|
+
emitProgress(epubData._configs, { phase: "zip", current: 1, total: 1 });
|
|
1193
1301
|
resolve();
|
|
1194
1302
|
} catch (e) {
|
|
1195
1303
|
log("[Error] " + e.message);
|
|
@@ -1247,7 +1355,7 @@ function check(options) {
|
|
|
1247
1355
|
}
|
|
1248
1356
|
return result(true, void 0, options);
|
|
1249
1357
|
}
|
|
1250
|
-
function parseOptions(options) {
|
|
1358
|
+
async function parseOptions(options, configs) {
|
|
1251
1359
|
const tmpDir = options.tmpDir || os.tmpdir();
|
|
1252
1360
|
const id = uuid.v4();
|
|
1253
1361
|
const data = {
|
|
@@ -1272,7 +1380,8 @@ function parseOptions(options) {
|
|
|
1272
1380
|
docHeader: "",
|
|
1273
1381
|
images: [],
|
|
1274
1382
|
content: [],
|
|
1275
|
-
log: (msg) => options.verbose && logger.log(msg)
|
|
1383
|
+
log: (msg) => options.verbose && logger.log(msg),
|
|
1384
|
+
_configs: configs
|
|
1276
1385
|
};
|
|
1277
1386
|
if (data.version === 2) {
|
|
1278
1387
|
data.docHeader = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -1291,7 +1400,17 @@ function parseOptions(options) {
|
|
|
1291
1400
|
if (typeof data.author === "string") {
|
|
1292
1401
|
data.author = [data.author];
|
|
1293
1402
|
}
|
|
1294
|
-
|
|
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;
|
|
1295
1414
|
if (data.cover) {
|
|
1296
1415
|
data._coverMediaType = mime.getType(data.cover) || "";
|
|
1297
1416
|
data._coverExtension = mime.getExtension(data._coverMediaType) || "";
|
|
@@ -1299,6 +1418,9 @@ function parseOptions(options) {
|
|
|
1299
1418
|
return data;
|
|
1300
1419
|
}
|
|
1301
1420
|
async function epubGen(options, configs) {
|
|
1421
|
+
if (configs) {
|
|
1422
|
+
configs = { ...configs, concurrency: normalizeConcurrency(configs.concurrency) };
|
|
1423
|
+
}
|
|
1302
1424
|
if (configs?.logger) {
|
|
1303
1425
|
logger.setLogger(configs.logger);
|
|
1304
1426
|
}
|
|
@@ -1312,7 +1434,7 @@ async function epubGen(options, configs) {
|
|
|
1312
1434
|
}
|
|
1313
1435
|
let t;
|
|
1314
1436
|
try {
|
|
1315
|
-
const data = parseOptions(options);
|
|
1437
|
+
const data = await parseOptions(options, configs);
|
|
1316
1438
|
const timeoutSeconds = data.timeoutSeconds || 0;
|
|
1317
1439
|
if (timeoutSeconds > 0) {
|
|
1318
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';
|