wp-epub-gen 0.2.3 → 0.4.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/.vscode/settings.json +26 -0
- package/README.md +261 -75
- package/build/downloadImage.d.ts +0 -5
- package/build/errors.d.ts +0 -1
- package/build/generateTempFile.d.ts +0 -5
- package/build/index.cjs +1279 -0
- package/build/index.d.ts +3 -7
- package/build/index.js +927 -425
- package/build/libs/safeFineName.d.ts +0 -1
- package/build/libs/utils.d.ts +1 -7
- package/build/logger.d.ts +40 -0
- package/build/makeCover.d.ts +0 -5
- package/build/parseContent.d.ts +0 -5
- package/build/parseContent.test.d.ts +1 -0
- package/build/render.d.ts +0 -5
- package/build/templates.d.ts +11 -0
- package/build/types.d.ts +9 -1
- package/eslint.config.js +33 -0
- package/package.json +37 -35
- package/templates/epub3/toc.xhtml.ejs +1 -1
- package/tsconfig.json +8 -7
- package/vite.config.ts +76 -3
- package/vitest.config.ts +15 -0
- package/.eslintrc.js +0 -24
- package/.mocharc.json +0 -3
package/build/index.js
CHANGED
|
@@ -1,62 +1,311 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const ejs = require("ejs");
|
|
16
|
-
const entities = require("entities");
|
|
17
|
-
function _interopNamespaceDefault(e) {
|
|
18
|
-
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
19
|
-
if (e) {
|
|
20
|
-
for (const k in e) {
|
|
21
|
-
if (k !== "default") {
|
|
22
|
-
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
-
enumerable: true,
|
|
25
|
-
get: () => e[k]
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
n.default = e;
|
|
31
|
-
return Object.freeze(n);
|
|
32
|
-
}
|
|
33
|
-
const mime__namespace = /* @__PURE__ */ _interopNamespaceDefault(mime);
|
|
34
|
-
const os__namespace = /* @__PURE__ */ _interopNamespaceDefault(os);
|
|
35
|
-
const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
|
|
36
|
-
const fs__namespace$1 = /* @__PURE__ */ _interopNamespaceDefault(fs$1);
|
|
37
|
-
const request__namespace = /* @__PURE__ */ _interopNamespaceDefault(request);
|
|
38
|
-
const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
|
|
39
|
-
const ejs__namespace = /* @__PURE__ */ _interopNamespaceDefault(ejs);
|
|
40
|
-
const entities__namespace = /* @__PURE__ */ _interopNamespaceDefault(entities);
|
|
1
|
+
import os from "os";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { v4 } from "uuid";
|
|
5
|
+
import * as cheerio from "cheerio";
|
|
6
|
+
import { remove } from "diacritics";
|
|
7
|
+
import uslug from "uslug";
|
|
8
|
+
import archiver from "archiver";
|
|
9
|
+
import fs$1 from "fs-extra";
|
|
10
|
+
import request from "superagent";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import { promisify } from "util";
|
|
13
|
+
import ejs from "ejs";
|
|
14
|
+
import * as entities from "entities";
|
|
41
15
|
const errors = {
|
|
42
16
|
no_output_path: "No output path!",
|
|
43
17
|
no_title: "Title is required.",
|
|
44
18
|
no_content: "Content is required."
|
|
45
19
|
};
|
|
20
|
+
class GlobalLogger {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.customLogger = null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 获取 Logger 单例实例
|
|
26
|
+
*/
|
|
27
|
+
static getInstance() {
|
|
28
|
+
if (!GlobalLogger.instance) {
|
|
29
|
+
GlobalLogger.instance = new GlobalLogger();
|
|
30
|
+
}
|
|
31
|
+
return GlobalLogger.instance;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 设置自定义 logger
|
|
35
|
+
* @param logger 自定义 logger 实例
|
|
36
|
+
*/
|
|
37
|
+
setLogger(logger2) {
|
|
38
|
+
this.customLogger = logger2;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 获取当前使用的 logger
|
|
42
|
+
*/
|
|
43
|
+
getLogger() {
|
|
44
|
+
return this.customLogger || {
|
|
45
|
+
log: (msg) => console.log(msg),
|
|
46
|
+
info: (msg) => console.info(msg),
|
|
47
|
+
error: (msg) => console.error(msg),
|
|
48
|
+
warn: (msg) => console.warn(msg)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 输出普通日志
|
|
53
|
+
*/
|
|
54
|
+
log(msg) {
|
|
55
|
+
this.getLogger().log(msg);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 输出信息日志
|
|
59
|
+
*/
|
|
60
|
+
info(msg) {
|
|
61
|
+
this.getLogger().info(msg);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 输出错误日志
|
|
65
|
+
*/
|
|
66
|
+
error(msg) {
|
|
67
|
+
this.getLogger().error(msg);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 输出警告日志
|
|
71
|
+
*/
|
|
72
|
+
warn(msg) {
|
|
73
|
+
this.getLogger().warn(msg);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const logger = GlobalLogger.getInstance();
|
|
46
77
|
function safeFineName(name) {
|
|
47
78
|
return name.replace(/[/\\?%*:|"<>\t\r\n]/g, "_");
|
|
48
79
|
}
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
const mimeModule$1 = require("mime/lite");
|
|
81
|
+
const mime$1 = mimeModule$1.default || mimeModule$1;
|
|
82
|
+
const ALLOWED_ATTRIBUTES = [
|
|
83
|
+
"about",
|
|
84
|
+
"accesskey",
|
|
85
|
+
"alt",
|
|
86
|
+
"aria-activedescendant",
|
|
87
|
+
"aria-atomic",
|
|
88
|
+
"aria-autocomplete",
|
|
89
|
+
"aria-busy",
|
|
90
|
+
"aria-checked",
|
|
91
|
+
"aria-controls",
|
|
92
|
+
"aria-describedat",
|
|
93
|
+
"aria-describedby",
|
|
94
|
+
"aria-disabled",
|
|
95
|
+
"aria-dropeffect",
|
|
96
|
+
"aria-expanded",
|
|
97
|
+
"aria-flowto",
|
|
98
|
+
"aria-grabbed",
|
|
99
|
+
"aria-haspopup",
|
|
100
|
+
"aria-hidden",
|
|
101
|
+
"aria-invalid",
|
|
102
|
+
"aria-label",
|
|
103
|
+
"aria-labelledby",
|
|
104
|
+
"aria-level",
|
|
105
|
+
"aria-live",
|
|
106
|
+
"aria-multiline",
|
|
107
|
+
"aria-multiselectable",
|
|
108
|
+
"aria-orientation",
|
|
109
|
+
"aria-owns",
|
|
110
|
+
"aria-posinset",
|
|
111
|
+
"aria-pressed",
|
|
112
|
+
"aria-readonly",
|
|
113
|
+
"aria-relevant",
|
|
114
|
+
"aria-required",
|
|
115
|
+
"aria-selected",
|
|
116
|
+
"aria-setsize",
|
|
117
|
+
"aria-sort",
|
|
118
|
+
"aria-valuemax",
|
|
119
|
+
"aria-valuemin",
|
|
120
|
+
"aria-valuenow",
|
|
121
|
+
"aria-valuetext",
|
|
122
|
+
"class",
|
|
123
|
+
"colspan",
|
|
124
|
+
"content",
|
|
125
|
+
// 去除重复
|
|
126
|
+
"contenteditable",
|
|
127
|
+
"contextmenu",
|
|
128
|
+
"datatype",
|
|
129
|
+
"dir",
|
|
130
|
+
"draggable",
|
|
131
|
+
"dropzone",
|
|
132
|
+
"epub:prefix",
|
|
133
|
+
"epub:type",
|
|
134
|
+
"hidden",
|
|
135
|
+
"href",
|
|
136
|
+
"hreflang",
|
|
137
|
+
"id",
|
|
138
|
+
// 去除重复
|
|
139
|
+
"inlist",
|
|
140
|
+
"itemid",
|
|
141
|
+
"itemref",
|
|
142
|
+
"itemscope",
|
|
143
|
+
"itemtype",
|
|
144
|
+
"lang",
|
|
145
|
+
"media",
|
|
146
|
+
"ns1:type",
|
|
147
|
+
"ns2:alphabet",
|
|
148
|
+
"ns2:ph",
|
|
149
|
+
"onabort",
|
|
150
|
+
"onblur",
|
|
151
|
+
"oncanplay",
|
|
152
|
+
"oncanplaythrough",
|
|
153
|
+
"onchange",
|
|
154
|
+
"onclick",
|
|
155
|
+
"oncontextmenu",
|
|
156
|
+
"ondblclick",
|
|
157
|
+
"ondrag",
|
|
158
|
+
"ondragend",
|
|
159
|
+
"ondragenter",
|
|
160
|
+
"ondragleave",
|
|
161
|
+
"ondragover",
|
|
162
|
+
"ondragstart",
|
|
163
|
+
"ondrop",
|
|
164
|
+
"ondurationchange",
|
|
165
|
+
"onemptied",
|
|
166
|
+
"onended",
|
|
167
|
+
"onerror",
|
|
168
|
+
"onfocus",
|
|
169
|
+
"oninput",
|
|
170
|
+
"oninvalid",
|
|
171
|
+
"onkeydown",
|
|
172
|
+
"onkeypress",
|
|
173
|
+
"onkeyup",
|
|
174
|
+
"onload",
|
|
175
|
+
"onloadeddata",
|
|
176
|
+
"onloadedmetadata",
|
|
177
|
+
"onloadstart",
|
|
178
|
+
"onmousedown",
|
|
179
|
+
"onmousemove",
|
|
180
|
+
"onmouseout",
|
|
181
|
+
"onmouseover",
|
|
182
|
+
"onmouseup",
|
|
183
|
+
"onmousewheel",
|
|
184
|
+
"onpause",
|
|
185
|
+
"onplay",
|
|
186
|
+
"onplaying",
|
|
187
|
+
"onprogress",
|
|
188
|
+
"onratechange",
|
|
189
|
+
"onreadystatechange",
|
|
190
|
+
"onreset",
|
|
191
|
+
"onscroll",
|
|
192
|
+
"onseeked",
|
|
193
|
+
"onseeking",
|
|
194
|
+
"onselect",
|
|
195
|
+
"onshow",
|
|
196
|
+
"onstalled",
|
|
197
|
+
"onsubmit",
|
|
198
|
+
"onsuspend",
|
|
199
|
+
"ontimeupdate",
|
|
200
|
+
"onvolumechange",
|
|
201
|
+
"onwaiting",
|
|
202
|
+
"prefix",
|
|
203
|
+
"property",
|
|
204
|
+
"rel",
|
|
205
|
+
"resource",
|
|
206
|
+
"rev",
|
|
207
|
+
"role",
|
|
208
|
+
"rowspan",
|
|
209
|
+
"spellcheck",
|
|
210
|
+
"src",
|
|
211
|
+
"style",
|
|
212
|
+
"tabindex",
|
|
213
|
+
"target",
|
|
214
|
+
"title",
|
|
215
|
+
// 去除重复
|
|
216
|
+
"type",
|
|
217
|
+
"typeof",
|
|
218
|
+
"vocab",
|
|
219
|
+
"xml:base",
|
|
220
|
+
"xml:lang",
|
|
221
|
+
"xml:space"
|
|
222
|
+
];
|
|
223
|
+
const ALLOWED_XHTML11_TAGS = [
|
|
224
|
+
"a",
|
|
225
|
+
"abbr",
|
|
226
|
+
"acronym",
|
|
227
|
+
"address",
|
|
228
|
+
"applet",
|
|
229
|
+
"b",
|
|
230
|
+
"bar",
|
|
231
|
+
"basefont",
|
|
232
|
+
"bdo",
|
|
233
|
+
"big",
|
|
234
|
+
"blockquote",
|
|
235
|
+
"br",
|
|
236
|
+
"caption",
|
|
237
|
+
"center",
|
|
238
|
+
"cite",
|
|
239
|
+
"code",
|
|
240
|
+
"col",
|
|
241
|
+
"colgroup",
|
|
242
|
+
"dd",
|
|
243
|
+
"del",
|
|
244
|
+
"dfn",
|
|
245
|
+
"div",
|
|
246
|
+
"dl",
|
|
247
|
+
"dt",
|
|
248
|
+
"em",
|
|
249
|
+
"embed",
|
|
250
|
+
"font",
|
|
251
|
+
"h1",
|
|
252
|
+
"h2",
|
|
253
|
+
"h3",
|
|
254
|
+
"h4",
|
|
255
|
+
"h5",
|
|
256
|
+
"h6",
|
|
257
|
+
"hr",
|
|
258
|
+
"i",
|
|
259
|
+
"iframe",
|
|
260
|
+
"img",
|
|
261
|
+
"ins",
|
|
262
|
+
"kbd",
|
|
263
|
+
"li",
|
|
264
|
+
"map",
|
|
265
|
+
"noscript",
|
|
266
|
+
"ns:svg",
|
|
267
|
+
"object",
|
|
268
|
+
// 去除重复
|
|
269
|
+
"ol",
|
|
270
|
+
"p",
|
|
271
|
+
"param",
|
|
272
|
+
"pre",
|
|
273
|
+
"q",
|
|
274
|
+
"s",
|
|
275
|
+
"samp",
|
|
276
|
+
"script",
|
|
277
|
+
"small",
|
|
278
|
+
"span",
|
|
279
|
+
"strike",
|
|
280
|
+
"strong",
|
|
281
|
+
"sub",
|
|
282
|
+
"sup",
|
|
283
|
+
"table",
|
|
284
|
+
"tbody",
|
|
285
|
+
"td",
|
|
286
|
+
"tfoot",
|
|
287
|
+
"th",
|
|
288
|
+
"thead",
|
|
289
|
+
"tr",
|
|
290
|
+
"tt",
|
|
291
|
+
"u",
|
|
292
|
+
"ul",
|
|
293
|
+
"var"
|
|
294
|
+
];
|
|
295
|
+
const ALLOWED_ATTRIBUTES_SET = new Set(ALLOWED_ATTRIBUTES);
|
|
296
|
+
const ALLOWED_XHTML11_TAGS_SET = new Set(ALLOWED_XHTML11_TAGS);
|
|
297
|
+
const SELF_CLOSING_TAGS = /* @__PURE__ */ new Set(["img", "br", "hr"]);
|
|
298
|
+
function initializeChapterInfo(content, index, epubConfigs) {
|
|
299
|
+
const chapter = { ...content };
|
|
51
300
|
let { filename } = chapter;
|
|
52
301
|
if (!filename) {
|
|
53
|
-
let titleSlug = uslug(
|
|
54
|
-
titleSlug = titleSlug.replace(/[
|
|
302
|
+
let titleSlug = uslug(remove(chapter.title || "no title"));
|
|
303
|
+
titleSlug = titleSlug.replace(/[/\\]/g, "_");
|
|
55
304
|
chapter.href = `${index}_${titleSlug}.xhtml`;
|
|
56
305
|
chapter.filePath = path.join(epubConfigs.dir, "OEBPS", chapter.href);
|
|
57
306
|
} else {
|
|
58
307
|
filename = safeFineName(filename);
|
|
59
|
-
|
|
308
|
+
const is_xhtml = filename.endsWith(".xhtml");
|
|
60
309
|
chapter.href = is_xhtml ? filename : `${filename}.xhtml`;
|
|
61
310
|
if (is_xhtml) {
|
|
62
311
|
chapter.filePath = path.join(epubConfigs.dir, "OEBPS", filename);
|
|
@@ -68,311 +317,258 @@ function parseContent(content, index, epubConfigs) {
|
|
|
68
317
|
chapter.dir = path.dirname(chapter.filePath);
|
|
69
318
|
chapter.excludeFromToc = chapter.excludeFromToc || false;
|
|
70
319
|
chapter.beforeToc = chapter.beforeToc || false;
|
|
320
|
+
return chapter;
|
|
321
|
+
}
|
|
322
|
+
function normalizeAuthorInfo(chapter) {
|
|
71
323
|
if (chapter.author && typeof chapter.author === "string") {
|
|
72
324
|
chapter.author = [chapter.author];
|
|
73
325
|
} else if (!chapter.author || !Array.isArray(chapter.author)) {
|
|
74
326
|
chapter.author = [];
|
|
75
327
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"aria-dropeffect",
|
|
95
|
-
"aria-expanded",
|
|
96
|
-
"aria-flowto",
|
|
97
|
-
"aria-grabbed",
|
|
98
|
-
"aria-haspopup",
|
|
99
|
-
"aria-hidden",
|
|
100
|
-
"aria-invalid",
|
|
101
|
-
"aria-label",
|
|
102
|
-
"aria-labelledby",
|
|
103
|
-
"aria-level",
|
|
104
|
-
"aria-live",
|
|
105
|
-
"aria-multiline",
|
|
106
|
-
"aria-multiselectable",
|
|
107
|
-
"aria-orientation",
|
|
108
|
-
"aria-owns",
|
|
109
|
-
"aria-posinset",
|
|
110
|
-
"aria-pressed",
|
|
111
|
-
"aria-readonly",
|
|
112
|
-
"aria-relevant",
|
|
113
|
-
"aria-required",
|
|
114
|
-
"aria-selected",
|
|
115
|
-
"aria-setsize",
|
|
116
|
-
"aria-sort",
|
|
117
|
-
"aria-valuemax",
|
|
118
|
-
"aria-valuemin",
|
|
119
|
-
"aria-valuenow",
|
|
120
|
-
"aria-valuetext",
|
|
121
|
-
"class",
|
|
122
|
-
"content",
|
|
123
|
-
"contenteditable",
|
|
124
|
-
"contextmenu",
|
|
125
|
-
"datatype",
|
|
126
|
-
"dir",
|
|
127
|
-
"draggable",
|
|
128
|
-
"dropzone",
|
|
129
|
-
"hidden",
|
|
130
|
-
"hreflang",
|
|
131
|
-
"id",
|
|
132
|
-
"inlist",
|
|
133
|
-
"itemid",
|
|
134
|
-
"itemref",
|
|
135
|
-
"itemscope",
|
|
136
|
-
"itemtype",
|
|
137
|
-
"lang",
|
|
138
|
-
"media",
|
|
139
|
-
"ns1:type",
|
|
140
|
-
"ns2:alphabet",
|
|
141
|
-
"ns2:ph",
|
|
142
|
-
"onabort",
|
|
143
|
-
"onblur",
|
|
144
|
-
"oncanplay",
|
|
145
|
-
"oncanplaythrough",
|
|
146
|
-
"onchange",
|
|
147
|
-
"onclick",
|
|
148
|
-
"oncontextmenu",
|
|
149
|
-
"ondblclick",
|
|
150
|
-
"ondrag",
|
|
151
|
-
"ondragend",
|
|
152
|
-
"ondragenter",
|
|
153
|
-
"ondragleave",
|
|
154
|
-
"ondragover",
|
|
155
|
-
"ondragstart",
|
|
156
|
-
"ondrop",
|
|
157
|
-
"ondurationchange",
|
|
158
|
-
"onemptied",
|
|
159
|
-
"onended",
|
|
160
|
-
"onerror",
|
|
161
|
-
"onfocus",
|
|
162
|
-
"oninput",
|
|
163
|
-
"oninvalid",
|
|
164
|
-
"onkeydown",
|
|
165
|
-
"onkeypress",
|
|
166
|
-
"onkeyup",
|
|
167
|
-
"onload",
|
|
168
|
-
"onloadeddata",
|
|
169
|
-
"onloadedmetadata",
|
|
170
|
-
"onloadstart",
|
|
171
|
-
"onmousedown",
|
|
172
|
-
"onmousemove",
|
|
173
|
-
"onmouseout",
|
|
174
|
-
"onmouseover",
|
|
175
|
-
"onmouseup",
|
|
176
|
-
"onmousewheel",
|
|
177
|
-
"onpause",
|
|
178
|
-
"onplay",
|
|
179
|
-
"onplaying",
|
|
180
|
-
"onprogress",
|
|
181
|
-
"onratechange",
|
|
182
|
-
"onreadystatechange",
|
|
183
|
-
"onreset",
|
|
184
|
-
"onscroll",
|
|
185
|
-
"onseeked",
|
|
186
|
-
"onseeking",
|
|
187
|
-
"onselect",
|
|
188
|
-
"onshow",
|
|
189
|
-
"onstalled",
|
|
190
|
-
"onsubmit",
|
|
191
|
-
"onsuspend",
|
|
192
|
-
"ontimeupdate",
|
|
193
|
-
"onvolumechange",
|
|
194
|
-
"onwaiting",
|
|
195
|
-
"prefix",
|
|
196
|
-
"property",
|
|
197
|
-
"rel",
|
|
198
|
-
"resource",
|
|
199
|
-
"rev",
|
|
200
|
-
"role",
|
|
201
|
-
"spellcheck",
|
|
202
|
-
"style",
|
|
203
|
-
"tabindex",
|
|
204
|
-
"target",
|
|
205
|
-
"title",
|
|
206
|
-
"type",
|
|
207
|
-
"typeof",
|
|
208
|
-
"vocab",
|
|
209
|
-
"xml:base",
|
|
210
|
-
"xml:lang",
|
|
211
|
-
"xml:space",
|
|
212
|
-
"colspan",
|
|
213
|
-
"rowspan",
|
|
214
|
-
"epub:type",
|
|
215
|
-
"epub:prefix"
|
|
216
|
-
];
|
|
217
|
-
let allowedXhtml11Tags = [
|
|
218
|
-
"div",
|
|
219
|
-
"p",
|
|
220
|
-
"h1",
|
|
221
|
-
"h2",
|
|
222
|
-
"h3",
|
|
223
|
-
"h4",
|
|
224
|
-
"h5",
|
|
225
|
-
"h6",
|
|
226
|
-
"ul",
|
|
227
|
-
"ol",
|
|
228
|
-
"li",
|
|
229
|
-
"dl",
|
|
230
|
-
"dt",
|
|
231
|
-
"dd",
|
|
232
|
-
"address",
|
|
233
|
-
"hr",
|
|
234
|
-
"pre",
|
|
235
|
-
"blockquote",
|
|
236
|
-
"center",
|
|
237
|
-
"ins",
|
|
238
|
-
"del",
|
|
239
|
-
"a",
|
|
240
|
-
"span",
|
|
241
|
-
"bdo",
|
|
242
|
-
"br",
|
|
243
|
-
"em",
|
|
244
|
-
"strong",
|
|
245
|
-
"dfn",
|
|
246
|
-
"code",
|
|
247
|
-
"samp",
|
|
248
|
-
"kbd",
|
|
249
|
-
"bar",
|
|
250
|
-
"cite",
|
|
251
|
-
"abbr",
|
|
252
|
-
"acronym",
|
|
253
|
-
"q",
|
|
254
|
-
"sub",
|
|
255
|
-
"sup",
|
|
256
|
-
"tt",
|
|
257
|
-
"i",
|
|
258
|
-
"b",
|
|
259
|
-
"big",
|
|
260
|
-
"small",
|
|
261
|
-
"u",
|
|
262
|
-
"s",
|
|
263
|
-
"strike",
|
|
264
|
-
"basefont",
|
|
265
|
-
"font",
|
|
266
|
-
"object",
|
|
267
|
-
"param",
|
|
268
|
-
"img",
|
|
269
|
-
"table",
|
|
270
|
-
"caption",
|
|
271
|
-
"colgroup",
|
|
272
|
-
"col",
|
|
273
|
-
"thead",
|
|
274
|
-
"tfoot",
|
|
275
|
-
"tbody",
|
|
276
|
-
"tr",
|
|
277
|
-
"th",
|
|
278
|
-
"td",
|
|
279
|
-
"embed",
|
|
280
|
-
"applet",
|
|
281
|
-
"iframe",
|
|
282
|
-
"img",
|
|
283
|
-
"map",
|
|
284
|
-
"noscript",
|
|
285
|
-
"ns:svg",
|
|
286
|
-
"object",
|
|
287
|
-
"script",
|
|
288
|
-
"table",
|
|
289
|
-
"tt",
|
|
290
|
-
"var"
|
|
291
|
-
];
|
|
292
|
-
let $ = cheerio.load(chapter.data, {
|
|
293
|
-
lowerCaseTags: true,
|
|
294
|
-
recognizeSelfClosing: true
|
|
295
|
-
});
|
|
296
|
-
let body = $("body");
|
|
297
|
-
if (body.length) {
|
|
298
|
-
let html = body.html();
|
|
299
|
-
if (html) {
|
|
300
|
-
$ = cheerio.load(html, {
|
|
328
|
+
}
|
|
329
|
+
function getAllowedAttributes() {
|
|
330
|
+
return ALLOWED_ATTRIBUTES;
|
|
331
|
+
}
|
|
332
|
+
function getAllowedXhtml11Tags() {
|
|
333
|
+
return ALLOWED_XHTML11_TAGS;
|
|
334
|
+
}
|
|
335
|
+
function loadAndProcessHtml(data) {
|
|
336
|
+
if (!data || typeof data !== "string") {
|
|
337
|
+
throw new Error("Invalid HTML data: data must be a non-empty string");
|
|
338
|
+
}
|
|
339
|
+
const trimmedData = data.trim();
|
|
340
|
+
if (trimmedData.length === 0) {
|
|
341
|
+
throw new Error("Invalid HTML data: data cannot be empty or whitespace only");
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
let $ = cheerio.load(trimmedData, {
|
|
345
|
+
xml: {
|
|
301
346
|
lowerCaseTags: true,
|
|
302
347
|
recognizeSelfClosing: true
|
|
303
|
-
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
const body = $("body");
|
|
351
|
+
if (body.length) {
|
|
352
|
+
const html = body.html();
|
|
353
|
+
if (html) {
|
|
354
|
+
$ = cheerio.load(html, {
|
|
355
|
+
xml: {
|
|
356
|
+
lowerCaseTags: true,
|
|
357
|
+
recognizeSelfClosing: true
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
304
361
|
}
|
|
362
|
+
return $;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`Failed to parse HTML content: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
366
|
+
);
|
|
305
367
|
}
|
|
368
|
+
}
|
|
369
|
+
function processHtmlElements($, allowedAttributes, allowedXhtml11Tags, epubConfigs, index) {
|
|
370
|
+
const allowedAttrsSet = ALLOWED_ATTRIBUTES_SET;
|
|
371
|
+
const allowedTagsSet = ALLOWED_XHTML11_TAGS_SET;
|
|
372
|
+
const selfClosingTags = SELF_CLOSING_TAGS;
|
|
306
373
|
$($("*").get().reverse()).each(function(elemIndex, elem) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (
|
|
311
|
-
if (
|
|
312
|
-
$
|
|
374
|
+
const attrs = elem.attribs || {};
|
|
375
|
+
const $elem = $(elem);
|
|
376
|
+
const tagName = elem.name;
|
|
377
|
+
if (selfClosingTags.has(tagName)) {
|
|
378
|
+
if (tagName === "img" && !$elem.attr("alt")) {
|
|
379
|
+
$elem.attr("alt", "image-placeholder");
|
|
313
380
|
}
|
|
314
381
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
382
|
+
const attrsToRemove = [];
|
|
383
|
+
for (const [attrName] of Object.entries(attrs)) {
|
|
384
|
+
if (allowedAttrsSet.has(attrName)) {
|
|
385
|
+
if (attrName === "type" && tagName !== "script") {
|
|
386
|
+
attrsToRemove.push(attrName);
|
|
319
387
|
}
|
|
320
388
|
} else {
|
|
321
|
-
|
|
389
|
+
attrsToRemove.push(attrName);
|
|
322
390
|
}
|
|
323
|
-
}
|
|
391
|
+
}
|
|
392
|
+
for (const attrName of attrsToRemove) {
|
|
393
|
+
$elem.removeAttr(attrName);
|
|
394
|
+
}
|
|
324
395
|
if (epubConfigs.version === 2) {
|
|
325
|
-
if (!
|
|
396
|
+
if (!allowedTagsSet.has(tagName)) {
|
|
326
397
|
if (epubConfigs.verbose) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
that.name,
|
|
330
|
-
"tag isn't allowed on EPUB 2/XHTML 1.1 DTD."
|
|
398
|
+
logger.warn(
|
|
399
|
+
`Warning (content[${index}]): ${tagName} tag isn't allowed on EPUB 2/XHTML 1.1 DTD.`
|
|
331
400
|
);
|
|
332
401
|
}
|
|
333
|
-
|
|
334
|
-
$
|
|
402
|
+
const child = $elem.html();
|
|
403
|
+
$elem.replaceWith($("<div>" + child + "</div>"));
|
|
335
404
|
}
|
|
336
405
|
}
|
|
337
406
|
});
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
407
|
+
}
|
|
408
|
+
function processImages($, chapter, epubConfigs) {
|
|
409
|
+
$("img").each((index, elem) => {
|
|
410
|
+
const url = $(elem).attr("src") || "";
|
|
411
|
+
if (!url || url.trim().length === 0) {
|
|
412
|
+
logger.warn(`Image at index ${index} in chapter has empty src attribute, removing element`);
|
|
413
|
+
$(elem).remove();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const trimmedUrl = url.trim();
|
|
417
|
+
try {
|
|
418
|
+
if (!trimmedUrl.match(/^(https?:\/\/|data:|\.\/|\/)/)) {
|
|
419
|
+
logger.warn(`Image URL "${trimmedUrl}" appears to be invalid, but processing anyway`);
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
logger.error(`Error validating image URL "${trimmedUrl}": ${error}`);
|
|
423
|
+
}
|
|
424
|
+
const image = epubConfigs.images.find((el) => el.url === trimmedUrl);
|
|
341
425
|
let id;
|
|
342
426
|
let extension;
|
|
343
427
|
if (image) {
|
|
344
428
|
id = image.id;
|
|
345
429
|
extension = image.extension;
|
|
346
430
|
} else {
|
|
347
|
-
id =
|
|
348
|
-
let mediaType =
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
431
|
+
id = v4();
|
|
432
|
+
let mediaType = "";
|
|
433
|
+
try {
|
|
434
|
+
const cleanUrl = trimmedUrl.replace(/\?.*/, "");
|
|
435
|
+
mediaType = mime$1.getType(cleanUrl) || "";
|
|
436
|
+
if (!mediaType) {
|
|
437
|
+
const urlExtension = cleanUrl.split(".").pop()?.toLowerCase();
|
|
438
|
+
if (urlExtension && ["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(urlExtension)) {
|
|
439
|
+
mediaType = `image/${urlExtension === "jpg" ? "jpeg" : urlExtension}`;
|
|
440
|
+
logger.warn(
|
|
441
|
+
`Could not determine MIME type for "${trimmedUrl}", inferred as "${mediaType}"`
|
|
442
|
+
);
|
|
443
|
+
} else {
|
|
444
|
+
logger.warn(
|
|
445
|
+
`Could not determine MIME type for "${trimmedUrl}", defaulting to image/jpeg`
|
|
446
|
+
);
|
|
447
|
+
mediaType = "image/jpeg";
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
} catch (error) {
|
|
451
|
+
logger.error(`Error determining MIME type for "${trimmedUrl}": ${error}`);
|
|
452
|
+
mediaType = "image/jpeg";
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
extension = mime$1.getExtension(mediaType) || "jpg";
|
|
456
|
+
} catch (error) {
|
|
457
|
+
logger.error(`Error getting extension for MIME type "${mediaType}": ${error}`);
|
|
458
|
+
extension = "jpg";
|
|
459
|
+
}
|
|
460
|
+
const dir = chapter.dir || "";
|
|
461
|
+
const img = { id, url: trimmedUrl, dir, mediaType, extension };
|
|
352
462
|
epubConfigs.images.push(img);
|
|
463
|
+
if (epubConfigs.verbose) {
|
|
464
|
+
logger.info(`Added image: ${trimmedUrl} -> images/${id}.${extension} (${mediaType})`);
|
|
465
|
+
}
|
|
353
466
|
}
|
|
354
|
-
|
|
467
|
+
try {
|
|
468
|
+
$(elem).attr("src", `images/${id}.${extension}`);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
logger.error(`Error setting src attribute for image ${id}: ${error}`);
|
|
471
|
+
$(elem).remove();
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function extractAndCleanHtmlContent($, originalData) {
|
|
476
|
+
let data;
|
|
477
|
+
if ($("body").length) {
|
|
478
|
+
data = $("body").html() || "";
|
|
479
|
+
} else {
|
|
480
|
+
data = $.root().html() || "";
|
|
481
|
+
}
|
|
482
|
+
if (!originalData) {
|
|
483
|
+
return data.replace(
|
|
484
|
+
/<(br|hr|img|input|meta|area|base|col|embed|link|source|track|wbr)([^>]*?)><\/\1>/gi,
|
|
485
|
+
"<$1$2/>"
|
|
486
|
+
).replace(
|
|
487
|
+
new RegExp("<(br|hr|img|input|meta|area|base|col|embed|link|source|track|wbr)([^>]*?)(?<!\\/)>", "gi"),
|
|
488
|
+
"<$1$2/>"
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
492
|
+
const entityRegex = /&[a-zA-Z][a-zA-Z0-9]*;|&#[0-9]+;|&#x[0-9a-fA-F]+;/g;
|
|
493
|
+
const matches = Array.from(originalData.matchAll(entityRegex));
|
|
494
|
+
let processedOriginal = originalData;
|
|
495
|
+
const timestamp = Date.now();
|
|
496
|
+
const randomId = Math.random().toString(36).substring(2, 8);
|
|
497
|
+
const placeholderPrefix = `__ENTITY_${timestamp}_${randomId}_`;
|
|
498
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
499
|
+
const match = matches[i];
|
|
500
|
+
const placeholder = `${placeholderPrefix}${i}__`;
|
|
501
|
+
entityMap.set(placeholder, match[0]);
|
|
502
|
+
processedOriginal = processedOriginal.substring(0, match.index) + placeholder + processedOriginal.substring(match.index + match[0].length);
|
|
503
|
+
}
|
|
504
|
+
const $temp = cheerio.load(processedOriginal, {
|
|
505
|
+
xmlMode: false
|
|
355
506
|
});
|
|
356
|
-
|
|
507
|
+
let tempData;
|
|
508
|
+
if ($temp("body").length) {
|
|
509
|
+
tempData = $temp("body").html() || "";
|
|
510
|
+
} else {
|
|
511
|
+
tempData = $temp.root().html() || "";
|
|
512
|
+
}
|
|
513
|
+
for (const [placeholder, entity] of entityMap) {
|
|
514
|
+
tempData = tempData.replace(new RegExp(placeholder, "g"), entity);
|
|
515
|
+
}
|
|
516
|
+
return tempData.replace(
|
|
517
|
+
/<(br|hr|img|input|meta|area|base|col|embed|link|source|track|wbr)([^>]*?)><\/\1>/gi,
|
|
518
|
+
"<$1$2/>"
|
|
519
|
+
).replace(
|
|
520
|
+
new RegExp("<(br|hr|img|input|meta|area|base|col|embed|link|source|track|wbr)([^>]*?)(?<!\\/)>", "gi"),
|
|
521
|
+
"<$1$2/>"
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
function processChildrenChapters(chapter, index, epubConfigs) {
|
|
357
525
|
if (Array.isArray(chapter.children)) {
|
|
358
526
|
chapter.children = chapter.children.map(
|
|
359
|
-
(
|
|
527
|
+
(content, idx) => parseContent(content, `${index}_${idx}`, epubConfigs)
|
|
360
528
|
);
|
|
361
529
|
}
|
|
530
|
+
}
|
|
531
|
+
function parseContent(content, index, epubConfigs) {
|
|
532
|
+
if (!content) {
|
|
533
|
+
throw new Error("Content cannot be null or undefined");
|
|
534
|
+
}
|
|
535
|
+
if (!content.data) {
|
|
536
|
+
logger.warn(`Chapter at index ${index} has no data, using empty string`);
|
|
537
|
+
content.data = "";
|
|
538
|
+
}
|
|
539
|
+
const chapter = initializeChapterInfo(content, index, epubConfigs);
|
|
540
|
+
normalizeAuthorInfo(chapter);
|
|
541
|
+
const allowedAttributes = getAllowedAttributes();
|
|
542
|
+
const allowedXhtml11Tags = getAllowedXhtml11Tags();
|
|
543
|
+
if (!chapter.data || chapter.data.trim().length === 0) {
|
|
544
|
+
logger.warn(`Chapter at index ${index} has empty data, setting empty content`);
|
|
545
|
+
chapter.data = "";
|
|
546
|
+
} else {
|
|
547
|
+
let $;
|
|
548
|
+
try {
|
|
549
|
+
$ = loadAndProcessHtml(chapter.data);
|
|
550
|
+
} catch (error) {
|
|
551
|
+
logger.error(`Failed to process HTML for chapter ${index}: ${error}`);
|
|
552
|
+
$ = cheerio.load(`<div>${chapter.data}</div>`);
|
|
553
|
+
}
|
|
554
|
+
processHtmlElements($, allowedAttributes, allowedXhtml11Tags, epubConfigs, index);
|
|
555
|
+
processImages($, chapter, epubConfigs);
|
|
556
|
+
chapter.data = extractAndCleanHtmlContent($, content.data);
|
|
557
|
+
}
|
|
558
|
+
processChildrenChapters(chapter, index, epubConfigs);
|
|
362
559
|
return chapter;
|
|
363
560
|
}
|
|
364
|
-
|
|
365
|
-
const writeFile =
|
|
561
|
+
promisify(fs.readFile);
|
|
562
|
+
const writeFile = promisify(fs.writeFile);
|
|
366
563
|
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";
|
|
367
564
|
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
368
565
|
async function fileIsStable(filename, max_wait = 3e4) {
|
|
369
|
-
|
|
370
|
-
let last_size =
|
|
566
|
+
const start_time = (/* @__PURE__ */ new Date()).getTime();
|
|
567
|
+
let last_size = fs.statSync(filename).size;
|
|
371
568
|
while ((/* @__PURE__ */ new Date()).getTime() - start_time <= max_wait) {
|
|
372
569
|
await wait(1e3);
|
|
373
|
-
|
|
374
|
-
if (size === last_size)
|
|
375
|
-
return true;
|
|
570
|
+
const size = fs.statSync(filename).size;
|
|
571
|
+
if (size === last_size) return true;
|
|
376
572
|
last_size = size;
|
|
377
573
|
}
|
|
378
574
|
return false;
|
|
@@ -382,26 +578,31 @@ function simpleMinifier(xhtml) {
|
|
|
382
578
|
return xhtml;
|
|
383
579
|
}
|
|
384
580
|
const downloadImage = async (epubData, options) => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
581
|
+
const { url } = options;
|
|
582
|
+
const { log } = epubData;
|
|
583
|
+
const epub_dir = epubData.dir;
|
|
388
584
|
if (!url) {
|
|
389
585
|
return;
|
|
390
586
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
587
|
+
const image_dir = path.join(epub_dir, "OEBPS", "images");
|
|
588
|
+
fs$1.ensureDirSync(image_dir);
|
|
589
|
+
const filename = path.join(image_dir, options.id + "." + options.extension);
|
|
394
590
|
if (url.startsWith("file://") || url.startsWith("/")) {
|
|
395
|
-
let
|
|
591
|
+
let aux_path = url.replace(/^file:\/\//i, "");
|
|
592
|
+
try {
|
|
593
|
+
aux_path = decodeURIComponent(aux_path);
|
|
594
|
+
} catch {
|
|
595
|
+
log(`[URL Decode Warning] Failed to decode path: ${aux_path}`);
|
|
596
|
+
}
|
|
396
597
|
if (process.platform === "win32") {
|
|
397
|
-
if (
|
|
398
|
-
|
|
598
|
+
if (aux_path.match(/^\/[a-zA-Z]:/)) {
|
|
599
|
+
aux_path = aux_path.replace(/^\//, "");
|
|
399
600
|
}
|
|
400
601
|
}
|
|
401
|
-
log(`[Copy 1] '${
|
|
402
|
-
if (
|
|
602
|
+
log(`[Copy 1] '${aux_path}' to '${filename}'`);
|
|
603
|
+
if (fs$1.existsSync(aux_path)) {
|
|
403
604
|
try {
|
|
404
|
-
|
|
605
|
+
fs$1.copySync(aux_path, filename);
|
|
405
606
|
} catch (e) {
|
|
406
607
|
log("[Copy 1 Error] " + e.message);
|
|
407
608
|
}
|
|
@@ -412,18 +613,18 @@ const downloadImage = async (epubData, options) => {
|
|
|
412
613
|
}
|
|
413
614
|
let requestAction;
|
|
414
615
|
if (url.startsWith("http")) {
|
|
415
|
-
requestAction =
|
|
416
|
-
requestAction.pipe(
|
|
616
|
+
requestAction = request.get(url).set({ "User-Agent": USER_AGENT });
|
|
617
|
+
requestAction.pipe(fs$1.createWriteStream(filename));
|
|
417
618
|
} else {
|
|
418
619
|
log(`[Copy 2] '${url}' to '${filename}'`);
|
|
419
|
-
requestAction =
|
|
420
|
-
requestAction.pipe(
|
|
620
|
+
requestAction = fs$1.createReadStream(path.join(options.dir || "", url));
|
|
621
|
+
requestAction.pipe(fs$1.createWriteStream(filename));
|
|
421
622
|
}
|
|
422
|
-
return new Promise((resolve,
|
|
623
|
+
return new Promise((resolve, _reject) => {
|
|
423
624
|
requestAction.on("error", (err) => {
|
|
424
625
|
log("[Download Error] Error while downloading: " + url);
|
|
425
626
|
log(err);
|
|
426
|
-
|
|
627
|
+
fs$1.unlinkSync(filename);
|
|
427
628
|
resolve();
|
|
428
629
|
});
|
|
429
630
|
requestAction.on("end", () => {
|
|
@@ -433,43 +634,339 @@ const downloadImage = async (epubData, options) => {
|
|
|
433
634
|
});
|
|
434
635
|
};
|
|
435
636
|
const downloadAllImages = async (epubData) => {
|
|
436
|
-
|
|
437
|
-
if (images.length === 0)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
for (let image of images) {
|
|
637
|
+
const { images } = epubData;
|
|
638
|
+
if (images.length === 0) return;
|
|
639
|
+
fs$1.ensureDirSync(path.join(epubData.dir, "OEBPS", "images"));
|
|
640
|
+
for (const image of images) {
|
|
441
641
|
await downloadImage(epubData, image);
|
|
442
642
|
}
|
|
443
643
|
};
|
|
644
|
+
const epub2_content_opf_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
645
|
+
<package xmlns="http://www.idpf.org/2007/opf"
|
|
646
|
+
version="2.0"
|
|
647
|
+
unique-identifier="BookId">
|
|
648
|
+
|
|
649
|
+
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
650
|
+
xmlns:opf="http://www.idpf.org/2007/opf">
|
|
651
|
+
|
|
652
|
+
<dc:identifier id="BookId" opf:scheme="URN"><%= id %></dc:identifier>
|
|
653
|
+
<dc:title><%= title %></dc:title>
|
|
654
|
+
<dc:description><%= description %></dc:description>
|
|
655
|
+
<dc:publisher><%= publisher || "anonymous" %></dc:publisher>
|
|
656
|
+
<dc:creator opf:role="aut" opf:file-as="<%= author.length ? author.join(",") : author %>"><%= author.length ? author.join(",") : author %></dc:creator>
|
|
657
|
+
<dc:date opf:event="modification"><%= date %></dc:date>
|
|
658
|
+
<dc:language><%= lang || "en" %></dc:language>
|
|
659
|
+
<meta name="cover" content="image_cover"/>
|
|
660
|
+
<meta name="generator" content="epub-gen"/>
|
|
661
|
+
|
|
662
|
+
</metadata>
|
|
663
|
+
|
|
664
|
+
<manifest>
|
|
665
|
+
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
|
|
666
|
+
<item id="toc" href="toc.xhtml" media-type="application/xhtml+xml"/>
|
|
667
|
+
<item id="css" href="style.css" media-type="text/css"/>
|
|
668
|
+
|
|
669
|
+
<% if(locals.cover) { %>
|
|
670
|
+
<item id="image_cover" href="cover.<%= _coverExtension %>" media-type="<%= _coverMediaType %>"/>
|
|
671
|
+
<% } %>
|
|
672
|
+
|
|
673
|
+
<% images.forEach(function(image, index){ %>
|
|
674
|
+
<item id="image_<%= index %>" href="images/<%= image.id %>.<%= image.extension %>" media-type="<%= image.mediaType %>"/>
|
|
675
|
+
<% }) %>
|
|
676
|
+
|
|
677
|
+
<% content.forEach(function(content, index){ %>
|
|
678
|
+
<item id="content_<%= index %>_<%= content.id %>" href="<%= content.href %>" media-type="application/xhtml+xml"/>
|
|
679
|
+
<% }) %>
|
|
680
|
+
|
|
681
|
+
<% fonts.forEach(function(font, index) { %>
|
|
682
|
+
<item id="font_<%= index %>" href="fonts/<%= font %>" media-type="application/x-font-ttf"/>
|
|
683
|
+
<% }) %>
|
|
684
|
+
</manifest>
|
|
685
|
+
|
|
686
|
+
<spine toc="ncx">
|
|
687
|
+
<% content.forEach(function(content, index){ %>
|
|
688
|
+
<% if(content.beforeToc && !content.excludeFromToc){ %>
|
|
689
|
+
<itemref idref="content_<%= index %>_<%= content.id %>"/>
|
|
690
|
+
<% } %>
|
|
691
|
+
<% }) %>
|
|
692
|
+
<itemref idref="toc"/>
|
|
693
|
+
<% content.forEach(function(content, index){ %>
|
|
694
|
+
<% if(!content.beforeToc && !content.excludeFromToc){ %>
|
|
695
|
+
<itemref idref="content_<%= index %>_<%= content.id %>"/>
|
|
696
|
+
<% } %>
|
|
697
|
+
<% }) %>
|
|
698
|
+
</spine>
|
|
699
|
+
<guide/>
|
|
700
|
+
</package>
|
|
701
|
+
`;
|
|
702
|
+
const epub2_toc_xhtml_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
703
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
704
|
+
<html xml:lang="<%- lang %>" xmlns="http://www.w3.org/1999/xhtml">
|
|
705
|
+
<head>
|
|
706
|
+
<title><%= title %></title>
|
|
707
|
+
<meta charset="UTF-8"/>
|
|
708
|
+
<link rel="stylesheet" type="text/css" href="style.css"/>
|
|
709
|
+
</head>
|
|
710
|
+
<body>
|
|
711
|
+
<h1 class="h1"><%= tocTitle %></h1>
|
|
712
|
+
<% content.forEach(function(content, index){ %>
|
|
713
|
+
<% if(!content.excludeFromToc){ %>
|
|
714
|
+
<p class="table-of-content">
|
|
715
|
+
<a href="<%= content.href %>"><%= (1 + index) + ". " + (content.title || "Chapter " + (1 + index)) %>
|
|
716
|
+
<% if(content.author.length){ %>
|
|
717
|
+
- <small class="toc-author"><%= content.author.join(",") %></small>
|
|
718
|
+
<% } %>
|
|
719
|
+
<% if(content.url){ %><span class="toc-link"><%= content.url %></span>
|
|
720
|
+
<% }else{ %><span class="toc-link"></span>
|
|
721
|
+
<% } %>
|
|
722
|
+
</a>
|
|
723
|
+
</p>
|
|
724
|
+
<% } %>
|
|
725
|
+
<% }) %>
|
|
726
|
+
</body>
|
|
727
|
+
</html>
|
|
728
|
+
`;
|
|
729
|
+
const epub3_content_opf_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
730
|
+
<package xmlns="http://www.idpf.org/2007/opf"
|
|
731
|
+
version="3.0"
|
|
732
|
+
unique-identifier="BookId"
|
|
733
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
734
|
+
xmlns:dcterms="http://purl.org/dc/terms/"
|
|
735
|
+
xml:lang="en"
|
|
736
|
+
xmlns:media="http://www.idpf.org/epub/vocab/overlays/#"
|
|
737
|
+
prefix="ibooks: http://vocabulary.itunes.apple.com/rdf/ibooks/vocabulary-extensions-1.0/">
|
|
738
|
+
|
|
739
|
+
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
|
|
740
|
+
|
|
741
|
+
<dc:identifier id="BookId"><%= id %></dc:identifier>
|
|
742
|
+
<meta refines="#BookId" property="identifier-type" scheme="onix:codelist5">22</meta>
|
|
743
|
+
<meta property="dcterms:identifier" id="meta-identifier">BookId</meta>
|
|
744
|
+
<dc:title><%= title %></dc:title>
|
|
745
|
+
<meta property="dcterms:title" id="meta-title"><%= title %></meta>
|
|
746
|
+
<dc:language><%= lang || "en" %></dc:language>
|
|
747
|
+
<meta property="dcterms:language" id="meta-language"><%= lang || "en" %></meta>
|
|
748
|
+
<meta property="dcterms:modified"><%= (new Date()).toISOString().split(".")[0] + "Z" %></meta>
|
|
749
|
+
<dc:creator id="creator"><%= author.length ? author.join(",") : author %></dc:creator>
|
|
750
|
+
<meta refines="#creator" property="file-as"><%= author.length ? author.join(",") : author %></meta>
|
|
751
|
+
<meta property="dcterms:publisher"><%= publisher || "anonymous" %></meta>
|
|
752
|
+
<dc:publisher><%= publisher || "anonymous" %></dc:publisher>
|
|
753
|
+
<% var date = new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var stringDate = "" + year + "-" + month + "-" + day; %>
|
|
754
|
+
<meta property="dcterms:date"><%= stringDate %></meta>
|
|
755
|
+
<dc:date><%= stringDate %></dc:date>
|
|
756
|
+
<meta property="dcterms:rights">All rights reserved</meta>
|
|
757
|
+
<dc:rights>Copyright © <%= (new Date()).getFullYear() %> by <%= publisher || "anonymous" %></dc:rights>
|
|
758
|
+
<% if(locals.cover) { %>
|
|
759
|
+
<meta name="cover" content="image_cover" />
|
|
760
|
+
<% } %>
|
|
761
|
+
<meta name="generator" content="epub-gen" />
|
|
762
|
+
<meta property="ibooks:specified-fonts">true</meta>
|
|
763
|
+
</metadata>
|
|
764
|
+
|
|
765
|
+
<manifest>
|
|
766
|
+
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
|
|
767
|
+
<item id="toc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav" />
|
|
768
|
+
<item id="css" href="style.css" media-type="text/css" />
|
|
769
|
+
|
|
770
|
+
<% if(locals.cover) { %>
|
|
771
|
+
<item id="image_cover" href="cover.<%= _coverExtension %>" media-type="<%= _coverMediaType %>" />
|
|
772
|
+
<% } %>
|
|
773
|
+
|
|
774
|
+
<% images.forEach(function(image, index){ %>
|
|
775
|
+
<item id="image_<%= index %>" href="images/<%= image.id %>.<%= image.extension %>" media-type="<%= image.mediaType %>" />
|
|
776
|
+
<% }) %>
|
|
777
|
+
|
|
778
|
+
<% function renderContentItem(content) { %>
|
|
779
|
+
<% content.forEach(function(content){ %>
|
|
780
|
+
<item id="content_<%= content.id %>" href="<%= content.href %>" media-type="application/xhtml+xml" />
|
|
781
|
+
<% if (Array.isArray(content.children)) { %>
|
|
782
|
+
<% renderContentItem(content.children) %>
|
|
783
|
+
<% } %>
|
|
784
|
+
<% }) %>
|
|
785
|
+
<% } %>
|
|
786
|
+
<% renderContentItem(content) %>
|
|
787
|
+
|
|
788
|
+
<% fonts.forEach(function(font, index){ %>
|
|
789
|
+
<item id="font_<%= index %>" href="fonts/<%= font %>" media-type="application/x-font-ttf" />
|
|
790
|
+
<% }) %>
|
|
791
|
+
</manifest>
|
|
792
|
+
|
|
793
|
+
<spine toc="ncx">
|
|
794
|
+
<% var nodes_1 = content.filter(item => !item.excludeFromToc && item.beforeToc) %>
|
|
795
|
+
<% var nodes_2 = content.filter(item => !item.excludeFromToc && !item.beforeToc) %>
|
|
796
|
+
<% function renderToc(nodes) { %>
|
|
797
|
+
<% nodes.forEach(function(content){ %>
|
|
798
|
+
<itemref idref="content_<%= content.id %>" />
|
|
799
|
+
<% if (Array.isArray(content.children)) { %>
|
|
800
|
+
<% renderToc(content.children) %>
|
|
801
|
+
<% } %>
|
|
802
|
+
<% }) %>
|
|
803
|
+
<% } %>
|
|
804
|
+
<% renderToc(nodes_1) %>
|
|
805
|
+
<itemref idref="toc" />
|
|
806
|
+
<% renderToc(nodes_2) %>
|
|
807
|
+
</spine>
|
|
808
|
+
<guide>
|
|
809
|
+
<reference type="text" title="Table of Content" href="toc.xhtml" />
|
|
810
|
+
</guide>
|
|
811
|
+
</package>
|
|
812
|
+
`;
|
|
813
|
+
const epub3_toc_xhtml_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
814
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
815
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="<%- lang %>"
|
|
816
|
+
lang="<%- lang %>">
|
|
817
|
+
<head>
|
|
818
|
+
<title><%= title %></title>
|
|
819
|
+
<meta charset="UTF-8" />
|
|
820
|
+
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
821
|
+
</head>
|
|
822
|
+
<body>
|
|
823
|
+
<h1 class="h1"><%= tocTitle %></h1>
|
|
824
|
+
<nav id="toc" class="TOC" epub:type="toc">
|
|
825
|
+
<% var nodes_1 = content.filter(item => !item.excludeFromToc && item.beforeToc) %>
|
|
826
|
+
<% var nodes_2 = content.filter(item => !item.excludeFromToc && !item.beforeToc) %>
|
|
827
|
+
<% function renderToc(nodes, indent = 0) { %>
|
|
828
|
+
<ol>
|
|
829
|
+
<% nodes.forEach(function(content, index){ %>
|
|
830
|
+
<li class="table-of-content">
|
|
831
|
+
<a href="<%= content.href %>"><%= (content.title || "Chapter " + (1 + index)) %>
|
|
832
|
+
<% if(content.author.length){ %> - <small
|
|
833
|
+
class="toc-author"><%= content.author.join(",") %></small>
|
|
834
|
+
<% } %>
|
|
835
|
+
<% if(content.url){ %><span class="toc-link"><%= content.url %></span>
|
|
836
|
+
<% } %>
|
|
837
|
+
</a>
|
|
838
|
+
<% if (Array.isArray(content.children) && content.children.length > 0) { %>
|
|
839
|
+
<% renderToc(content.children, indent + 1) %>
|
|
840
|
+
<% } %>
|
|
841
|
+
</li>
|
|
842
|
+
<% }) %>
|
|
843
|
+
</ol>
|
|
844
|
+
<% } %>
|
|
845
|
+
<% renderToc(nodes_1) %>
|
|
846
|
+
<% renderToc(nodes_2) %>
|
|
847
|
+
</nav>
|
|
848
|
+
|
|
849
|
+
</body>
|
|
850
|
+
</html>
|
|
851
|
+
`;
|
|
852
|
+
const template_css = `.epub-author {
|
|
853
|
+
color: #555;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.epub-link {
|
|
857
|
+
margin-bottom: 30px;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.epub-link a {
|
|
861
|
+
color: #666;
|
|
862
|
+
font-size: 90%;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.toc-author {
|
|
866
|
+
font-size: 90%;
|
|
867
|
+
color: #555;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.toc-link {
|
|
871
|
+
color: #999;
|
|
872
|
+
font-size: 85%;
|
|
873
|
+
display: block;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
hr {
|
|
877
|
+
border: 0;
|
|
878
|
+
border-bottom: 1px solid #dedede;
|
|
879
|
+
margin: 60px 10%;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.TOC > ol {
|
|
883
|
+
margin: 0;
|
|
884
|
+
padding: 0;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.TOC > ol ol {
|
|
888
|
+
padding: 0;
|
|
889
|
+
margin-left: 2em;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.TOC li {
|
|
893
|
+
font-size: 16px;
|
|
894
|
+
list-style: none;
|
|
895
|
+
margin: 0 auto;
|
|
896
|
+
padding: 0;
|
|
897
|
+
}
|
|
898
|
+
`;
|
|
899
|
+
const toc_ncx_ejs = `<?xml version="1.0" encoding="UTF-8"?>
|
|
900
|
+
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
|
|
901
|
+
<head>
|
|
902
|
+
<meta name="dtb:uid" content="<%= id %>"/>
|
|
903
|
+
<meta name="dtb:generator" content="epub-gen"/>
|
|
904
|
+
<meta name="dtb:depth" content="<%= (toc_depth || 1)%>"/>
|
|
905
|
+
<meta name="dtb:totalPageCount" content="0"/>
|
|
906
|
+
<meta name="dtb:maxPageNumber" content="0"/>
|
|
907
|
+
</head>
|
|
908
|
+
<docTitle>
|
|
909
|
+
<text><%= title %></text>
|
|
910
|
+
</docTitle>
|
|
911
|
+
<docAuthor>
|
|
912
|
+
<text><%= author %></text>
|
|
913
|
+
</docAuthor>
|
|
914
|
+
<navMap>
|
|
915
|
+
<% var _index = 1; %>
|
|
916
|
+
<% var nodes_1 = content.filter(c => !c.excludeFromToc && c.beforeToc) %>
|
|
917
|
+
<% var nodes_2 = content.filter(c => !c.excludeFromToc && !c.beforeToc) %>
|
|
918
|
+
<% function renderToc(nodes) { %>
|
|
919
|
+
<% nodes.forEach(function(content, index){ %>
|
|
920
|
+
<navPoint id="content_<%= content.id %>" playOrder="<%= _index++ %>" class="chapter">
|
|
921
|
+
<navLabel>
|
|
922
|
+
<text><%= (tocAutoNumber ? ((1 + index) + ". ") : "") + (content.title || "Chapter " + (1 + index)) %></text>
|
|
923
|
+
</navLabel>
|
|
924
|
+
<content src="<%= content.href %>"/>
|
|
925
|
+
<% if (Array.isArray(content.children)) { %>
|
|
926
|
+
<% renderToc(content.children) %>
|
|
927
|
+
<% } %>
|
|
928
|
+
</navPoint>
|
|
929
|
+
<% }) %>
|
|
930
|
+
<% } %>
|
|
931
|
+
|
|
932
|
+
<% renderToc(nodes_1) %>
|
|
933
|
+
|
|
934
|
+
<navPoint id="toc" playOrder="<%= _index++ %>" class="chapter">
|
|
935
|
+
<navLabel>
|
|
936
|
+
<text><%= tocTitle %></text>
|
|
937
|
+
</navLabel>
|
|
938
|
+
<content src="toc.xhtml"/>
|
|
939
|
+
</navPoint>
|
|
940
|
+
|
|
941
|
+
<% renderToc(nodes_2) %>
|
|
942
|
+
</navMap>
|
|
943
|
+
</ncx>
|
|
944
|
+
`;
|
|
444
945
|
const generateTempFile = async (epubData) => {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
let fonts_dir = path__namespace.join(oebps_dir, "fonts");
|
|
454
|
-
await fs__namespace$1.ensureDir(fonts_dir);
|
|
946
|
+
const { log } = epubData;
|
|
947
|
+
const oebps_dir = path.join(epubData.dir, "OEBPS");
|
|
948
|
+
await fs$1.ensureDir(oebps_dir);
|
|
949
|
+
epubData.css = epubData.css || template_css;
|
|
950
|
+
await writeFile(path.join(oebps_dir, "style.css"), epubData.css, "utf-8");
|
|
951
|
+
if (epubData.fonts?.length) {
|
|
952
|
+
const fonts_dir = path.join(oebps_dir, "fonts");
|
|
953
|
+
await fs$1.ensureDir(fonts_dir);
|
|
455
954
|
epubData.fonts = epubData.fonts.map((font) => {
|
|
456
|
-
|
|
457
|
-
if (!
|
|
955
|
+
const filename = path.basename(font);
|
|
956
|
+
if (!fs$1.existsSync(font)) {
|
|
458
957
|
log(`Custom font not found at '${font}'.`);
|
|
459
958
|
} else {
|
|
460
|
-
|
|
959
|
+
fs$1.copySync(font, path.join(fonts_dir, filename));
|
|
461
960
|
}
|
|
462
961
|
return filename;
|
|
463
962
|
});
|
|
464
963
|
}
|
|
465
964
|
const isAppendTitle = (global_append, local_append) => {
|
|
466
|
-
if (typeof local_append === "boolean")
|
|
467
|
-
return local_append;
|
|
965
|
+
if (typeof local_append === "boolean") return local_append;
|
|
468
966
|
return !!global_append;
|
|
469
967
|
};
|
|
470
968
|
const saveContentToFile = (content) => {
|
|
471
|
-
|
|
472
|
-
let title = entities__namespace.encodeXML(content.title || "");
|
|
969
|
+
const title = entities.encodeXML(content.title || "");
|
|
473
970
|
let html = `${epubData.docHeader}
|
|
474
971
|
<head>
|
|
475
972
|
<meta charset="UTF-8" />
|
|
@@ -481,21 +978,21 @@ const generateTempFile = async (epubData) => {
|
|
|
481
978
|
if (content.title && isAppendTitle(epubData.appendChapterTitles, content.appendChapterTitle)) {
|
|
482
979
|
html += `<h1>${title}</h1>`;
|
|
483
980
|
}
|
|
484
|
-
html += content.title && content.author &&
|
|
981
|
+
html += content.title && content.author && content.author?.length ? `<p class='epub-author'>${entities.encodeXML(content.author.join(", "))}</p>` : "";
|
|
485
982
|
html += content.title && content.url ? `<p class="epub-link"><a href="${content.url}">${content.url}</a></p>` : "";
|
|
486
983
|
html += `${content.data}`;
|
|
487
984
|
html += "\n</body>\n</html>";
|
|
488
|
-
|
|
489
|
-
|
|
985
|
+
fs$1.ensureDirSync(path.dirname(content.filePath));
|
|
986
|
+
fs$1.writeFileSync(content.filePath, html, "utf-8");
|
|
490
987
|
if (Array.isArray(content.children)) {
|
|
491
988
|
content.children.map(saveContentToFile);
|
|
492
989
|
}
|
|
493
990
|
};
|
|
494
991
|
epubData.content.map(saveContentToFile);
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
992
|
+
const metainf_dir = path.join(epubData.dir, "META-INF");
|
|
993
|
+
fs$1.ensureDirSync(metainf_dir);
|
|
994
|
+
fs$1.writeFileSync(
|
|
995
|
+
path.join(metainf_dir, "container.xml"),
|
|
499
996
|
`<?xml version="1.0" encoding="UTF-8" ?>
|
|
500
997
|
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
|
501
998
|
<rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles>
|
|
@@ -503,8 +1000,8 @@ const generateTempFile = async (epubData) => {
|
|
|
503
1000
|
"utf-8"
|
|
504
1001
|
);
|
|
505
1002
|
if (epubData.version === 2) {
|
|
506
|
-
|
|
507
|
-
|
|
1003
|
+
const fn = path.join(metainf_dir, "com.apple.ibooks.display-options.xml");
|
|
1004
|
+
fs$1.writeFileSync(
|
|
508
1005
|
fn,
|
|
509
1006
|
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
510
1007
|
<display_options>
|
|
@@ -516,50 +1013,53 @@ const generateTempFile = async (epubData) => {
|
|
|
516
1013
|
"utf-8"
|
|
517
1014
|
);
|
|
518
1015
|
}
|
|
519
|
-
let
|
|
520
|
-
|
|
521
|
-
|
|
1016
|
+
let opfTemplate;
|
|
1017
|
+
let ncxTocTemplate;
|
|
1018
|
+
let htmlTocTemplate;
|
|
1019
|
+
if (epubData.customOpfTemplatePath && fs$1.existsSync(epubData.customOpfTemplatePath)) {
|
|
1020
|
+
const { readFile } = require("./libs/utils");
|
|
1021
|
+
opfTemplate = await readFile(epubData.customOpfTemplatePath, "utf-8");
|
|
1022
|
+
} else {
|
|
1023
|
+
opfTemplate = epubData.version === 2 ? epub2_content_opf_ejs : epub3_content_opf_ejs;
|
|
522
1024
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
1025
|
+
if (epubData.customNcxTocTemplatePath && fs$1.existsSync(epubData.customNcxTocTemplatePath)) {
|
|
1026
|
+
const { readFile } = require("./libs/utils");
|
|
1027
|
+
ncxTocTemplate = await readFile(epubData.customNcxTocTemplatePath, "utf-8");
|
|
1028
|
+
} else {
|
|
1029
|
+
ncxTocTemplate = toc_ncx_ejs;
|
|
526
1030
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
1031
|
+
if (epubData.customHtmlTocTemplatePath && fs$1.existsSync(epubData.customHtmlTocTemplatePath)) {
|
|
1032
|
+
const { readFile } = require("./libs/utils");
|
|
1033
|
+
htmlTocTemplate = await readFile(epubData.customHtmlTocTemplatePath, "utf-8");
|
|
1034
|
+
} else {
|
|
1035
|
+
htmlTocTemplate = epubData.version === 2 ? epub2_toc_xhtml_ejs : epub3_toc_xhtml_ejs;
|
|
530
1036
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
);
|
|
537
|
-
fs__namespace$1.writeFileSync(
|
|
538
|
-
path__namespace.join(oebps_dir, "toc.ncx"),
|
|
539
|
-
await ejs__namespace.renderFile(ncxTocPath, { ...epubData, toc_depth }),
|
|
1037
|
+
const toc_depth = 1;
|
|
1038
|
+
fs$1.writeFileSync(path.join(oebps_dir, "content.opf"), ejs.render(opfTemplate, epubData), "utf-8");
|
|
1039
|
+
fs$1.writeFileSync(
|
|
1040
|
+
path.join(oebps_dir, "toc.ncx"),
|
|
1041
|
+
ejs.render(ncxTocTemplate, { ...epubData, toc_depth }),
|
|
540
1042
|
"utf-8"
|
|
541
1043
|
);
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
simpleMinifier(
|
|
1044
|
+
fs$1.writeFileSync(
|
|
1045
|
+
path.join(oebps_dir, "toc.xhtml"),
|
|
1046
|
+
simpleMinifier(ejs.render(htmlTocTemplate, epubData)),
|
|
545
1047
|
"utf-8"
|
|
546
1048
|
);
|
|
547
1049
|
};
|
|
548
1050
|
async function makeCover(data) {
|
|
549
|
-
|
|
550
|
-
if (!cover)
|
|
551
|
-
|
|
552
|
-
let destPath = path__namespace.join(data.dir, "OEBPS", `cover.${_coverExtension}`);
|
|
1051
|
+
const { cover, _coverExtension, log } = data;
|
|
1052
|
+
if (!cover) return;
|
|
1053
|
+
const destPath = path.join(data.dir, "OEBPS", `cover.${_coverExtension}`);
|
|
553
1054
|
let writeStream = null;
|
|
554
1055
|
if (cover.startsWith("http")) {
|
|
555
|
-
writeStream =
|
|
556
|
-
writeStream.pipe(
|
|
1056
|
+
writeStream = request.get(cover).set({ "User-Agent": USER_AGENT });
|
|
1057
|
+
writeStream.pipe(fs$1.createWriteStream(destPath));
|
|
557
1058
|
} else {
|
|
558
|
-
if (!
|
|
559
|
-
return;
|
|
1059
|
+
if (!fs$1.existsSync(cover)) return;
|
|
560
1060
|
log("local cover image: " + cover);
|
|
561
|
-
writeStream =
|
|
562
|
-
writeStream.pipe(
|
|
1061
|
+
writeStream = fs$1.createReadStream(cover);
|
|
1062
|
+
writeStream.pipe(fs$1.createWriteStream(destPath));
|
|
563
1063
|
}
|
|
564
1064
|
return new Promise((resolve) => {
|
|
565
1065
|
writeStream.on("end", () => {
|
|
@@ -569,9 +1069,9 @@ async function makeCover(data) {
|
|
|
569
1069
|
writeStream.on("error", (e) => {
|
|
570
1070
|
log("[Error] cover image error: " + e.message);
|
|
571
1071
|
log("destPath: " + destPath);
|
|
572
|
-
if (
|
|
1072
|
+
if (fs$1.existsSync(destPath)) {
|
|
573
1073
|
try {
|
|
574
|
-
|
|
1074
|
+
fs$1.unlinkSync(destPath);
|
|
575
1075
|
log("destPath removed.");
|
|
576
1076
|
} catch (e2) {
|
|
577
1077
|
log("[Error] remove cover image error: " + e2.message);
|
|
@@ -582,7 +1082,7 @@ async function makeCover(data) {
|
|
|
582
1082
|
});
|
|
583
1083
|
}
|
|
584
1084
|
async function render(data) {
|
|
585
|
-
|
|
1085
|
+
const { log } = data;
|
|
586
1086
|
log("Generating Template Files...");
|
|
587
1087
|
await generateTempFile(data);
|
|
588
1088
|
log("Downloading Images...");
|
|
@@ -599,14 +1099,14 @@ async function render(data) {
|
|
|
599
1099
|
}
|
|
600
1100
|
}
|
|
601
1101
|
async function genEpub(epubData) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1102
|
+
const { log, dir, output } = epubData;
|
|
1103
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1104
|
+
const outputStream = fs$1.createWriteStream(epubData.output);
|
|
605
1105
|
log("Zipping temp dir to " + output);
|
|
606
1106
|
return new Promise((resolve, reject) => {
|
|
607
1107
|
archive.on("end", async () => {
|
|
608
1108
|
log("Done zipping, clearing temp dir...");
|
|
609
|
-
|
|
1109
|
+
const stable = await fileIsStable(epubData.output);
|
|
610
1110
|
if (!stable) {
|
|
611
1111
|
log("Output epub file is not stable!");
|
|
612
1112
|
}
|
|
@@ -635,14 +1135,18 @@ async function genEpub(epubData) {
|
|
|
635
1135
|
archive.finalize();
|
|
636
1136
|
});
|
|
637
1137
|
}
|
|
638
|
-
const
|
|
1138
|
+
const mimeModule = require("mime/lite");
|
|
1139
|
+
const mime = mimeModule.default || mimeModule;
|
|
1140
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1141
|
+
const __dirname = path.dirname(__filename);
|
|
1142
|
+
const baseDir = __dirname;
|
|
639
1143
|
function result(success, message, options) {
|
|
640
1144
|
if (options && options.verbose) {
|
|
641
1145
|
if (!success) {
|
|
642
|
-
|
|
1146
|
+
logger.error(new Error(message));
|
|
643
1147
|
}
|
|
644
1148
|
}
|
|
645
|
-
|
|
1149
|
+
const out = {
|
|
646
1150
|
success
|
|
647
1151
|
};
|
|
648
1152
|
if (typeof message === "string") {
|
|
@@ -666,9 +1170,9 @@ function check(options) {
|
|
|
666
1170
|
return result(true, void 0, options);
|
|
667
1171
|
}
|
|
668
1172
|
function parseOptions(options) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1173
|
+
const tmpDir = options.tmpDir || os.tmpdir();
|
|
1174
|
+
const id = v4();
|
|
1175
|
+
const data = {
|
|
672
1176
|
description: options.title,
|
|
673
1177
|
publisher: "anonymous",
|
|
674
1178
|
author: ["anonymous"],
|
|
@@ -685,12 +1189,12 @@ function parseOptions(options) {
|
|
|
685
1189
|
...options,
|
|
686
1190
|
id,
|
|
687
1191
|
tmpDir,
|
|
688
|
-
dir:
|
|
1192
|
+
dir: path.resolve(tmpDir, id),
|
|
689
1193
|
baseDir,
|
|
690
1194
|
docHeader: "",
|
|
691
1195
|
images: [],
|
|
692
1196
|
content: [],
|
|
693
|
-
log: (msg) => options.verbose &&
|
|
1197
|
+
log: (msg) => options.verbose && logger.log(msg)
|
|
694
1198
|
};
|
|
695
1199
|
if (data.version === 2) {
|
|
696
1200
|
data.docHeader = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -699,7 +1203,7 @@ function parseOptions(options) {
|
|
|
699
1203
|
`;
|
|
700
1204
|
} else {
|
|
701
1205
|
data.docHeader = `<?xml version="1.0" encoding="UTF-8"?>
|
|
702
|
-
<!DOCTYPE html>
|
|
1206
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
|
703
1207
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="#{self.options.lang}">
|
|
704
1208
|
`;
|
|
705
1209
|
}
|
|
@@ -711,48 +1215,46 @@ function parseOptions(options) {
|
|
|
711
1215
|
}
|
|
712
1216
|
data.content = options.content.map((content, index) => parseContent(content, index, data));
|
|
713
1217
|
if (data.cover) {
|
|
714
|
-
data._coverMediaType =
|
|
715
|
-
data._coverExtension =
|
|
1218
|
+
data._coverMediaType = mime.getType(data.cover) || "";
|
|
1219
|
+
data._coverExtension = mime.getExtension(data._coverMediaType) || "";
|
|
716
1220
|
}
|
|
717
1221
|
return data;
|
|
718
1222
|
}
|
|
719
|
-
async function epubGen(options,
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
options.output = output;
|
|
1223
|
+
async function epubGen(options, configs) {
|
|
1224
|
+
if (configs?.logger) {
|
|
1225
|
+
logger.setLogger(configs.logger);
|
|
723
1226
|
}
|
|
724
|
-
|
|
725
|
-
|
|
1227
|
+
options = { ...options };
|
|
1228
|
+
const o = check(options);
|
|
1229
|
+
const verbose = options.verbose !== false;
|
|
726
1230
|
if (!o.success) {
|
|
727
|
-
if (verbose)
|
|
728
|
-
console.error(o.message);
|
|
1231
|
+
if (verbose) logger.error(o.message);
|
|
729
1232
|
return o;
|
|
730
1233
|
}
|
|
731
1234
|
let t;
|
|
732
1235
|
try {
|
|
733
|
-
|
|
734
|
-
|
|
1236
|
+
const data = parseOptions(options);
|
|
1237
|
+
const timeoutSeconds = data.timeoutSeconds || 0;
|
|
735
1238
|
if (timeoutSeconds > 0) {
|
|
736
|
-
if (verbose)
|
|
737
|
-
console.log(`TIMEOUT: ${timeoutSeconds}s`);
|
|
1239
|
+
if (verbose) logger.log(`TIMEOUT: ${timeoutSeconds}s`);
|
|
738
1240
|
t = setTimeout(() => {
|
|
739
1241
|
throw new Error("timeout!");
|
|
740
1242
|
}, timeoutSeconds * 1e3);
|
|
741
1243
|
} else {
|
|
742
|
-
if (verbose)
|
|
743
|
-
console.log(`TIMEOUT: N/A`);
|
|
1244
|
+
if (verbose) logger.log(`TIMEOUT: N/A`);
|
|
744
1245
|
}
|
|
745
1246
|
await render(data);
|
|
746
1247
|
return result(true, void 0, data);
|
|
747
1248
|
} catch (e) {
|
|
748
|
-
if (verbose)
|
|
749
|
-
console.error(e);
|
|
1249
|
+
if (verbose) logger.error(e);
|
|
750
1250
|
return result(false, e.message, options);
|
|
751
1251
|
} finally {
|
|
752
1252
|
clearTimeout(t);
|
|
753
1253
|
}
|
|
754
1254
|
}
|
|
755
1255
|
const gen = epubGen;
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1256
|
+
export {
|
|
1257
|
+
epubGen,
|
|
1258
|
+
errors,
|
|
1259
|
+
gen
|
|
1260
|
+
};
|