diora-web/tui/tools/parity/web_blocks.cjs

56 lines
2.7 KiB
JavaScript
Raw Permalink Normal View History

// The web reader's own block list for EPUBs, from the real app code (static/js/app/40-epub.js
// parseEpub + the reader's block selector) in headless Chromium. Reference for compare_blocks.py.
//
// node web_blocks.cjs <diora-web checkout> <out.json> book.epub [more.epub ...]
//
// Needs playwright-core (e.g. NODE_PATH=<diora-web>/node_modules) and a Chromium
// (PLAYWRIGHT_BROWSERS_PATH as for the e2e tests).
const fs = require('fs');
const path = require('path');
const { chromium } = require('playwright-core');
(async () => {
const [web, out, ...books] = process.argv.slice(2);
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('about:blank');
await page.addScriptTag({ path: path.join(web, 'static/js/jszip.min.js') });
await page.addScriptTag({ path: path.join(web, 'static/js/app/40-epub.js') });
const result = {};
for (const file of books) {
const b64 = fs.readFileSync(file).toString('base64');
result[file] = await page.evaluate(async (b64) => {
const bin = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
const book = await parseEpub(bin.buffer);
const el = document.createElement('div');
el.innerHTML = book.chapters.join('');
document.body.appendChild(el);
const SEL = 'p, h1, h2, h3, h4, h5, h6, li, blockquote, dt, dd, figcaption, div:not(:has(*))'; // EPUB_BLOCK_SELECTOR, 44-books.js
const blocks = Array.from(el.querySelectorAll(SEL));
const chapters = Array.from(el.querySelectorAll('[data-epub-src]'));
// _bookCharIndex (44-books.js): offset of each block's first text node among all text nodes
const offsets = new Map();
let pos = 0;
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
for (let n; (n = walker.nextNode());) { offsets.set(n, pos); pos += n.nodeValue.length; }
const starts = [];
blocks.forEach((b, i) => {
const first = document.createTreeWalker(b, NodeFilter.SHOW_TEXT).nextNode();
starts[i] = first ? offsets.get(first) : (i ? starts[i - 1] + blocks[i - 1].textContent.length : 0);
});
const res = blocks.map((b, i) => ({
start: starts[i],
tag: b.tagName.toLowerCase(),
text: b.textContent.replace(/\s+/g, ' ').trim(),
len: b.textContent.length,
ch: chapters.findIndex((c) => c.contains(b)),
}));
el.remove();
return { title: book.title, chapters: chapters.length, total: pos || 1, blocks: res };
}, b64);
console.error(path.basename(file), result[file].blocks.length, 'blocks');
}
fs.writeFileSync(out, JSON.stringify(result));
await browser.close();
})().catch((e) => { console.error(e); process.exit(1); });