Dieselben verschlüsselten Listen wie der Web-Reader (annotations.py). Die Anker des Webs (Block-Index plus Zeichenposition im Rohtext) werden auf den angezeigten Text umgerechnet (marks.py, blocks.collapse_map), alte XPath-Anker über das Zitat gefunden. Im Reader: v markiert (Cursor je Wort, Anfang/Ende, Farbe oder Notiz), n legt eine Notiz an, b ein Lesezeichen, a öffnet die Liste (springen, Notiz ändern, löschen). Der Server ersetzt die Liste als Ganzes; die TUI legt ihre Änderungen vor dem Schreiben dreiwege je Eintrag-ID auf die aktuelle Liste des Servers, offline bleiben sie vorgemerkt. Geprüft: die echten resolveAnchorRange des Webs lösen 81 TUI-Anker exakt auf (tools/parity/web_anchors.cjs), 34 von 34 echten Web-Markierungen werden angezeigt, der Server-Smoke schreibt und mischt zwei Geräte. 75 Tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
42 lines
1.8 KiB
JavaScript
42 lines
1.8 KiB
JavaScript
// Does the web reader find a highlight made in the TUI where the TUI put it? Runs the web's own
|
|
// resolveAnchorRange (72-highlights.js) on the book rendered like the reader renders it, and
|
|
// returns the text of the range it finds for each anchor.
|
|
//
|
|
// node web_anchors.cjs <diora-web checkout> <book.epub> <anchors.json> <out.json>
|
|
//
|
|
// anchors.json: a list of highlight objects ({anchor: {...}, ...}). Same setup as web_blocks.cjs.
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { chromium } = require('playwright-core');
|
|
|
|
(async () => {
|
|
const [web, epub, anchorsFile, out] = process.argv.slice(2);
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage();
|
|
await page.goto('about:blank');
|
|
await page.evaluate(() => {
|
|
window.$ = (id) => document.getElementById(id);
|
|
window.currentPdfDoc = null;
|
|
});
|
|
await page.addScriptTag({ path: path.join(web, 'static/js/jszip.min.js') });
|
|
for (const f of ['40-epub.js', '44-books.js', '72-highlights.js']) {
|
|
try { await page.addScriptTag({ path: path.join(web, 'static/js/app', f) }); }
|
|
catch (e) { console.error('loading', f, e.message); }
|
|
}
|
|
const items = JSON.parse(fs.readFileSync(anchorsFile, 'utf-8'));
|
|
const b64 = fs.readFileSync(epub).toString('base64');
|
|
const result = await page.evaluate(async ({ b64, items }) => {
|
|
const bin = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
const book = await parseEpub(bin.buffer);
|
|
const el = document.createElement('div');
|
|
el.id = 'reader-content';
|
|
el.innerHTML = book.chapters.join('');
|
|
document.body.appendChild(el);
|
|
return items.map((h) => {
|
|
const r = resolveAnchorRange(h);
|
|
return r ? r.toString() : null;
|
|
});
|
|
}, { b64, items });
|
|
fs.writeFileSync(out, JSON.stringify(result));
|
|
await browser.close();
|
|
})().catch((e) => { console.error(e); process.exit(1); });
|