vue-book-reader
Version:
vue-book-reader is a vue wrapper for [foliate-js](https://github.com/johnfactotum/foliate-js) - library for rendering e-books in the browser. Supports EPUB, MOBI, KF8 (AZW3), FB2, CBZ, PDF (experimental; requires PDF.js), or add support for other formats
264 lines (263 loc) • 8.79 kB
JavaScript
const N = (a) => a ? a.replace(/[\t\n\f\r ]+/g, " ").replace(/^[\t\n\f\r ]+/, "").replace(/[\t\n\f\r ]+$/, "") : "", d = (a) => N(a == null ? void 0 : a.textContent), w = {
XLINK: "http://www.w3.org/1999/xlink",
EPUB: "http://www.idpf.org/2007/ops"
}, S = {
XML: "application/xml",
XHTML: "application/xhtml+xml"
}, m = {
strong: ["strong", "self"],
emphasis: ["em", "self"],
style: ["span", "self"],
a: "anchor",
strikethrough: ["s", "self"],
sub: ["sub", "self"],
sup: ["sup", "self"],
code: ["code", "self"],
image: "image"
}, D = {
tr: ["tr", ["align"]],
th: ["th", ["colspan", "rowspan", "align", "valign"]],
td: ["td", ["colspan", "rowspan", "align", "valign"]]
}, E = {
epigraph: ["blockquote"],
subtitle: ["h2", m],
"text-author": ["p", m],
date: ["p", m],
stanza: "stanza"
}, T = {
title: ["header", {
p: ["h1", m],
"empty-line": ["br"]
}],
epigraph: ["blockquote", "self"],
image: "image",
annotation: ["aside"],
section: ["section", "self"],
p: ["p", m],
poem: ["blockquote", E],
subtitle: ["h2", m],
cite: ["blockquote", "self"],
"empty-line": ["br"],
table: ["table", D],
"text-author": ["p", m]
};
E.epigraph.push(T);
const $ = {
image: "image",
title: ["section", {
p: ["h1", m],
"empty-line": ["br"]
}],
epigraph: ["section", T],
section: ["section", T]
}, k = (a) => {
const t = a.getAttributeNS(w.XLINK, "href"), [, o] = t.split("#"), s = a.getRootNode().getElementById(o);
return s ? `data:${s.getAttribute("content-type")};base64,${s.textContent}` : t;
};
class X {
constructor(t) {
this.fb2 = t, this.doc = document.implementation.createDocument(w.XHTML, "html");
}
image(t) {
const o = this.doc.createElement("img");
return o.alt = t.getAttribute("alt"), o.title = t.getAttribute("title"), o.setAttribute("src", k(t)), o;
}
anchor(t) {
const o = this.convert(t, { a: ["a", m] });
return o.setAttribute("href", t.getAttributeNS(w.XLINK, "href")), t.getAttribute("type") === "note" && o.setAttributeNS(w.EPUB, "epub:type", "noteref"), o;
}
stanza(t) {
const o = this.convert(t, {
stanza: ["p", {
title: ["header", {
p: ["strong", m],
"empty-line": ["br"]
}],
subtitle: ["p", m]
}]
});
for (const s of t.children)
s.nodeName === "v" && (o.append(this.doc.createTextNode(s.textContent)), o.append(this.doc.createElement("br")));
return o;
}
convert(t, o) {
if (t.nodeType === 3)
return this.doc.createTextNode(t.textContent);
if (t.nodeType === 4)
return this.doc.createCDATASection(t.textContent);
if (t.nodeType === 8)
return this.doc.createComment(t.textContent);
const s = o == null ? void 0 : o[t.nodeName];
if (!s)
return null;
if (typeof s == "string")
return this[s](t);
const [p, c] = s, u = this.doc.createElement(p);
if (t.id && (u.id = t.id), u.classList.add(t.nodeName), Array.isArray(c))
for (const g of c)
u.setAttribute(g, t.getAttribute(g));
const y = c === "self" ? o : Array.isArray(c) ? null : c;
let b = t.firstChild;
for (; b; ) {
const g = this.convert(b, y);
g && u.append(g), b = b.nextSibling;
}
return u;
}
}
const z = async (a) => {
var u;
const t = await a.arrayBuffer(), o = new TextDecoder("utf-8").decode(t), s = new DOMParser(), p = s.parseFromString(o, S.XML), c = p.xmlEncoding || ((u = o.match(/^<\?xml\s+version\s*=\s*["']1.\d+"\s+encoding\s*=\s*["']([A-Za-z0-9._-]*)["']/)) == null ? void 0 : u[1]);
if (c && c.toLowerCase() !== "utf-8") {
const y = new TextDecoder(c).decode(t);
return s.parseFromString(y, S.XML);
}
return p;
}, O = URL.createObjectURL(new Blob([`
@namespace epub "http://www.idpf.org/2007/ops";
body > img, section > img {
display: block;
margin: auto;
}
.title h1 {
text-align: center;
}
body > section > .title, body.notesBodyType > .title {
margin: 3em 0;
}
body.notesBodyType > section .title h1 {
text-align: start;
}
body.notesBodyType > section .title {
margin: 1em 0;
}
p {
text-indent: 1em;
margin: 0;
}
:not(p) + p, p:first-child {
text-indent: 0;
}
.poem p {
text-indent: 0;
margin: 1em 0;
}
.text-author, .date {
text-align: end;
}
.text-author:before {
content: "—";
}
table {
border-collapse: collapse;
}
td, th {
padding: .25em;
}
a[epub|type~="noteref"] {
font-size: .75em;
vertical-align: super;
}
body:not(.notesBodyType) > .title, body:not(.notesBodyType) > .epigraph {
margin: 3em 0;
}
`], { type: "text/css" })), I = (a) => `<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><link href="${O}" rel="stylesheet" type="text/css"/></head>
<body>${a}</body>
</html>`, v = "data-foliate-id", H = async (a) => {
const t = {}, o = await z(a), s = new X(o), p = (e) => o.querySelector(e), c = (e) => [...o.querySelectorAll(e)], u = (e) => {
const n = d(e.querySelector("nickname"));
if (n)
return n;
const r = d(e.querySelector("first-name")), i = d(e.querySelector("middle-name")), l = d(e.querySelector("last-name")), f = [r, i, l].filter((h) => h).join(" "), x = l ? [l, [r, i].filter((h) => h).join(" ")].join(", ") : null;
return { name: f, sortAs: x };
}, y = (e) => (e == null ? void 0 : e.getAttribute("value")) ?? d(e), b = p("title-info annotation");
if (t.metadata = {
title: d(p("title-info book-title")),
identifier: d(p("document-info id")),
language: d(p("title-info lang")),
author: c("title-info author").map(u),
translator: c("title-info translator").map(u),
producer: c("document-info author").map(u).concat(c("document-info program-used").map(d)),
publisher: d(p("publish-info publisher")),
published: y(p("title-info date")),
modified: y(p("document-info date")),
description: b ? s.convert(
b,
{ annotation: ["div", T] }
).innerHTML : null,
subject: c("title-info genre").map(d)
}, p("coverpage image")) {
const e = k(p("coverpage image"));
t.getCover = () => fetch(e).then((n) => n.blob());
} else
t.getCover = () => null;
const g = Array.from(o.querySelectorAll("body"), (e) => {
const n = s.convert(e, { body: ["body", $] });
return [Array.from(n.children, (r) => {
const i = [r, ...r.querySelectorAll("[id]")].map((l) => l.id);
return { el: r, ids: i };
}), n];
}), C = [], q = g[0][0].map(({ el: e, ids: n }) => {
const r = Array.from(
e.querySelectorAll(":scope > section > .title"),
(i, l) => (i.setAttribute(v, l), { title: d(i), index: l })
);
return { ids: n, titles: r, el: e };
}).concat(g.slice(1).map(([e, n]) => {
const r = e.map((i) => i.ids).flat();
return n.classList.add("notesBodyType"), { ids: r, el: n, linear: "no" };
})).map(({ ids: e, titles: n, el: r, linear: i }) => {
var M;
const l = I(r.outerHTML), f = new Blob([l], { type: S.XHTML }), x = URL.createObjectURL(f);
C.push(x);
const h = N(
((M = r.querySelector(".title, .subtitle, p")) == null ? void 0 : M.textContent) ?? (r.classList.contains("title") ? r.textContent : "")
);
return {
ids: e,
title: h,
titles: n,
load: () => x,
createDocument: () => new DOMParser().parseFromString(l, S.XHTML),
// doo't count image data as it'd skew the size too much
size: f.size - Array.from(
r.querySelectorAll("[src]"),
(L) => {
var A;
return ((A = L.getAttribute("src")) == null ? void 0 : A.length) ?? 0;
}
).reduce((L, A) => L + A, 0),
linear: i
};
}), B = /* @__PURE__ */ new Map();
return t.sections = q.map((e, n) => {
const { ids: r, load: i, createDocument: l, size: f, linear: x } = e;
for (const h of r)
h && B.set(h, n);
return { id: n, load: i, createDocument: l, size: f, linear: x };
}), t.toc = q.map(({ title: e, titles: n }, r) => {
const i = r.toString();
return {
label: e,
href: i,
subitems: n != null && n.length ? n.map(({ title: l, index: f }) => ({
label: l,
href: `${i}#${f}`
})) : null
};
}).filter((e) => e), t.resolveHref = (e) => {
const [n, r] = e.split("#");
return n ? { index: Number(n), anchor: (i) => i.querySelector(`[${v}="${r}"]`) } : { index: B.get(r), anchor: (i) => i.getElementById(r) };
}, t.splitTOCHref = (e) => {
var n;
return ((n = e == null ? void 0 : e.split("#")) == null ? void 0 : n.map((r) => Number(r))) ?? [];
}, t.getTOCFragment = (e, n) => e.querySelector(`[${v}="${n}"]`), t.destroy = () => {
for (const e of C)
URL.revokeObjectURL(e);
}, t;
};
export {
H as makeFB2
};