@storyteller-platform/epub 1.0.1 → 1.0.3
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 +2887 -141
- package/dist/index.d.ts +18 -5
- package/dist/index.js +192 -71
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** @jsxImportSource @storyteller-platform/epub */
|
|
2
1
|
import type { Document, Element, Node } from "@xmldom/xmldom";
|
|
3
2
|
import type { AdapterOptions, ArchiveSource, EpubStorageAdapter, EpubStorageAdapterClass, EpubStorageKind } from "./adapters/interface.ts";
|
|
4
3
|
import * as Upgrade from "./upgrade.tsx";
|
|
@@ -24,6 +23,17 @@ declare global {
|
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
}
|
|
26
|
+
declare module "@esfx/async-readerwriterlock" {
|
|
27
|
+
interface AsyncReaderWriterLockReader {
|
|
28
|
+
[Symbol.dispose](): void;
|
|
29
|
+
}
|
|
30
|
+
interface AsyncReaderWriterLockWriter {
|
|
31
|
+
[Symbol.dispose](): void;
|
|
32
|
+
}
|
|
33
|
+
interface AsyncReaderWriterLockUpgradeableReader {
|
|
34
|
+
[Symbol.dispose](): void;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
27
37
|
export type ManifestItem = {
|
|
28
38
|
id: string;
|
|
29
39
|
href: string;
|
|
@@ -177,7 +187,7 @@ export declare class Epub {
|
|
|
177
187
|
private rootfile;
|
|
178
188
|
private manifest;
|
|
179
189
|
private spine;
|
|
180
|
-
private
|
|
190
|
+
private packageLock;
|
|
181
191
|
/**
|
|
182
192
|
* Storage backend kind in use for this instance
|
|
183
193
|
*
|
|
@@ -246,14 +256,15 @@ export declare class Epub {
|
|
|
246
256
|
getRootfile(): Promise<string>;
|
|
247
257
|
getPackageDocumentSource(): Promise<string>;
|
|
248
258
|
private getPackageDocument;
|
|
249
|
-
getPackageElement(): Promise<Element>;
|
|
259
|
+
getPackageElement(lock?: boolean): Promise<Element>;
|
|
250
260
|
/**
|
|
251
261
|
* Safely modify the package document, without race conditions.
|
|
252
262
|
*
|
|
253
263
|
* Since the reading the package document is an async process,
|
|
254
264
|
* multiple simultaneously dispatched function calls that all
|
|
255
265
|
* attempt to modify it can clobber each other's changes. This
|
|
256
|
-
* method uses a
|
|
266
|
+
* method uses a read/write lock to ensure that each update runs
|
|
267
|
+
* exclusively.
|
|
257
268
|
*
|
|
258
269
|
* @param producer The function to update the package document. If
|
|
259
270
|
* it returns a new package document, that will be persisted, otherwise
|
|
@@ -264,7 +275,9 @@ export declare class Epub {
|
|
|
264
275
|
* Retrieve the manifest for the Epub.
|
|
265
276
|
*
|
|
266
277
|
* This is represented as a map from each manifest items'
|
|
267
|
-
* id to the rest of its properties.
|
|
278
|
+
* id to the rest of its properties. Manifest hrefs are
|
|
279
|
+
* always URI-encoded (e.g. `chapter one.xhtml` -> `chapter%20one.xhtml`),
|
|
280
|
+
* even if the raw XML is (incorrectly) not URI-encoded.
|
|
268
281
|
*
|
|
269
282
|
* @link https://www.w3.org/TR/epub-33/#sec-pkg-manifest
|
|
270
283
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
1
53
|
import { jsx as _jsx, jsxs as _jsxs } from "@storyteller-platform/epub/jsx-runtime";
|
|
2
|
-
|
|
54
|
+
/** @jsxImportSource @storyteller-platform/epub */
|
|
55
|
+
import { AsyncReaderWriterLock } from "@esfx/async-readerwriterlock";
|
|
3
56
|
import memoize from "mem";
|
|
4
57
|
import { MediaType } from "@storyteller-platform/media-types";
|
|
5
58
|
import { copyFile } from "#files";
|
|
@@ -24,6 +77,16 @@ export class EpubReadOnlyError extends Error {
|
|
|
24
77
|
* makes it easy to distinguish normal hrefs from external/invalid ones
|
|
25
78
|
*/
|
|
26
79
|
const ENTRY_ORIGIN = "epub:///";
|
|
80
|
+
function ensureEncodedPath(href) {
|
|
81
|
+
let decoded = href;
|
|
82
|
+
try {
|
|
83
|
+
decoded = decodeURIComponent(href);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// href may be un-encoded but contain a raw % symbol
|
|
87
|
+
}
|
|
88
|
+
return entryToUrlPath(decoded);
|
|
89
|
+
}
|
|
27
90
|
/**
|
|
28
91
|
* Needs to be convoluted like this, bc
|
|
29
92
|
* - `encodeURIComponent` escapes `/` and `\`
|
|
@@ -132,7 +195,7 @@ export class Epub {
|
|
|
132
195
|
rootfile = null;
|
|
133
196
|
manifest = null;
|
|
134
197
|
spine = null;
|
|
135
|
-
|
|
198
|
+
packageLock = new AsyncReaderWriterLock();
|
|
136
199
|
/**
|
|
137
200
|
* Storage backend kind in use for this instance
|
|
138
201
|
*
|
|
@@ -263,15 +326,32 @@ export class Epub {
|
|
|
263
326
|
ensureOpfNamespace(packageDocument);
|
|
264
327
|
return packageDocument;
|
|
265
328
|
}
|
|
266
|
-
async getPackageElement() {
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
329
|
+
async getPackageElement(lock = true) {
|
|
330
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
331
|
+
try {
|
|
332
|
+
const stack = __addDisposableResource(env_1, new DisposableStack(), false);
|
|
333
|
+
if (!lock) {
|
|
334
|
+
stack.adopt(await this.packageLock.read(), (lock) => {
|
|
335
|
+
lock.unlock();
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
const packageDocument = await this.getPackageDocument();
|
|
339
|
+
stack.dispose();
|
|
340
|
+
const packageElement = packageDocument.documentElement?.localName === "package"
|
|
341
|
+
? packageDocument.documentElement
|
|
342
|
+
: null;
|
|
343
|
+
if (!packageElement) {
|
|
344
|
+
throw new Error("Failed to parse EPUB: Found no package element in package document");
|
|
345
|
+
}
|
|
346
|
+
return packageElement;
|
|
347
|
+
}
|
|
348
|
+
catch (e_1) {
|
|
349
|
+
env_1.error = e_1;
|
|
350
|
+
env_1.hasError = true;
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
__disposeResources(env_1);
|
|
273
354
|
}
|
|
274
|
-
return packageElement;
|
|
275
355
|
}
|
|
276
356
|
/**
|
|
277
357
|
* Safely modify the package document, without race conditions.
|
|
@@ -279,15 +359,18 @@ export class Epub {
|
|
|
279
359
|
* Since the reading the package document is an async process,
|
|
280
360
|
* multiple simultaneously dispatched function calls that all
|
|
281
361
|
* attempt to modify it can clobber each other's changes. This
|
|
282
|
-
* method uses a
|
|
362
|
+
* method uses a read/write lock to ensure that each update runs
|
|
363
|
+
* exclusively.
|
|
283
364
|
*
|
|
284
365
|
* @param producer The function to update the package document. If
|
|
285
366
|
* it returns a new package document, that will be persisted, otherwise
|
|
286
367
|
* it will be assumed that the package document was modified in place.
|
|
287
368
|
*/
|
|
288
369
|
async withPackage(producer) {
|
|
289
|
-
|
|
290
|
-
|
|
370
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
371
|
+
try {
|
|
372
|
+
this.assertWritable();
|
|
373
|
+
const _ = __addDisposableResource(env_2, await this.packageLock.write(), false);
|
|
291
374
|
const packageDocument = await this.getPackageDocument();
|
|
292
375
|
const packageElement = packageDocument.documentElement?.localName === "package"
|
|
293
376
|
? packageDocument.documentElement
|
|
@@ -299,43 +382,65 @@ export class Epub {
|
|
|
299
382
|
const updatedPackageDocument = serialize(produced ?? packageDocument);
|
|
300
383
|
const rootfile = await this.getRootfile();
|
|
301
384
|
await this.writeEntryContents(rootfile, updatedPackageDocument, "utf-8");
|
|
302
|
-
}
|
|
385
|
+
}
|
|
386
|
+
catch (e_2) {
|
|
387
|
+
env_2.error = e_2;
|
|
388
|
+
env_2.hasError = true;
|
|
389
|
+
}
|
|
390
|
+
finally {
|
|
391
|
+
__disposeResources(env_2);
|
|
392
|
+
}
|
|
303
393
|
}
|
|
304
394
|
/**
|
|
305
395
|
* Retrieve the manifest for the Epub.
|
|
306
396
|
*
|
|
307
397
|
* This is represented as a map from each manifest items'
|
|
308
|
-
* id to the rest of its properties.
|
|
398
|
+
* id to the rest of its properties. Manifest hrefs are
|
|
399
|
+
* always URI-encoded (e.g. `chapter one.xhtml` -> `chapter%20one.xhtml`),
|
|
400
|
+
* even if the raw XML is (incorrectly) not URI-encoded.
|
|
309
401
|
*
|
|
310
402
|
* @link https://www.w3.org/TR/epub-33/#sec-pkg-manifest
|
|
311
403
|
*/
|
|
312
404
|
async getManifest() {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const href = item.getAttribute("href");
|
|
323
|
-
if (!id || !href) {
|
|
324
|
-
return acc;
|
|
405
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
406
|
+
try {
|
|
407
|
+
if (this.manifest !== null)
|
|
408
|
+
return this.manifest;
|
|
409
|
+
const _ = __addDisposableResource(env_3, await this.packageLock.read(), false);
|
|
410
|
+
const packageElement = await this.getPackageElement(false);
|
|
411
|
+
const manifest = findXmlChildByNameNS(NS.opf, "manifest", packageElement);
|
|
412
|
+
if (!manifest) {
|
|
413
|
+
throw new Error("Failed to parse EPUB: Found no manifest element in package document");
|
|
325
414
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
415
|
+
const manifestRecord = {};
|
|
416
|
+
for (const item of Array.from(manifest.children)) {
|
|
417
|
+
const id = item.getAttribute("id");
|
|
418
|
+
const hrefAttr = item.getAttribute("href");
|
|
419
|
+
if (!hrefAttr)
|
|
420
|
+
continue;
|
|
421
|
+
const href = ensureEncodedPath(hrefAttr);
|
|
422
|
+
if (!id || !href) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
manifestRecord[id] = {
|
|
329
426
|
id,
|
|
330
427
|
href,
|
|
331
428
|
mediaType: item.getAttribute("media-type") ?? undefined,
|
|
332
429
|
mediaOverlay: item.getAttribute("media-overlay") ?? undefined,
|
|
333
430
|
fallback: item.getAttribute("fallback") ?? undefined,
|
|
334
431
|
properties: item.getAttribute("properties")?.split(" ") ?? undefined,
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
this.manifest = manifestRecord;
|
|
435
|
+
return this.manifest;
|
|
436
|
+
}
|
|
437
|
+
catch (e_3) {
|
|
438
|
+
env_3.error = e_3;
|
|
439
|
+
env_3.hasError = true;
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
__disposeResources(env_3);
|
|
443
|
+
}
|
|
339
444
|
}
|
|
340
445
|
/**
|
|
341
446
|
* Returns the first index in the metadata element's children array
|
|
@@ -477,12 +582,12 @@ export class Epub {
|
|
|
477
582
|
* @link https://www.w3.org/TR/epub-33/#sec-opf-dcidentifier
|
|
478
583
|
*/
|
|
479
584
|
async setUniqueIdentifier(identifier) {
|
|
480
|
-
await this.withPackage(
|
|
585
|
+
await this.withPackage((packageElement) => {
|
|
481
586
|
const metadata = findXmlChildByNameNS(NS.opf, "metadata", packageElement);
|
|
482
587
|
if (!metadata) {
|
|
483
588
|
throw new Error("Failed to parse EPUB: found no metadata element in package document");
|
|
484
589
|
}
|
|
485
|
-
let uniqueId =
|
|
590
|
+
let uniqueId = packageElement.getAttribute("unique-identifier") ?? null;
|
|
486
591
|
if (!uniqueId) {
|
|
487
592
|
// Create a new unique identifier id
|
|
488
593
|
const newUniqueId = xmlId();
|
|
@@ -1187,8 +1292,12 @@ export class Epub {
|
|
|
1187
1292
|
* @link https://www.w3.org/TR/epub-33/#sec-prefix-attr
|
|
1188
1293
|
*/
|
|
1189
1294
|
async setPackageVocabularyPrefix(prefix, uri) {
|
|
1190
|
-
await this.withPackage(
|
|
1191
|
-
const
|
|
1295
|
+
await this.withPackage((packageElement) => {
|
|
1296
|
+
const prefixValue = packageElement.getAttribute("prefix");
|
|
1297
|
+
const matches = prefixValue?.matchAll(/(?:([a-z]+): +(\S+)\s*)/gs);
|
|
1298
|
+
const prefixes = matches
|
|
1299
|
+
? Array.from(matches).reduce((acc, match) => match[1] && match[2] ? { ...acc, [match[1]]: match[2] } : acc, {})
|
|
1300
|
+
: {};
|
|
1192
1301
|
prefixes[prefix] = uri;
|
|
1193
1302
|
packageElement.setAttribute("prefix", Object.entries(prefixes)
|
|
1194
1303
|
.map(([p, u]) => `${p}: ${u}`)
|
|
@@ -1538,21 +1647,32 @@ export class Epub {
|
|
|
1538
1647
|
return this.addCreator(contributor, index, "contributor");
|
|
1539
1648
|
}
|
|
1540
1649
|
async getSpine() {
|
|
1541
|
-
|
|
1650
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
1651
|
+
try {
|
|
1652
|
+
if (this.spine !== null)
|
|
1653
|
+
return this.spine;
|
|
1654
|
+
const _ = __addDisposableResource(env_4, await this.packageLock.read(), false);
|
|
1655
|
+
const packageElement = await this.getPackageElement(false);
|
|
1656
|
+
const spine = findXmlChildByNameNS(NS.opf, "spine", packageElement);
|
|
1657
|
+
if (!spine)
|
|
1658
|
+
throw new Error("Failed to parse EPUB: Found no spine element in package document");
|
|
1659
|
+
this.spine = Array.from(spine.children)
|
|
1660
|
+
.map((itemref) => {
|
|
1661
|
+
return {
|
|
1662
|
+
itemRef: itemref.getAttribute("idref"),
|
|
1663
|
+
linear: itemref.getAttribute("linear") !== "no",
|
|
1664
|
+
};
|
|
1665
|
+
})
|
|
1666
|
+
.filter((itemref) => !!itemref.itemRef);
|
|
1542
1667
|
return this.spine;
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
linear: itemref.getAttribute("linear") !== "no",
|
|
1552
|
-
};
|
|
1553
|
-
})
|
|
1554
|
-
.filter((itemref) => !!itemref.itemRef);
|
|
1555
|
-
return this.spine;
|
|
1668
|
+
}
|
|
1669
|
+
catch (e_4) {
|
|
1670
|
+
env_4.error = e_4;
|
|
1671
|
+
env_4.hasError = true;
|
|
1672
|
+
}
|
|
1673
|
+
finally {
|
|
1674
|
+
__disposeResources(env_4);
|
|
1675
|
+
}
|
|
1556
1676
|
}
|
|
1557
1677
|
/**
|
|
1558
1678
|
* Retrieve the manifest items that make up the Epub's spine.
|
|
@@ -1605,9 +1725,9 @@ export class Epub {
|
|
|
1605
1725
|
insert(spine, item, index === undefined
|
|
1606
1726
|
? null
|
|
1607
1727
|
: (Array.from(spine.children)[index] ?? null));
|
|
1728
|
+
// Reset the spine cache
|
|
1729
|
+
this.spine = null;
|
|
1608
1730
|
});
|
|
1609
|
-
// Reset the spine cache
|
|
1610
|
-
this.spine = null;
|
|
1611
1731
|
}
|
|
1612
1732
|
/**
|
|
1613
1733
|
* Remove the spine item at the specified index.
|
|
@@ -1622,9 +1742,9 @@ export class Epub {
|
|
|
1622
1742
|
const itemref = Array.from(spine.children)[index];
|
|
1623
1743
|
if (itemref)
|
|
1624
1744
|
spine.removeChild(itemref);
|
|
1745
|
+
// Reset the spine cache
|
|
1746
|
+
this.spine = null;
|
|
1625
1747
|
});
|
|
1626
|
-
// Reset the spine cache
|
|
1627
|
-
this.spine = null;
|
|
1628
1748
|
}
|
|
1629
1749
|
async getNavigationChildren(ol, navHref, { resolveToRoot } = {}) {
|
|
1630
1750
|
const children = [];
|
|
@@ -1781,8 +1901,6 @@ export class Epub {
|
|
|
1781
1901
|
Use if resolving a relative href from a file other than the package document.
|
|
1782
1902
|
*/
|
|
1783
1903
|
async resolveHref(href, relativeTo, { toRoot } = {}) {
|
|
1784
|
-
if (href.startsWith("#"))
|
|
1785
|
-
return href;
|
|
1786
1904
|
const rootfile = await this.getRootfile();
|
|
1787
1905
|
const from = relativeTo
|
|
1788
1906
|
? await this.resolveEntry(rootfile, relativeTo)
|
|
@@ -1790,7 +1908,7 @@ export class Epub {
|
|
|
1790
1908
|
const url = resolveEntryUrl(from, href);
|
|
1791
1909
|
if (!url)
|
|
1792
1910
|
return href;
|
|
1793
|
-
const entry =
|
|
1911
|
+
const entry = url.pathname.slice(1).split("/");
|
|
1794
1912
|
const base = toRoot ? [] : rootfile.split("/").slice(0, -1);
|
|
1795
1913
|
let shared = 0;
|
|
1796
1914
|
while (shared < base.length && base[shared] === entry[shared]) {
|
|
@@ -1798,9 +1916,9 @@ export class Epub {
|
|
|
1798
1916
|
}
|
|
1799
1917
|
const relative = [
|
|
1800
1918
|
...Array(base.length - shared).fill(".."),
|
|
1801
|
-
...entry.slice(shared)
|
|
1919
|
+
...entry.slice(shared),
|
|
1802
1920
|
];
|
|
1803
|
-
return relative.join("/") + url.hash;
|
|
1921
|
+
return ensureEncodedPath(relative.join("/")) + url.hash;
|
|
1804
1922
|
}
|
|
1805
1923
|
async readFileContents(href, relativeTo, encoding) {
|
|
1806
1924
|
const rootfile = await this.getRootfile();
|
|
@@ -1946,17 +2064,20 @@ export class Epub {
|
|
|
1946
2064
|
if (!href)
|
|
1947
2065
|
return;
|
|
1948
2066
|
await this.removeEntry(href);
|
|
2067
|
+
// Reset the cached manifest and spine, so that they will be read from
|
|
2068
|
+
// the updated XML next time
|
|
2069
|
+
this.manifest = null;
|
|
2070
|
+
this.spine = null;
|
|
1949
2071
|
});
|
|
1950
|
-
// Reset the cached manifest and spine, so that they will be read from
|
|
1951
|
-
// the updated XML next time
|
|
1952
|
-
this.manifest = null;
|
|
1953
|
-
this.spine = null;
|
|
1954
2072
|
}
|
|
1955
2073
|
async addManifestItem(item, contents, encoding) {
|
|
1956
2074
|
const existingItems = Object.values(await this.getManifest());
|
|
1957
2075
|
if (existingItems.some((existing) => existing.id === item.id)) {
|
|
1958
2076
|
throw new Error(`Cannot add manifest item: an item with the id "${item.id}" already exists`);
|
|
1959
2077
|
}
|
|
2078
|
+
if (item.href !== ensureEncodedPath(item.href)) {
|
|
2079
|
+
throw new Error(`Manifest hrefs must be URI encoded. Received ${item.href}`);
|
|
2080
|
+
}
|
|
1960
2081
|
// 4.2.1: hrefs that normalize and case-fold to the same path would
|
|
1961
2082
|
// overwrite each other on a normalizing filesystem.
|
|
1962
2083
|
const foldedHref = foldPathName(item.href);
|
|
@@ -1970,10 +2091,10 @@ export class Epub {
|
|
|
1970
2091
|
if (!manifest)
|
|
1971
2092
|
throw new Error("Failed to parse EPUB: Found no manifest element in package document");
|
|
1972
2093
|
insert(manifest, _jsx("item", { id: item.id, href: item.href, ...(item.mediaType && { "media-type": item.mediaType }), ...(item.fallback && { fallback: item.fallback }), ...(item.mediaOverlay && { "media-overlay": item.mediaOverlay }), ...(item.properties && { properties: item.properties.join(" ") }) }));
|
|
2094
|
+
// Reset the cached manifest, so that it will be read from
|
|
2095
|
+
// the updated XML next time
|
|
2096
|
+
this.manifest = null;
|
|
1973
2097
|
});
|
|
1974
|
-
// Reset the cached manifest, so that it will be read from
|
|
1975
|
-
// the updated XML next time
|
|
1976
|
-
this.manifest = null;
|
|
1977
2098
|
const rootfile = await this.getRootfile();
|
|
1978
2099
|
const filename = await this.resolveEntry(rootfile, item.href);
|
|
1979
2100
|
/* oxlint-disable typescript/no-unnecessary-type-assertion -- tsgolint treats these casts as unnecessary, but each one still narrows the value that is written out */
|
|
@@ -2006,10 +2127,10 @@ export class Epub {
|
|
|
2006
2127
|
}), ...(newItem.properties && {
|
|
2007
2128
|
properties: newItem.properties.join(" "),
|
|
2008
2129
|
}) }), existing);
|
|
2130
|
+
// Reset the cached manifest, so that it will be read from
|
|
2131
|
+
// the updated XML next time
|
|
2132
|
+
this.manifest = null;
|
|
2009
2133
|
});
|
|
2010
|
-
// Reset the cached manifest, so that it will be read from
|
|
2011
|
-
// the updated XML next time
|
|
2012
|
-
this.manifest = null;
|
|
2013
2134
|
}
|
|
2014
2135
|
/**
|
|
2015
2136
|
* Add a new metadata entry to the Epub.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyteller-platform/epub",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"license": "MIT",
|
|
@@ -80,12 +80,12 @@
|
|
|
80
80
|
"vitest": "^5.0.0"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
|
+
"@esfx/async-readerwriterlock": "^1.0.0",
|
|
83
84
|
"@storyteller-platform/fs": "^0.3.0",
|
|
84
85
|
"@storyteller-platform/media-types": "^0.2.1",
|
|
85
86
|
"@storyteller-platform/path": "^0.3.1",
|
|
86
87
|
"@xmldom/xmldom": "^0.9.12",
|
|
87
88
|
"@zip.js/zip.js": "^2.18.2",
|
|
88
|
-
"async-mutex": "^0.5.0",
|
|
89
89
|
"mem": "^8.0.0",
|
|
90
90
|
"nanoid": "^5.1.5",
|
|
91
91
|
"yauzl-promise": "^4.0.0",
|