@epubook/core 0.0.2
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/chunks/index.cjs +188 -0
- package/dist/chunks/index.mjs +170 -0
- package/dist/index.cjs +26 -0
- package/dist/index.d.ts +212 -0
- package/dist/index.mjs +7 -0
- package/dist/shared/core.2c388ac3.cjs +490 -0
- package/dist/shared/core.c365bf12.mjs +461 -0
- package/package.json +55 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fastXmlParser = require('fast-xml-parser');
|
|
4
|
+
const path = require('pathe');
|
|
5
|
+
const node_fs = require('node:fs');
|
|
6
|
+
const node_crypto = require('node:crypto');
|
|
7
|
+
const defu$1 = require('defu');
|
|
8
|
+
const fflate = require('fflate');
|
|
9
|
+
|
|
10
|
+
function _interopNamespaceDefault(e) {
|
|
11
|
+
const n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
for (const k in e) {
|
|
14
|
+
n[k] = e[k];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
n.default = e;
|
|
18
|
+
return n;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
22
|
+
|
|
23
|
+
const MIMETYPE = "application/epub+zip";
|
|
24
|
+
const ImageJpeg = "image/jpeg";
|
|
25
|
+
const ImagePng = "image/png";
|
|
26
|
+
const ImageWebp = "image/webp";
|
|
27
|
+
const TextCSS = "text/css";
|
|
28
|
+
const XHTML = "application/xhtml+xml";
|
|
29
|
+
|
|
30
|
+
const builder = new fastXmlParser.XMLBuilder({
|
|
31
|
+
format: true,
|
|
32
|
+
ignoreAttributes: false,
|
|
33
|
+
suppressUnpairedNode: false,
|
|
34
|
+
unpairedTags: ["link"]
|
|
35
|
+
});
|
|
36
|
+
class XHTMLBuilder {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.info = {
|
|
39
|
+
language: "en",
|
|
40
|
+
title: ""
|
|
41
|
+
};
|
|
42
|
+
this._head = [];
|
|
43
|
+
this._body = [];
|
|
44
|
+
}
|
|
45
|
+
language(value) {
|
|
46
|
+
this.info.language = value;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
title(value) {
|
|
50
|
+
this.info.title = value;
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
style(href) {
|
|
54
|
+
this._head.push({
|
|
55
|
+
tag: "link",
|
|
56
|
+
attrs: {
|
|
57
|
+
href,
|
|
58
|
+
rel: "stylesheet",
|
|
59
|
+
type: TextCSS
|
|
60
|
+
},
|
|
61
|
+
children: [""]
|
|
62
|
+
});
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
body(node) {
|
|
66
|
+
this._body.push(node);
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
build() {
|
|
70
|
+
function build(node) {
|
|
71
|
+
const attrs = Object.fromEntries(
|
|
72
|
+
Object.entries(node.attrs ?? {}).map(([key, value]) => ["@_" + key, value])
|
|
73
|
+
);
|
|
74
|
+
const obj = {
|
|
75
|
+
...attrs
|
|
76
|
+
};
|
|
77
|
+
if (Array.isArray(node.children)) {
|
|
78
|
+
const text = node.children.filter((c) => typeof c === "string");
|
|
79
|
+
const nodes = node.children.filter((c) => typeof c !== "string");
|
|
80
|
+
if (text.length > 0) {
|
|
81
|
+
obj["#text"] = text[0];
|
|
82
|
+
}
|
|
83
|
+
Object.assign(obj, list(nodes));
|
|
84
|
+
}
|
|
85
|
+
return obj;
|
|
86
|
+
}
|
|
87
|
+
function list(nodes) {
|
|
88
|
+
const obj = {};
|
|
89
|
+
for (const c of nodes) {
|
|
90
|
+
if (c.tag in obj) {
|
|
91
|
+
obj[c.tag].push(build(c));
|
|
92
|
+
} else {
|
|
93
|
+
obj[c.tag] = [build(c)];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return obj;
|
|
97
|
+
}
|
|
98
|
+
return builder.build({
|
|
99
|
+
html: {
|
|
100
|
+
"@_xmlns": "http://www.w3.org/1999/xhtml",
|
|
101
|
+
"@_xmlns:epub": "http://www.idpf.org/2007/ops",
|
|
102
|
+
"@_lang": this.info.language,
|
|
103
|
+
"@_xml:lang": this.info.language,
|
|
104
|
+
head: {
|
|
105
|
+
title: this.info.title,
|
|
106
|
+
...list(this._head)
|
|
107
|
+
},
|
|
108
|
+
body: list(this._body)
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const Fragment = "Fragment";
|
|
114
|
+
function h(tag, attrs = {}, ...children) {
|
|
115
|
+
const sub = children.flatMap(
|
|
116
|
+
(c) => typeof c === "object" && !Array.isArray(c) && c.tag === Fragment ? c.children ?? [] : c
|
|
117
|
+
).filter((c) => c !== void 0 && c !== null && c !== false);
|
|
118
|
+
const o = {
|
|
119
|
+
tag,
|
|
120
|
+
attrs: attrs ?? {},
|
|
121
|
+
children: sub
|
|
122
|
+
};
|
|
123
|
+
return o;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildTocNav(nav) {
|
|
127
|
+
const builder = new XHTMLBuilder();
|
|
128
|
+
const root = {
|
|
129
|
+
tag: "nav",
|
|
130
|
+
attrs: {
|
|
131
|
+
"epub:type": "toc"
|
|
132
|
+
},
|
|
133
|
+
children: []
|
|
134
|
+
};
|
|
135
|
+
if (nav.title && nav.heading) {
|
|
136
|
+
root.children.push(h("h" + nav.heading, {}, nav.title));
|
|
137
|
+
}
|
|
138
|
+
root.children.push(/* @__PURE__ */ h("ol", null, list(nav.list)));
|
|
139
|
+
return builder.title(nav.title ?? "Toc").body(root);
|
|
140
|
+
function list(items) {
|
|
141
|
+
return items.map((i) => /* @__PURE__ */ h("li", null, i.href ? /* @__PURE__ */ h("a", { href: i.href }, i.text) : /* @__PURE__ */ h("span", null, i.text), "list" in i && i.list && /* @__PURE__ */ h("ol", null, list(i.list))));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class ManifestItem {
|
|
146
|
+
constructor(href, id) {
|
|
147
|
+
this.optional = {};
|
|
148
|
+
this._href = href;
|
|
149
|
+
this._id = id;
|
|
150
|
+
}
|
|
151
|
+
update(info) {
|
|
152
|
+
for (const [key, value] of Object.entries(info)) {
|
|
153
|
+
if (!!value) {
|
|
154
|
+
this.optional[key] = value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
href() {
|
|
160
|
+
return this._href;
|
|
161
|
+
}
|
|
162
|
+
id() {
|
|
163
|
+
return this._id;
|
|
164
|
+
}
|
|
165
|
+
fallback() {
|
|
166
|
+
return this.optional.fallback;
|
|
167
|
+
}
|
|
168
|
+
mediaOverlay() {
|
|
169
|
+
return this.optional.mediaOverlay;
|
|
170
|
+
}
|
|
171
|
+
mediaType() {
|
|
172
|
+
return this.optional.mediaType;
|
|
173
|
+
}
|
|
174
|
+
properties() {
|
|
175
|
+
return this.optional.properties;
|
|
176
|
+
}
|
|
177
|
+
ref() {
|
|
178
|
+
return new ManifestItemRef(this._id);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
class ManifestItemRef {
|
|
182
|
+
constructor(idref) {
|
|
183
|
+
this.optional = {};
|
|
184
|
+
this._idref = idref;
|
|
185
|
+
}
|
|
186
|
+
update(info) {
|
|
187
|
+
for (const [key, value] of Object.entries(info)) {
|
|
188
|
+
if (!!value) {
|
|
189
|
+
this.optional[key] = value;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
idref() {
|
|
195
|
+
return this._idref;
|
|
196
|
+
}
|
|
197
|
+
id() {
|
|
198
|
+
return this.optional.id;
|
|
199
|
+
}
|
|
200
|
+
linear() {
|
|
201
|
+
return this.optional.linear;
|
|
202
|
+
}
|
|
203
|
+
properties() {
|
|
204
|
+
return this.optional.properties;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
class Item {
|
|
208
|
+
constructor(file, mediaType) {
|
|
209
|
+
this.file = file;
|
|
210
|
+
this.mediaType = mediaType;
|
|
211
|
+
}
|
|
212
|
+
filename() {
|
|
213
|
+
return this.file;
|
|
214
|
+
}
|
|
215
|
+
update(info) {
|
|
216
|
+
if (info.properties) {
|
|
217
|
+
this._properties = info.properties;
|
|
218
|
+
}
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
id() {
|
|
222
|
+
return this.file.replace(/\/|\\/g, "_").replace(/\.[\w]+$/, "");
|
|
223
|
+
}
|
|
224
|
+
manifest() {
|
|
225
|
+
return new ManifestItem(this.file, this.id()).update({
|
|
226
|
+
mediaType: this.mediaType,
|
|
227
|
+
properties: this._properties
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
itemref() {
|
|
231
|
+
return this.manifest().ref();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
class Html extends Item {
|
|
235
|
+
constructor(file, content) {
|
|
236
|
+
super(file, XHTML);
|
|
237
|
+
this.content = content;
|
|
238
|
+
}
|
|
239
|
+
static async read(src, dst) {
|
|
240
|
+
if (!src.endsWith(".xhtml")) {
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
const content = await node_fs.promises.readFile(src, "utf-8");
|
|
244
|
+
return new Html(dst, content);
|
|
245
|
+
}
|
|
246
|
+
async bundle() {
|
|
247
|
+
return fflate.strToU8(this.content);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
class Style extends Item {
|
|
251
|
+
constructor(file, content) {
|
|
252
|
+
super(file, TextCSS);
|
|
253
|
+
this.content = content;
|
|
254
|
+
}
|
|
255
|
+
static async read(src, dst) {
|
|
256
|
+
if (!src.endsWith(".css")) {
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
const content = await node_fs.promises.readFile(src, "utf-8");
|
|
260
|
+
return new Style(dst, content);
|
|
261
|
+
}
|
|
262
|
+
async bundle() {
|
|
263
|
+
return fflate.strToU8(this.content);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
class Image extends Item {
|
|
267
|
+
constructor(file, type, data) {
|
|
268
|
+
super(file, type);
|
|
269
|
+
this.data = data;
|
|
270
|
+
}
|
|
271
|
+
static async read(src, dst) {
|
|
272
|
+
const content = await node_fs.promises.readFile(src);
|
|
273
|
+
const media = this.getExtMediaType(src);
|
|
274
|
+
if (media) {
|
|
275
|
+
return new Image(dst, media, content);
|
|
276
|
+
} else {
|
|
277
|
+
return void 0;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async bundle() {
|
|
281
|
+
return this.data;
|
|
282
|
+
}
|
|
283
|
+
static getExtMediaType(file) {
|
|
284
|
+
const ext = path__namespace.extname(file);
|
|
285
|
+
if (ext === "jpg" || ext === "jpeg") {
|
|
286
|
+
return ImageJpeg;
|
|
287
|
+
} else if (ext === "png") {
|
|
288
|
+
return ImagePng;
|
|
289
|
+
} else if (ext === "webp") {
|
|
290
|
+
return ImageWebp;
|
|
291
|
+
} else {
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const defu = defu$1.createDefu((obj, key, value) => {
|
|
298
|
+
if (obj[key] instanceof Date && value instanceof Date) {
|
|
299
|
+
obj[key] = value;
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
class PackageDocument {
|
|
304
|
+
constructor(file) {
|
|
305
|
+
this.SpecVersion = "3.0";
|
|
306
|
+
this._uniqueIdentifier = "uuid";
|
|
307
|
+
this._identifier = node_crypto.randomUUID();
|
|
308
|
+
this._metadata = {
|
|
309
|
+
title: "",
|
|
310
|
+
language: "zh-CN",
|
|
311
|
+
contributor: [],
|
|
312
|
+
coverage: "",
|
|
313
|
+
creator: {
|
|
314
|
+
name: "unknown",
|
|
315
|
+
uid: "creator"
|
|
316
|
+
},
|
|
317
|
+
date: /* @__PURE__ */ new Date(),
|
|
318
|
+
description: "",
|
|
319
|
+
format: "",
|
|
320
|
+
publisher: "",
|
|
321
|
+
relation: "",
|
|
322
|
+
rights: "",
|
|
323
|
+
source: "",
|
|
324
|
+
subject: "",
|
|
325
|
+
type: "",
|
|
326
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
327
|
+
};
|
|
328
|
+
this._items = [];
|
|
329
|
+
this._spine = [];
|
|
330
|
+
this.file = file;
|
|
331
|
+
}
|
|
332
|
+
filename() {
|
|
333
|
+
return this.file;
|
|
334
|
+
}
|
|
335
|
+
version() {
|
|
336
|
+
return this.SpecVersion;
|
|
337
|
+
}
|
|
338
|
+
// --- metadata ---
|
|
339
|
+
update(info) {
|
|
340
|
+
this._metadata = defu(info, this._metadata);
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
title() {
|
|
344
|
+
return this._metadata.title;
|
|
345
|
+
}
|
|
346
|
+
language() {
|
|
347
|
+
return this._metadata.language;
|
|
348
|
+
}
|
|
349
|
+
creator() {
|
|
350
|
+
return this._metadata.creator;
|
|
351
|
+
}
|
|
352
|
+
metadata() {
|
|
353
|
+
return this._metadata;
|
|
354
|
+
}
|
|
355
|
+
// --- manifest ---
|
|
356
|
+
addItem(item) {
|
|
357
|
+
this._items.push(item);
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
items() {
|
|
361
|
+
const l = [...this._items];
|
|
362
|
+
if (this._toc) {
|
|
363
|
+
l.push(this._toc);
|
|
364
|
+
}
|
|
365
|
+
return l;
|
|
366
|
+
}
|
|
367
|
+
manifest() {
|
|
368
|
+
return this.items().map((i) => i.manifest());
|
|
369
|
+
}
|
|
370
|
+
setSpine(items) {
|
|
371
|
+
this._spine.splice(0, this._spine.length, ...items.map((i) => i.itemref()));
|
|
372
|
+
return this;
|
|
373
|
+
}
|
|
374
|
+
spine() {
|
|
375
|
+
return this._spine;
|
|
376
|
+
}
|
|
377
|
+
// --- navigation ---
|
|
378
|
+
toc(nav, title) {
|
|
379
|
+
const content = buildTocNav({
|
|
380
|
+
heading: 2,
|
|
381
|
+
title,
|
|
382
|
+
list: nav.map(
|
|
383
|
+
(i) => Array.isArray(i.item) ? { text: i.text, list: i.item.map((i2) => ({ href: i2.item.filename(), text: i2.text })) } : { href: i.item.filename(), text: i.text }
|
|
384
|
+
)
|
|
385
|
+
}).language(this._metadata.language).build();
|
|
386
|
+
this._toc = new Html("nav.xhtml", content).update({ properties: "nav" });
|
|
387
|
+
return this;
|
|
388
|
+
}
|
|
389
|
+
// --- identifier ---
|
|
390
|
+
uniqueIdentifier() {
|
|
391
|
+
return this._uniqueIdentifier;
|
|
392
|
+
}
|
|
393
|
+
identifier() {
|
|
394
|
+
return this._identifier;
|
|
395
|
+
}
|
|
396
|
+
setIdentifier(identifier, uniqueIdentifier = "uuid") {
|
|
397
|
+
this._identifier = identifier;
|
|
398
|
+
this._uniqueIdentifier = uniqueIdentifier;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
class Epub {
|
|
403
|
+
constructor(meta = {}) {
|
|
404
|
+
/**
|
|
405
|
+
* See: https://www.w3.org/TR/epub-33/#sec-package-doc
|
|
406
|
+
*
|
|
407
|
+
* Now, it only supports single opf (OEBPS/content.opf)
|
|
408
|
+
*
|
|
409
|
+
* @returns list of package documents
|
|
410
|
+
*/
|
|
411
|
+
this.opfs = [new PackageDocument("OEBPS/content.opf")];
|
|
412
|
+
this.opfs[0].update(meta);
|
|
413
|
+
}
|
|
414
|
+
packages() {
|
|
415
|
+
return this.opfs;
|
|
416
|
+
}
|
|
417
|
+
main() {
|
|
418
|
+
return this.opfs[0];
|
|
419
|
+
}
|
|
420
|
+
addItem(...items) {
|
|
421
|
+
for (const item of items) {
|
|
422
|
+
this.opfs[0].addItem(item);
|
|
423
|
+
}
|
|
424
|
+
return this;
|
|
425
|
+
}
|
|
426
|
+
toc(nav, title) {
|
|
427
|
+
this.opfs[0].toc(nav, title);
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
spine(...items) {
|
|
431
|
+
this.opfs[0].setSpine(items);
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
async bundle() {
|
|
435
|
+
const { bundle } = await import('../chunks/index.cjs');
|
|
436
|
+
return await bundle(this);
|
|
437
|
+
}
|
|
438
|
+
async writeFile(file) {
|
|
439
|
+
const buffer = await this.bundle();
|
|
440
|
+
const dir = path__namespace.dirname(file);
|
|
441
|
+
if (!node_fs.existsSync(dir)) {
|
|
442
|
+
node_fs.mkdirSync(dir, { recursive: true });
|
|
443
|
+
}
|
|
444
|
+
await node_fs.promises.writeFile(file, buffer);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
class BundleError extends Error {
|
|
449
|
+
constructor(msg) {
|
|
450
|
+
super(msg);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const EsbuildOptions = {
|
|
455
|
+
jsxFactory: "h",
|
|
456
|
+
jsxFragment: "fragment",
|
|
457
|
+
loaders: {
|
|
458
|
+
".js": "js",
|
|
459
|
+
".ts": "ts",
|
|
460
|
+
".jsx": "jsx",
|
|
461
|
+
".tsx": "tsx"
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
function Rollup() {
|
|
465
|
+
return {
|
|
466
|
+
name: "epubook:inject-tsx",
|
|
467
|
+
transform(code, id) {
|
|
468
|
+
if (id.endsWith(".tsx")) {
|
|
469
|
+
return `import { h, fragment } from '@epubook/core';
|
|
470
|
+
` + code;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
exports.BundleError = BundleError;
|
|
477
|
+
exports.Epub = Epub;
|
|
478
|
+
exports.EsbuildOptions = EsbuildOptions;
|
|
479
|
+
exports.Fragment = Fragment;
|
|
480
|
+
exports.Html = Html;
|
|
481
|
+
exports.Image = Image;
|
|
482
|
+
exports.Item = Item;
|
|
483
|
+
exports.MIMETYPE = MIMETYPE;
|
|
484
|
+
exports.ManifestItem = ManifestItem;
|
|
485
|
+
exports.ManifestItemRef = ManifestItemRef;
|
|
486
|
+
exports.PackageDocument = PackageDocument;
|
|
487
|
+
exports.Rollup = Rollup;
|
|
488
|
+
exports.Style = Style;
|
|
489
|
+
exports.XHTMLBuilder = XHTMLBuilder;
|
|
490
|
+
exports.h = h;
|