@storyteller-platform/epub 0.4.10 → 0.5.1
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 +752 -134
- package/dist/chunk-BIEQXUOY.js +50 -0
- package/dist/index.cjs +388 -21
- package/dist/index.d.cts +2 -743
- package/dist/index.d.ts +2 -743
- package/dist/index.js +397 -66
- package/dist/upgrade.cjs +555 -0
- package/dist/upgrade.d.cts +909 -0
- package/dist/upgrade.d.ts +909 -0
- package/dist/upgrade.js +515 -0
- package/package.json +5 -4
package/dist/upgrade.js
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import "./chunk-BIEQXUOY.js";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import {
|
|
4
|
+
Epub
|
|
5
|
+
} from "./index.js";
|
|
6
|
+
const GUIDE_TO_EPUBTYPE = {
|
|
7
|
+
acknowledgements: "acknowledgments",
|
|
8
|
+
"other.afterword": "afterword",
|
|
9
|
+
"other.appendix": "appendix",
|
|
10
|
+
"other.backmatter": "backmatter",
|
|
11
|
+
bibliography: "bibliography",
|
|
12
|
+
text: "bodymatter",
|
|
13
|
+
"other.chapter": "chapter",
|
|
14
|
+
colophon: "colophon",
|
|
15
|
+
"other.conclusion": "conclusion",
|
|
16
|
+
"other.contributors": "contributors",
|
|
17
|
+
"copyright-page": "copyright-page",
|
|
18
|
+
cover: "cover",
|
|
19
|
+
dedication: "dedication",
|
|
20
|
+
"other.division": "division",
|
|
21
|
+
epigraph: "epigraph",
|
|
22
|
+
"other.epilogue": "epilogue",
|
|
23
|
+
"other.errata": "errata",
|
|
24
|
+
"other.footnotes": "footnotes",
|
|
25
|
+
foreword: "foreword",
|
|
26
|
+
"other.frontmatter": "frontmatter",
|
|
27
|
+
glossary: "glossary",
|
|
28
|
+
"other.halftitlepage": "halftitlepage",
|
|
29
|
+
"other.imprint": "imprint",
|
|
30
|
+
"other.imprimatur": "imprimatur",
|
|
31
|
+
index: "index",
|
|
32
|
+
"other.introduction": "introduction",
|
|
33
|
+
"other.landmarks": "landmarks",
|
|
34
|
+
"other.loa": "loa",
|
|
35
|
+
loi: "loi",
|
|
36
|
+
lot: "lot",
|
|
37
|
+
"other.lov": "lov",
|
|
38
|
+
notes: "",
|
|
39
|
+
"other.notice": "notice",
|
|
40
|
+
"other.other-credits": "other-credits",
|
|
41
|
+
"other.part": "part",
|
|
42
|
+
"other.preamble": "preamble",
|
|
43
|
+
preface: "preface",
|
|
44
|
+
"other.prologue": "prologue",
|
|
45
|
+
"other.rearnotes": "rearnotes",
|
|
46
|
+
"other.subchapter": "subchapter",
|
|
47
|
+
"title-page": "titlepage",
|
|
48
|
+
toc: "toc",
|
|
49
|
+
"other.volume": "volume",
|
|
50
|
+
"other.warning": "warning"
|
|
51
|
+
};
|
|
52
|
+
const XHTML_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
53
|
+
"application/xhtml+xml",
|
|
54
|
+
"application/vnd.adobe-page-template+xml",
|
|
55
|
+
"text/html"
|
|
56
|
+
]);
|
|
57
|
+
const OEB_FONTS = /* @__PURE__ */ new Set([
|
|
58
|
+
"application/vnd.ms-opentype",
|
|
59
|
+
"application/x-font-ttf",
|
|
60
|
+
"application/x-font-otf",
|
|
61
|
+
"application/x-font-truetype",
|
|
62
|
+
"application/font-sfnt",
|
|
63
|
+
"application/font-woff",
|
|
64
|
+
"application/font-woff2",
|
|
65
|
+
"font/woff",
|
|
66
|
+
"font/woff2",
|
|
67
|
+
"font/otf",
|
|
68
|
+
"font/ttf",
|
|
69
|
+
"font/sfnt"
|
|
70
|
+
]);
|
|
71
|
+
const FONT_MIME_BY_EXT = {
|
|
72
|
+
".ttf": "application/font-sfnt",
|
|
73
|
+
".otf": "application/font-sfnt",
|
|
74
|
+
".woff": "application/font-woff",
|
|
75
|
+
".woff2": "font/woff2"
|
|
76
|
+
};
|
|
77
|
+
function getMetadataElement(pkg) {
|
|
78
|
+
return Epub.findXmlChildByName("metadata", Epub.getXmlChildren(pkg));
|
|
79
|
+
}
|
|
80
|
+
function getManifestElement(pkg) {
|
|
81
|
+
return Epub.findXmlChildByName("manifest", Epub.getXmlChildren(pkg));
|
|
82
|
+
}
|
|
83
|
+
function getSpineElement(pkg) {
|
|
84
|
+
return Epub.findXmlChildByName("spine", Epub.getXmlChildren(pkg));
|
|
85
|
+
}
|
|
86
|
+
function ensureId(element) {
|
|
87
|
+
var _a;
|
|
88
|
+
const existing = (_a = element[":@"]) == null ? void 0 : _a["@_id"];
|
|
89
|
+
if (existing) return existing;
|
|
90
|
+
const id = `id-${nanoid(8)}`;
|
|
91
|
+
element[":@"] ??= {};
|
|
92
|
+
element[":@"]["@_id"] = id;
|
|
93
|
+
return id;
|
|
94
|
+
}
|
|
95
|
+
function findAllByName(name, xml) {
|
|
96
|
+
return xml.filter(
|
|
97
|
+
(node) => !Epub.isXmlTextNode(node) && name in node
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
function hasElementDeep(xml, name) {
|
|
101
|
+
for (const node of xml) {
|
|
102
|
+
if (Epub.isXmlTextNode(node)) continue;
|
|
103
|
+
if (Epub.getXmlElementName(node) === name) return true;
|
|
104
|
+
if (hasElementDeep(Epub.getXmlChildren(node), name)) return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
function textContentOf(element) {
|
|
109
|
+
return Epub.getXhtmlTextContent(Epub.getXmlChildren(element)).trim();
|
|
110
|
+
}
|
|
111
|
+
function removeFromArray(array, item) {
|
|
112
|
+
const idx = array.indexOf(item);
|
|
113
|
+
if (idx !== -1) array.splice(idx, 1);
|
|
114
|
+
}
|
|
115
|
+
function upgradeIdentifiers(pkg) {
|
|
116
|
+
const metadata = getMetadataElement(pkg);
|
|
117
|
+
if (!metadata) return;
|
|
118
|
+
for (const ident of findAllByName("dc:identifier", metadata.metadata)) {
|
|
119
|
+
const attrs = ident[":@"] ?? {};
|
|
120
|
+
let val = textContentOf(ident);
|
|
121
|
+
let scheme = attrs["@_opf:scheme"];
|
|
122
|
+
if (val.toLowerCase().startsWith("urn:")) {
|
|
123
|
+
const rest = val.slice(4);
|
|
124
|
+
const colonIdx = rest.indexOf(":");
|
|
125
|
+
if (colonIdx > 0) {
|
|
126
|
+
scheme = rest.slice(0, colonIdx);
|
|
127
|
+
val = rest.slice(colonIdx + 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (scheme && val && !scheme.toLowerCase().startsWith("uri")) {
|
|
131
|
+
val = `${scheme}:${val}`;
|
|
132
|
+
}
|
|
133
|
+
const id = attrs["@_id"];
|
|
134
|
+
ident[":@"] = id ? { "@_id": id } : {};
|
|
135
|
+
Epub.replaceXmlChildren(ident, [Epub.createXmlTextNode(val)]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function upgradeTitle(pkg) {
|
|
139
|
+
const metadata = getMetadataElement(pkg);
|
|
140
|
+
if (!metadata) return;
|
|
141
|
+
const titles = findAllByName("dc:title", metadata.metadata);
|
|
142
|
+
let firstTitle = null;
|
|
143
|
+
for (const title of titles) {
|
|
144
|
+
const text = textContentOf(title);
|
|
145
|
+
if (!text) {
|
|
146
|
+
removeFromArray(metadata.metadata, title);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
firstTitle ??= title;
|
|
150
|
+
}
|
|
151
|
+
if (!firstTitle) return;
|
|
152
|
+
const titleId = ensureId(firstTitle);
|
|
153
|
+
metadata.metadata.push(
|
|
154
|
+
Epub.createXmlElement(
|
|
155
|
+
"meta",
|
|
156
|
+
{ refines: `#${titleId}`, property: "title-type" },
|
|
157
|
+
[Epub.createXmlTextNode("main")]
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
function upgradeLanguages(pkg) {
|
|
162
|
+
var _a;
|
|
163
|
+
const metadata = getMetadataElement(pkg);
|
|
164
|
+
if (!metadata) return;
|
|
165
|
+
const langs = findAllByName("dc:language", metadata.metadata);
|
|
166
|
+
if (langs.length > 0) {
|
|
167
|
+
for (const lang of langs) {
|
|
168
|
+
const id = (_a = lang[":@"]) == null ? void 0 : _a["@_id"];
|
|
169
|
+
lang[":@"] = id ? { "@_id": id } : {};
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
metadata.metadata.push(
|
|
174
|
+
Epub.createXmlElement("dc:language", {}, [Epub.createXmlTextNode("und")])
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function upgradeAuthors(pkg) {
|
|
178
|
+
const metadata = getMetadataElement(pkg);
|
|
179
|
+
if (!metadata) return;
|
|
180
|
+
for (const which of ["dc:creator", "dc:contributor"]) {
|
|
181
|
+
for (const elem of findAllByName(which, metadata.metadata)) {
|
|
182
|
+
const attrs = elem[":@"] ?? {};
|
|
183
|
+
const role = attrs["@_opf:role"];
|
|
184
|
+
const sort = attrs["@_opf:file-as"];
|
|
185
|
+
const elemId = role || sort ? ensureId(elem) : attrs["@_id"];
|
|
186
|
+
elem[":@"] = elemId ? { "@_id": elemId } : {};
|
|
187
|
+
if (role) {
|
|
188
|
+
metadata.metadata.push(
|
|
189
|
+
Epub.createXmlElement(
|
|
190
|
+
"meta",
|
|
191
|
+
{
|
|
192
|
+
refines: `#${elemId}`,
|
|
193
|
+
property: "role",
|
|
194
|
+
scheme: "marc:relators"
|
|
195
|
+
},
|
|
196
|
+
[Epub.createXmlTextNode(role)]
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (sort) {
|
|
201
|
+
metadata.metadata.push(
|
|
202
|
+
Epub.createXmlElement(
|
|
203
|
+
"meta",
|
|
204
|
+
{ refines: `#${elemId}`, property: "file-as" },
|
|
205
|
+
[Epub.createXmlTextNode(sort)]
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function upgradeDate(pkg) {
|
|
213
|
+
var _a;
|
|
214
|
+
const metadata = getMetadataElement(pkg);
|
|
215
|
+
if (!metadata) return;
|
|
216
|
+
const dates = findAllByName("dc:date", metadata.metadata);
|
|
217
|
+
let kept = false;
|
|
218
|
+
for (const date of dates) {
|
|
219
|
+
const text = textContentOf(date);
|
|
220
|
+
if (!text || kept) {
|
|
221
|
+
removeFromArray(metadata.metadata, date);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
kept = true;
|
|
225
|
+
const id = (_a = date[":@"]) == null ? void 0 : _a["@_id"];
|
|
226
|
+
date[":@"] = id ? { "@_id": id } : {};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function upgradeMeta(pkg) {
|
|
230
|
+
const metadata = getMetadataElement(pkg);
|
|
231
|
+
if (!metadata) return;
|
|
232
|
+
for (const meta of findAllByName("meta", metadata.metadata)) {
|
|
233
|
+
const attrs = meta[":@"] ?? {};
|
|
234
|
+
const name = attrs["@_name"] ?? "";
|
|
235
|
+
const content = attrs["@_content"] ?? "";
|
|
236
|
+
let prop = null;
|
|
237
|
+
let value = content;
|
|
238
|
+
const cleanName = name.startsWith("rendition:") ? name.slice("rendition:".length) : name;
|
|
239
|
+
if (["orientation", "layout", "spread"].includes(cleanName)) {
|
|
240
|
+
prop = `rendition:${cleanName}`;
|
|
241
|
+
} else if (name === "fixed-layout") {
|
|
242
|
+
prop = "rendition:layout";
|
|
243
|
+
value = content.toLowerCase() === "true" ? "pre-paginated" : "reflowable";
|
|
244
|
+
} else if (name === "orientation-lock") {
|
|
245
|
+
prop = "rendition:orientation";
|
|
246
|
+
const map = {
|
|
247
|
+
portrait: "portrait",
|
|
248
|
+
landscape: "landscape"
|
|
249
|
+
};
|
|
250
|
+
value = map[content.toLowerCase()] ?? "auto";
|
|
251
|
+
}
|
|
252
|
+
if (!prop) continue;
|
|
253
|
+
delete attrs["@_name"];
|
|
254
|
+
delete attrs["@_content"];
|
|
255
|
+
attrs["@_property"] = prop;
|
|
256
|
+
Epub.replaceXmlChildren(meta, [Epub.createXmlTextNode(value)]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function upgradeCover(pkg) {
|
|
260
|
+
const metadata = getMetadataElement(pkg);
|
|
261
|
+
const manifest = getManifestElement(pkg);
|
|
262
|
+
if (!metadata || !manifest) return;
|
|
263
|
+
for (const meta of findAllByName("meta", metadata.metadata)) {
|
|
264
|
+
const attrs = meta[":@"];
|
|
265
|
+
const isCoverMeta = (attrs == null ? void 0 : attrs["@_name"]) === "cover" && attrs["@_content"];
|
|
266
|
+
if (!isCoverMeta) continue;
|
|
267
|
+
const itemId = attrs["@_content"];
|
|
268
|
+
for (const item of findAllByName("item", manifest.manifest)) {
|
|
269
|
+
const itemAttrs = item[":@"];
|
|
270
|
+
if (!itemAttrs || itemAttrs["@_id"] !== itemId) continue;
|
|
271
|
+
const mediaType = (itemAttrs["@_media-type"] ?? "").toLowerCase();
|
|
272
|
+
const isImage = mediaType && !mediaType.includes("xml") && !mediaType.includes("html");
|
|
273
|
+
if (!isImage) continue;
|
|
274
|
+
const existing = (itemAttrs["@_properties"] ?? "").split(" ").filter(Boolean);
|
|
275
|
+
const props = new Set(existing);
|
|
276
|
+
props.add("cover-image");
|
|
277
|
+
itemAttrs["@_properties"] = [...props].sort().join(" ");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function removeInvalidDcAttrs(pkg) {
|
|
282
|
+
const metadata = getMetadataElement(pkg);
|
|
283
|
+
if (!metadata) return;
|
|
284
|
+
for (const node of metadata.metadata) {
|
|
285
|
+
if (Epub.isXmlTextNode(node)) continue;
|
|
286
|
+
const name = Epub.getXmlElementName(node);
|
|
287
|
+
if (!name.startsWith("dc:")) continue;
|
|
288
|
+
const attrs = node[":@"];
|
|
289
|
+
if (!attrs) continue;
|
|
290
|
+
const id = attrs["@_id"];
|
|
291
|
+
node[":@"] = id ? { "@_id": id } : {};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function setLastModified(pkg) {
|
|
295
|
+
var _a;
|
|
296
|
+
const metadata = getMetadataElement(pkg);
|
|
297
|
+
if (!metadata) return;
|
|
298
|
+
for (let i = metadata.metadata.length - 1; i >= 0; i--) {
|
|
299
|
+
const node = metadata.metadata[i];
|
|
300
|
+
if (!node || Epub.isXmlTextNode(node)) continue;
|
|
301
|
+
if (((_a = node[":@"]) == null ? void 0 : _a["@_property"]) === "dcterms:modified") {
|
|
302
|
+
metadata.metadata.splice(i, 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+/, "");
|
|
306
|
+
metadata.metadata.push(
|
|
307
|
+
Epub.createXmlElement("meta", { property: "dcterms:modified" }, [
|
|
308
|
+
Epub.createXmlTextNode(now)
|
|
309
|
+
])
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
function upgradePackageMetadata(pkg) {
|
|
313
|
+
upgradeIdentifiers(pkg);
|
|
314
|
+
upgradeTitle(pkg);
|
|
315
|
+
upgradeLanguages(pkg);
|
|
316
|
+
upgradeAuthors(pkg);
|
|
317
|
+
upgradeDate(pkg);
|
|
318
|
+
upgradeMeta(pkg);
|
|
319
|
+
upgradeCover(pkg);
|
|
320
|
+
removeInvalidDcAttrs(pkg);
|
|
321
|
+
setLastModified(pkg);
|
|
322
|
+
}
|
|
323
|
+
function extractGuideLandmarks(pkg) {
|
|
324
|
+
const guide = Epub.findXmlChildByName("guide", Epub.getXmlChildren(pkg));
|
|
325
|
+
if (!guide) return [];
|
|
326
|
+
const landmarks = [];
|
|
327
|
+
for (const node of Epub.getXmlChildren(guide)) {
|
|
328
|
+
if (Epub.isXmlTextNode(node)) continue;
|
|
329
|
+
if (!("reference" in node)) continue;
|
|
330
|
+
const attrs = node[":@"] ?? {};
|
|
331
|
+
const href = attrs["@_href"] ?? "";
|
|
332
|
+
const title = attrs["@_title"] ?? "";
|
|
333
|
+
const guideType = (attrs["@_type"] ?? "").toLowerCase();
|
|
334
|
+
const epubType = GUIDE_TO_EPUBTYPE[guideType];
|
|
335
|
+
if (epubType === void 0 || !href) continue;
|
|
336
|
+
landmarks.push({ href, title, type: epubType });
|
|
337
|
+
}
|
|
338
|
+
return landmarks;
|
|
339
|
+
}
|
|
340
|
+
function removeGuide(pkg) {
|
|
341
|
+
const children = Epub.getXmlChildren(pkg);
|
|
342
|
+
const guide = Epub.findXmlChildByName("guide", children);
|
|
343
|
+
if (guide) {
|
|
344
|
+
removeFromArray(children, guide);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function removeSpineTocRef(pkg) {
|
|
348
|
+
const spine = getSpineElement(pkg);
|
|
349
|
+
if (!(spine == null ? void 0 : spine[":@"])) return;
|
|
350
|
+
delete spine[":@"]["@_toc"];
|
|
351
|
+
}
|
|
352
|
+
async function collectManifestProperties(epub) {
|
|
353
|
+
var _a;
|
|
354
|
+
const manifest = await epub.getManifest();
|
|
355
|
+
for (const item of Object.values(manifest)) {
|
|
356
|
+
const mediaType = ((_a = item.mediaType) == null ? void 0 : _a.toLowerCase()) ?? "";
|
|
357
|
+
if (!XHTML_MEDIA_TYPES.has(mediaType)) continue;
|
|
358
|
+
let xml;
|
|
359
|
+
try {
|
|
360
|
+
xml = await epub.readXhtmlItemContents(item.id);
|
|
361
|
+
} catch {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const props = new Set(item.properties ?? []);
|
|
365
|
+
const before = props.size;
|
|
366
|
+
if (hasElementDeep(xml, "svg")) props.add("svg");
|
|
367
|
+
if (hasElementDeep(xml, "script")) props.add("scripted");
|
|
368
|
+
if (hasElementDeep(xml, "math")) props.add("mathml");
|
|
369
|
+
if (hasElementDeep(xml, "epub:switch")) props.add("switch");
|
|
370
|
+
if (props.size === before) continue;
|
|
371
|
+
await epub.updateManifestItem(item.id, {
|
|
372
|
+
...item,
|
|
373
|
+
properties: [...props].sort()
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function fixFontMimeTypes(pkg) {
|
|
378
|
+
var _a, _b;
|
|
379
|
+
const manifest = getManifestElement(pkg);
|
|
380
|
+
if (!manifest) return;
|
|
381
|
+
for (const item of findAllByName("item", manifest.manifest)) {
|
|
382
|
+
const mt = (((_a = item[":@"]) == null ? void 0 : _a["@_media-type"]) ?? "").toLowerCase();
|
|
383
|
+
if (!OEB_FONTS.has(mt)) continue;
|
|
384
|
+
const href = ((_b = item[":@"]) == null ? void 0 : _b["@_href"]) ?? "";
|
|
385
|
+
const dotIdx = href.lastIndexOf(".");
|
|
386
|
+
if (dotIdx === -1) continue;
|
|
387
|
+
const ext = href.slice(dotIdx).toLowerCase();
|
|
388
|
+
const corrected = FONT_MIME_BY_EXT[ext];
|
|
389
|
+
if (corrected && corrected !== mt) {
|
|
390
|
+
item[":@"] ??= {};
|
|
391
|
+
item[":@"]["@_media-type"] = corrected;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function buildTocOl(entries) {
|
|
396
|
+
const children = entries.map((entry) => {
|
|
397
|
+
const label = entry.title.replace(/\s+/g, " ").trim();
|
|
398
|
+
const liChildren = [];
|
|
399
|
+
if (entry.href) {
|
|
400
|
+
liChildren.push(
|
|
401
|
+
Epub.createXmlElement("a", { href: entry.href }, [
|
|
402
|
+
Epub.createXmlTextNode(label)
|
|
403
|
+
])
|
|
404
|
+
);
|
|
405
|
+
} else {
|
|
406
|
+
liChildren.push(
|
|
407
|
+
Epub.createXmlElement("span", {}, [Epub.createXmlTextNode(label)])
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
if (entry.children && entry.children.length > 0) {
|
|
411
|
+
liChildren.push(buildTocOl(entry.children));
|
|
412
|
+
}
|
|
413
|
+
return Epub.createXmlElement("li", {}, liChildren);
|
|
414
|
+
});
|
|
415
|
+
return Epub.createXmlElement("ol", {}, children);
|
|
416
|
+
}
|
|
417
|
+
async function buildNavDocument(epub, tocEntries, landmarks) {
|
|
418
|
+
var _a;
|
|
419
|
+
let tocOl;
|
|
420
|
+
if (tocEntries.length > 0) {
|
|
421
|
+
tocOl = buildTocOl(tocEntries);
|
|
422
|
+
} else {
|
|
423
|
+
const spineItems = await epub.getSpineItems();
|
|
424
|
+
const firstHref = ((_a = spineItems[0]) == null ? void 0 : _a.href) ?? "#";
|
|
425
|
+
tocOl = Epub.createXmlElement("ol", {}, [
|
|
426
|
+
Epub.createXmlElement("li", {}, [
|
|
427
|
+
Epub.createXmlElement("a", { href: firstHref }, [
|
|
428
|
+
Epub.createXmlTextNode("Start")
|
|
429
|
+
])
|
|
430
|
+
])
|
|
431
|
+
]);
|
|
432
|
+
}
|
|
433
|
+
const tocNav = Epub.createXmlElement("nav", { "epub:type": "toc" }, [
|
|
434
|
+
Epub.createXmlElement("h1", {}, [
|
|
435
|
+
Epub.createXmlTextNode("Table of Contents")
|
|
436
|
+
]),
|
|
437
|
+
tocOl
|
|
438
|
+
]);
|
|
439
|
+
const body = [tocNav];
|
|
440
|
+
const validLandmarks = landmarks.filter((lm) => lm.type);
|
|
441
|
+
if (validLandmarks.length > 0) {
|
|
442
|
+
const lmOl = Epub.createXmlElement(
|
|
443
|
+
"ol",
|
|
444
|
+
{},
|
|
445
|
+
validLandmarks.map(
|
|
446
|
+
(lm) => Epub.createXmlElement("li", {}, [
|
|
447
|
+
Epub.createXmlElement("a", { "epub:type": lm.type, href: lm.href }, [
|
|
448
|
+
Epub.createXmlTextNode(lm.title || lm.type)
|
|
449
|
+
])
|
|
450
|
+
])
|
|
451
|
+
)
|
|
452
|
+
);
|
|
453
|
+
body.push(
|
|
454
|
+
Epub.createXmlElement("nav", { "epub:type": "landmarks", hidden: "" }, [
|
|
455
|
+
lmOl
|
|
456
|
+
])
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
const head = [
|
|
460
|
+
Epub.createXmlElement("title", {}, [Epub.createXmlTextNode("Navigation")])
|
|
461
|
+
];
|
|
462
|
+
const navDoc = await epub.createXhtmlDocument(body, head);
|
|
463
|
+
return Epub.xhtmlBuilder.build(navDoc);
|
|
464
|
+
}
|
|
465
|
+
function setPackageVersion(pkg, version) {
|
|
466
|
+
pkg[":@"] ??= {};
|
|
467
|
+
pkg[":@"]["@_version"] = version;
|
|
468
|
+
}
|
|
469
|
+
async function chooseNavHref(epub) {
|
|
470
|
+
const manifest = await epub.getManifest();
|
|
471
|
+
const existingHrefs = new Set(
|
|
472
|
+
Object.values(manifest).map((item) => item.href)
|
|
473
|
+
);
|
|
474
|
+
let candidate = "nav.xhtml";
|
|
475
|
+
let i = 1;
|
|
476
|
+
while (existingHrefs.has(candidate)) {
|
|
477
|
+
candidate = `nav${i}.xhtml`;
|
|
478
|
+
i++;
|
|
479
|
+
}
|
|
480
|
+
return candidate;
|
|
481
|
+
}
|
|
482
|
+
async function removeNcx(epub) {
|
|
483
|
+
const manifest = await epub.getManifest();
|
|
484
|
+
const ncxItem = Object.values(manifest).find(
|
|
485
|
+
(item) => {
|
|
486
|
+
var _a;
|
|
487
|
+
return ((_a = item.mediaType) == null ? void 0 : _a.toLowerCase()) === "application/x-dtbncx+xml";
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
if (ncxItem) {
|
|
491
|
+
await epub.removeManifestItem(ncxItem.id);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
export {
|
|
495
|
+
buildNavDocument,
|
|
496
|
+
buildTocOl,
|
|
497
|
+
chooseNavHref,
|
|
498
|
+
collectManifestProperties,
|
|
499
|
+
extractGuideLandmarks,
|
|
500
|
+
fixFontMimeTypes,
|
|
501
|
+
removeGuide,
|
|
502
|
+
removeInvalidDcAttrs,
|
|
503
|
+
removeNcx,
|
|
504
|
+
removeSpineTocRef,
|
|
505
|
+
setLastModified,
|
|
506
|
+
setPackageVersion,
|
|
507
|
+
upgradeAuthors,
|
|
508
|
+
upgradeCover,
|
|
509
|
+
upgradeDate,
|
|
510
|
+
upgradeIdentifiers,
|
|
511
|
+
upgradeLanguages,
|
|
512
|
+
upgradeMeta,
|
|
513
|
+
upgradePackageMetadata,
|
|
514
|
+
upgradeTitle
|
|
515
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyteller-platform/epub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"readme:toc": "markdown-toc --maxdepth=5 --append='\n- [API Docs](#api-docs)' --bullets='-' -i readme-stub.md",
|
|
24
24
|
"readme:api": "typedoc",
|
|
25
25
|
"readme": "yarn readme:api && yarn readme:toc && cat readme-stub.md > README.md && tail -n +2 gen/README.md >> README.md",
|
|
26
|
-
"test": "tsx -C @storyteller --test
|
|
27
|
-
"test:watch": "tsx -C @storyteller --test --watch
|
|
26
|
+
"test": "tsx -C @storyteller --test *.test.ts",
|
|
27
|
+
"test:watch": "tsx -C @storyteller --test --watch *.test.ts",
|
|
28
28
|
"prepack": "yarn build"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@types/node": "^24.0.0",
|
|
36
36
|
"@types/yauzl-promise": "^4",
|
|
37
37
|
"@types/yazl": "^3",
|
|
38
|
+
"epubchecker": "^5.2.1",
|
|
38
39
|
"eslint": "^8.0.0",
|
|
39
40
|
"markdown-toc": "^1.2.0",
|
|
40
41
|
"remark-toc": "^9.0.0",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
},
|
|
48
49
|
"dependencies": {
|
|
49
50
|
"@storyteller-platform/fs": "^0.1.4",
|
|
50
|
-
"@storyteller-platform/path": "^0.1.
|
|
51
|
+
"@storyteller-platform/path": "^0.1.2",
|
|
51
52
|
"@zip.js/zip.js": "^2.0.0",
|
|
52
53
|
"async-mutex": "^0.5.0",
|
|
53
54
|
"fast-xml-parser": "^4.0.1",
|