@papyrus-sdk/ui-react-native 0.2.35-beta.9 → 0.2.35
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/dist/index.d.mts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +520 -229
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +533 -241
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/runtime/comicRuntime.test.ts +9 -9
- package/runtime/epubAnnotations.js +7 -4
- package/runtime/epubAnnotations.test.ts +6 -2
- package/runtime/epubReader.js +58 -0
- package/runtime/epubReader.test.ts +74 -0
- package/runtime/epubRuntime.test.ts +48 -0
- package/runtime/index.html +224 -79
- package/runtime/runtime.js +224 -79
- package/runtime/syncComicRuntime.mjs +6 -3
package/runtime/runtime.js
CHANGED
|
@@ -109,6 +109,67 @@
|
|
|
109
109
|
/* @papyrus-comic-runtime:end */
|
|
110
110
|
|
|
111
111
|
(function () {
|
|
112
|
+
/* @papyrus-epub-reader:start */
|
|
113
|
+
(function(root){
|
|
114
|
+
function mode(value){return value==='chapter-scroll'?'chapter-scroll':'paged';}
|
|
115
|
+
function validCfi(value){return typeof value==='string' && value.length<=4096 && /^epubcfi\([^\n<>]+\)$/.test(value);}
|
|
116
|
+
function options(value){return {manager:'default',flow:mode(value)==='paged'?'paginated':'scrolled-doc',spread:'none',width:'100%',height:'100%',allowScriptedContent:false};}
|
|
117
|
+
function gesture(start,end,state){
|
|
118
|
+
if(!start || !end || state.selected || state.interactive || state.multitouch)return null;
|
|
119
|
+
var dx=end.x-start.x,dy=end.y-start.y,elapsed=end.time-start.time;
|
|
120
|
+
if(state.mode==='paged' && Math.abs(dx)>=48 && Math.abs(dx)>Math.abs(dy)*1.5 && elapsed>=0 && elapsed<1500)return (dx<0)!==Boolean(state.rtl)?'next':'prev';
|
|
121
|
+
if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
function location(raw,count,readingMode,geometry){
|
|
125
|
+
var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
|
|
126
|
+
var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
|
|
127
|
+
var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
|
|
128
|
+
var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
|
|
129
|
+
var atStart=Boolean(raw.atStart),atEnd=Boolean(raw.atEnd);
|
|
130
|
+
// Chromium may round a column offset down by a fraction of a CSS pixel.
|
|
131
|
+
// Keep epub.js authoritative outside a 1px boundary, and for RTL layouts.
|
|
132
|
+
if(visualPage!==null && total>0 && geometry && geometry.direction==='ltr' && geometry.pageWidth>0 && Number.isFinite(geometry.offset)){
|
|
133
|
+
var boundary=Math.round(geometry.offset/geometry.pageWidth);
|
|
134
|
+
if(Math.abs(geometry.offset-boundary*geometry.pageWidth)<=1){
|
|
135
|
+
visualPage=Math.min(total,Math.max(1,boundary+1));
|
|
136
|
+
atStart=chapter===(geometry.firstIndex ?? 0)+1 && visualPage===1;
|
|
137
|
+
atEnd=chapter===(geometry.lastIndex ?? count-1)+1 && visualPage===total;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
var fraction=total?Math.max(0,(visualPage-1)/total):0;
|
|
141
|
+
return {version:1,cfi:start.cfi,href:start.href||'',chapter:chapter,chapterCount:count,visualPage:visualPage,visualPageCount:total,progress:Math.min(1,Math.max(0,(chapter-1+fraction)/Math.max(1,count))),mode:mode(readingMode),atStart:atStart,atEnd:atEnd};
|
|
142
|
+
}
|
|
143
|
+
function installResizeAnchor(manager,readCfi){
|
|
144
|
+
var original=manager.resize,anchor=null;
|
|
145
|
+
function resize(width,height,target){
|
|
146
|
+
if(validCfi(target))anchor=target;
|
|
147
|
+
if(!anchor){var current=readCfi();if(validCfi(current))anchor=current;}
|
|
148
|
+
// epub.js accepts a logical target as its third argument. Keep it across
|
|
149
|
+
// keyboard/dialog reflows instead of adopting each new viewport start.
|
|
150
|
+
return original.call(this,width,height,anchor || target);
|
|
151
|
+
}
|
|
152
|
+
manager.resize=resize;
|
|
153
|
+
return {cfi:function(){return anchor;},invalidate:function(){anchor=null;},destroy:function(){if(manager.resize===resize)manager.resize=original;anchor=null;}};
|
|
154
|
+
}
|
|
155
|
+
function install(contents,config){
|
|
156
|
+
var doc=contents.document,win=contents.window,start=null,blocked=false;
|
|
157
|
+
function point(e){var t=e.changedTouches&&e.changedTouches[0]||e.touches&&e.touches[0];return t?{x:t.clientX,y:t.clientY,time:Date.now()}:null;}
|
|
158
|
+
function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
|
|
159
|
+
function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
|
|
160
|
+
function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
|
|
161
|
+
function invalidateScroll(finish){if(config.current() && start && finish && config.mode()==='chapter-scroll' && !blocked && !selected() && Math.abs(finish.y-start.y)>12 && Math.abs(finish.y-start.y)>Math.abs(finish.x-start.x) && config.interact)config.interact();}
|
|
162
|
+
function move(e){if(e.touches.length!==1)blocked=true;invalidateScroll(point(e));}
|
|
163
|
+
function end(e){var finish=point(e);invalidateScroll(finish);var action=gesture(start,finish,{mode:config.mode(),rtl:config.rtl(),selected:selected(),interactive:blocked||interactive(e.target)||Boolean(config.annotationHit && config.annotationHit(point(e))),multitouch:blocked});start=null;if(!config.current())return;if(action==='tap')config.tap();else if(action)config.turn(action==='next'?1:-1);}
|
|
164
|
+
function cancel(){start=null;blocked=true;}
|
|
165
|
+
function link(e){if(config.current() && e.target?.closest?.('a') && config.interact)config.interact();}
|
|
166
|
+
doc.addEventListener('touchstart',begin,{passive:true});doc.addEventListener('touchmove',move,{passive:true});doc.addEventListener('touchend',end,{passive:true});doc.addEventListener('touchcancel',cancel,{passive:true});doc.addEventListener('click',link);
|
|
167
|
+
return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);doc.removeEventListener('click',link);};
|
|
168
|
+
}
|
|
169
|
+
root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,installResizeAnchor:installResizeAnchor,install:install};
|
|
170
|
+
})(typeof window==='undefined'?globalThis:window);
|
|
171
|
+
|
|
172
|
+
/* @papyrus-epub-reader:end */
|
|
112
173
|
/* @papyrus-epub-annotations:start */
|
|
113
174
|
(function (root, factory) {
|
|
114
175
|
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
@@ -187,6 +248,7 @@
|
|
|
187
248
|
var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
|
|
188
249
|
var style = annotation.markupStyle || annotation.type;
|
|
189
250
|
var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
|
|
251
|
+
if(style === 'highlight')opacity=Math.min(0.35,opacity);
|
|
190
252
|
rects.forEach(function(rect) {
|
|
191
253
|
var mark = doc.createElement('div');
|
|
192
254
|
var bodyRect=doc.body.getBoundingClientRect();
|
|
@@ -199,8 +261,10 @@
|
|
|
199
261
|
if (hasNote) {
|
|
200
262
|
var last = rects[rects.length-1], button = doc.createElement('button');
|
|
201
263
|
button.textContent = '●'; button.setAttribute('aria-label',options.labels().comment || '');
|
|
202
|
-
|
|
203
|
-
var
|
|
264
|
+
var pageWidth=Math.max(44, options.pageWidth ? options.pageWidth() : win.innerWidth);
|
|
265
|
+
var bodyRect=doc.body.getBoundingClientRect(), column=Math.max(0,Math.floor((last.left-bodyRect.left)/pageWidth));
|
|
266
|
+
button.style.cssText = 'position:absolute;pointer-events:auto;border:0;background:transparent;color:'+color+';width:44px;height:44px;left:'+((column+1)*pageWidth-44)+'px;top:'+(Math.floor(last.top/44)*44-doc.body.getBoundingClientRect().top)+'px';
|
|
267
|
+
var key = column+":"+Math.floor(last.top/44);
|
|
204
268
|
var neighbors=noteGroups.get(key);
|
|
205
269
|
if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
|
|
206
270
|
neighbors.ids.push(annotation.id);
|
|
@@ -221,10 +285,10 @@
|
|
|
221
285
|
doc.addEventListener('click',tapMarkup);
|
|
222
286
|
var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
|
|
223
287
|
if (observer) observer.observe(doc.body);
|
|
224
|
-
win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
|
|
288
|
+
win.addEventListener('resize',schedule);win.addEventListener('scroll',schedule); doc.addEventListener('load',schedule,true);
|
|
225
289
|
if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
|
|
226
290
|
schedule();
|
|
227
|
-
return {schedule:schedule, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',schedule);doc.removeEventListener('load',schedule,true);doc.removeEventListener('click',tapMarkup);overlay.remove();doc.body.style.position=originalPosition;registry.clear();}};
|
|
291
|
+
return {schedule:schedule, hit:function(x,y){return hitRegions.some(function(item){return item.rects.some(function(r){return x>=r.left && x<=r.right && y>=r.top && y<=r.bottom;});});}, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',schedule);win.removeEventListener('scroll',schedule);doc.removeEventListener('load',schedule,true);doc.removeEventListener('click',tapMarkup);overlay.remove();doc.body.style.position=originalPosition;registry.clear();}};
|
|
228
292
|
}
|
|
229
293
|
return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
|
|
230
294
|
});
|
|
@@ -237,6 +301,7 @@
|
|
|
237
301
|
let currentType = null;
|
|
238
302
|
let book = null;
|
|
239
303
|
let rendition = null;
|
|
304
|
+
let epubResizeAnchor = null;
|
|
240
305
|
let documentSessionId = null;
|
|
241
306
|
let annotationDocumentId;
|
|
242
307
|
let annotationLabels = {};
|
|
@@ -649,6 +714,7 @@
|
|
|
649
714
|
};
|
|
650
715
|
|
|
651
716
|
const clearViewer = () => {
|
|
717
|
+
if(epubResizeAnchor){epubResizeAnchor.destroy();epubResizeAnchor=null;}
|
|
652
718
|
epubAnnotationRenderers.forEach(renderer => renderer.destroy());
|
|
653
719
|
epubAnnotationRenderers.clear();
|
|
654
720
|
if (epubInteractionCleanup) {
|
|
@@ -1305,26 +1371,57 @@
|
|
|
1305
1371
|
pageCount,
|
|
1306
1372
|
});
|
|
1307
1373
|
|
|
1374
|
+
if (book.locations?.generate) {
|
|
1375
|
+
const activeBook=book;
|
|
1376
|
+
activeBook.locations.generate(150).then(() => {
|
|
1377
|
+
if (isCurrentLoad() && activeBook===book && epubLocation) {
|
|
1378
|
+
epubLocation.progress=activeBook.locations.percentageFromCfi(epubLocation.cfi);
|
|
1379
|
+
sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
|
|
1380
|
+
}
|
|
1381
|
+
}).catch(() => {});
|
|
1382
|
+
}
|
|
1383
|
+
return renderEpubRendition(loadId,generation,null);
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
const renderEpubRendition = async (loadId,generation,target) => {
|
|
1387
|
+
const diagnosticPayload={loadId};
|
|
1388
|
+
const isCurrentLoad=()=>generation===epubLoadGeneration;
|
|
1389
|
+
if(epubInteractionCleanup)epubInteractionCleanup();
|
|
1390
|
+
if(rendition)rendition.destroy();
|
|
1308
1391
|
clearViewer();
|
|
1309
|
-
rendition = book.renderTo(viewer,
|
|
1310
|
-
manager: 'continuous',
|
|
1311
|
-
allowScriptedContent: false,
|
|
1312
|
-
width: '100%',
|
|
1313
|
-
height: '100%',
|
|
1314
|
-
flow: 'scrolled-continuous',
|
|
1315
|
-
spread: 'none',
|
|
1316
|
-
});
|
|
1392
|
+
rendition = book.renderTo(viewer, window.PapyrusEpubReader.options(epubViewMode === 'single' ? 'paged' : 'chapter-scroll'));
|
|
1317
1393
|
sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
|
|
1318
1394
|
|
|
1319
1395
|
if (rendition && rendition.hooks && rendition.hooks.content) {
|
|
1320
1396
|
const session = documentSessionId;
|
|
1321
1397
|
const activeRendition = rendition;
|
|
1322
1398
|
rendition.hooks.content.register((contents) => {
|
|
1399
|
+
const removeGestureListeners = window.PapyrusEpubReader.install(contents, {
|
|
1400
|
+
mode: () => epubViewMode === 'single' ? 'paged' : 'chapter-scroll',
|
|
1401
|
+
rtl: () => book.package?.metadata?.direction === 'rtl' || book.spine?.direction === 'rtl',
|
|
1402
|
+
blocked: () => Boolean(nativeMenuSelection && String(nativeMenuSelection.contents?.window?.getSelection() || '').trim()),
|
|
1403
|
+
annotationHit: point => epubAnnotationRenderers.get(contents)?.hit(point.x,point.y),
|
|
1404
|
+
current: () => session === documentSessionId && activeRendition === rendition,
|
|
1405
|
+
interact: () => epubResizeAnchor?.invalidate(),
|
|
1406
|
+
tap: () => sendEvent('EPUB_CONTENT_TAP',{documentSessionId:session}),
|
|
1407
|
+
turn: direction => {
|
|
1408
|
+
epubNavigationQueue = epubNavigationQueue.catch(()=>{}).then(() => {
|
|
1409
|
+
if(session !== documentSessionId || activeRendition !== rendition)return;
|
|
1410
|
+
epubResizeAnchor?.invalidate();
|
|
1411
|
+
return direction<0?activeRendition.prev():activeRendition.next();
|
|
1412
|
+
});
|
|
1413
|
+
epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR',{message:String(error)}));
|
|
1414
|
+
},
|
|
1415
|
+
});
|
|
1416
|
+
const gestureCleanup = () => {removeGestureListeners();epubSelectionCleanups.delete(gestureCleanup);};
|
|
1417
|
+
epubSelectionCleanups.add(gestureCleanup);
|
|
1418
|
+
contents.__papyrusGestureCleanup=gestureCleanup;
|
|
1323
1419
|
const href = spineItems[contents.sectionIndex]?.href;
|
|
1324
1420
|
if (href && window.PapyrusEpubAnnotations) {
|
|
1325
1421
|
const renderer = window.PapyrusEpubAnnotations.install(contents, {
|
|
1326
1422
|
href, get documentId() { return annotationDocumentId; },
|
|
1327
1423
|
isCurrent: () => session === documentSessionId && activeRendition === rendition,
|
|
1424
|
+
pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
|
|
1328
1425
|
annotations: () => epubAnnotations, labels: () => annotationLabels,
|
|
1329
1426
|
onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
|
|
1330
1427
|
});
|
|
@@ -1343,8 +1440,7 @@
|
|
|
1343
1440
|
typeof selectionWindow.getSelection === 'function'
|
|
1344
1441
|
) {
|
|
1345
1442
|
let selectionTimer = null;
|
|
1346
|
-
|
|
1347
|
-
let lastSelectionAt = 0;
|
|
1443
|
+
|
|
1348
1444
|
const reportSelection = () => {
|
|
1349
1445
|
selectionTimer = null;
|
|
1350
1446
|
if (session !== documentSessionId || activeRendition !== rendition) return;
|
|
@@ -1359,11 +1455,7 @@
|
|
|
1359
1455
|
try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
|
|
1360
1456
|
const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
|
|
1361
1457
|
if (!anchor) return;
|
|
1362
|
-
|
|
1363
|
-
const now = Date.now();
|
|
1364
|
-
if (key === lastSelectionKey && now - lastSelectionAt < 750) return;
|
|
1365
|
-
lastSelectionKey = key;
|
|
1366
|
-
lastSelectionAt = now;
|
|
1458
|
+
if (nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
|
|
1367
1459
|
const payload = {documentSessionId: session, text: anchor.quote,
|
|
1368
1460
|
pageIndex: contents.sectionIndex + 1, anchor};
|
|
1369
1461
|
nativeMenuSelection = {contents, payload};
|
|
@@ -1378,6 +1470,7 @@
|
|
|
1378
1470
|
// The content document still emits selectionchange, so resolve its
|
|
1379
1471
|
// live Range to a CFI before the native menu action can fire.
|
|
1380
1472
|
if (text) reportSelection();
|
|
1473
|
+
else if (nativeMenuSelection?.contents === contents) nativeMenuSelection = null;
|
|
1381
1474
|
};
|
|
1382
1475
|
selectionDocument.addEventListener('selectionchange', handleSelectionChange);
|
|
1383
1476
|
const selectionCleanup = () => {
|
|
@@ -1400,6 +1493,7 @@
|
|
|
1400
1493
|
typeof rendition.hooks.unloaded.register === 'function'
|
|
1401
1494
|
) {
|
|
1402
1495
|
rendition.hooks.unloaded.register((view) => {
|
|
1496
|
+
view?.contents?.__papyrusGestureCleanup?.();
|
|
1403
1497
|
const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
|
|
1404
1498
|
if (typeof cleanup === 'function') cleanup();
|
|
1405
1499
|
const renderer = epubAnnotationRenderers.get(view?.contents);
|
|
@@ -1415,21 +1509,37 @@
|
|
|
1415
1509
|
if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
|
|
1416
1510
|
const href = spineItems[contents.sectionIndex]?.href;
|
|
1417
1511
|
const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
|
|
1418
|
-
if (!anchor) return;
|
|
1512
|
+
if (!anchor || nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
|
|
1419
1513
|
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
|
|
1420
1514
|
const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
|
|
1421
1515
|
nativeMenuSelection = {contents, payload};
|
|
1422
1516
|
sendEvent('EPUB_TEXT_SELECTED', payload);
|
|
1423
1517
|
});
|
|
1424
|
-
['rendered','resized','layout'].forEach(name => rendition.on(name, () => {
|
|
1518
|
+
['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
|
|
1425
1519
|
if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
1426
1520
|
}));
|
|
1427
1521
|
rendition.on('relocated', (location) => {
|
|
1522
|
+
if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
|
|
1428
1523
|
const start = location && location.start;
|
|
1429
1524
|
let index = start && Number.isInteger(start.index) ? start.index : -1;
|
|
1430
1525
|
if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
|
|
1431
1526
|
if (index < 0 || index >= spineItems.length) return;
|
|
1432
1527
|
epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
|
|
1528
|
+
let pageGeometry;
|
|
1529
|
+
const manager=rendition.manager,layout=manager?.layout;
|
|
1530
|
+
if(epubViewMode==='single' && layout?.divisor===1 && !manager?.settings?.fullsize && manager?.settings?.direction==='ltr'){
|
|
1531
|
+
const visible=manager.visible?.();
|
|
1532
|
+
const view=visible?.find(view=>view.index===index);
|
|
1533
|
+
if(view && manager.container){
|
|
1534
|
+
pageGeometry={direction:'ltr',firstIndex:book.spine.first()?.index,lastIndex:book.spine.last()?.index,pageWidth:layout.pageWidth,offset:manager.container.getBoundingClientRect().left-view.position().left};
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
epubLocation=window.PapyrusEpubReader.location(location,spineItems.length,epubViewMode==='single'?'paged':'chapter-scroll',pageGeometry);
|
|
1538
|
+
if(epubLocation){
|
|
1539
|
+
epubLocation.cfi=epubResizeAnchor?.cfi() || epubLocation.cfi;
|
|
1540
|
+
if(book.locations?.length?.()>0)epubLocation.progress=book.locations.percentageFromCfi(epubLocation.cfi);
|
|
1541
|
+
sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
|
|
1542
|
+
}
|
|
1433
1543
|
const nextPage = index + 1;
|
|
1434
1544
|
if (nextPage === currentPage) return;
|
|
1435
1545
|
currentPage = nextPage;
|
|
@@ -1437,21 +1547,23 @@
|
|
|
1437
1547
|
});
|
|
1438
1548
|
}
|
|
1439
1549
|
|
|
1550
|
+
applyEpubZoom();
|
|
1440
1551
|
sendEpubDiagnostic('epub.display.start', diagnosticPayload);
|
|
1441
1552
|
await withEpubStageTimeout(
|
|
1442
|
-
|
|
1553
|
+
rendition.display(target || spineItems[0]?.href),
|
|
1443
1554
|
'rendition.display',
|
|
1444
1555
|
diagnosticPayload,
|
|
1445
1556
|
);
|
|
1446
1557
|
if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
|
|
1447
1558
|
sendEpubDiagnostic('epub.display.end', diagnosticPayload);
|
|
1559
|
+
const resizeRendition=rendition;
|
|
1560
|
+
epubResizeAnchor=window.PapyrusEpubReader.installResizeAnchor(resizeRendition.manager,()=>resizeRendition.location?.start?.cfi);
|
|
1448
1561
|
if (
|
|
1449
1562
|
rendition &&
|
|
1450
1563
|
rendition.manager &&
|
|
1451
1564
|
typeof rendition.manager.on === 'function' &&
|
|
1452
1565
|
typeof rendition.on === 'function'
|
|
1453
1566
|
) {
|
|
1454
|
-
let touchStart = null;
|
|
1455
1567
|
let lastScrollOffset = -1;
|
|
1456
1568
|
const manager = rendition.manager;
|
|
1457
1569
|
epubScrollDiagnostics = createEpubScrollDiagnostics(manager);
|
|
@@ -1526,48 +1638,19 @@
|
|
|
1526
1638
|
return result;
|
|
1527
1639
|
};
|
|
1528
1640
|
}
|
|
1529
|
-
const
|
|
1530
|
-
const touch =
|
|
1531
|
-
(event && event.changedTouches && event.changedTouches[0]) ||
|
|
1532
|
-
(event && event.touches && event.touches[0]);
|
|
1533
|
-
if (!touch) return null;
|
|
1534
|
-
const x = Number(touch.screenX ?? touch.clientX);
|
|
1535
|
-
const y = Number(touch.screenY ?? touch.clientY);
|
|
1536
|
-
return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
|
|
1537
|
-
};
|
|
1538
|
-
const handleScroll = (event) => {
|
|
1641
|
+
const handleScroll = event => {
|
|
1539
1642
|
const offsetY = Math.max(0, Number(event && event.top) || 0);
|
|
1540
1643
|
epubScrollDiagnostics && epubScrollDiagnostics.scroll(event);
|
|
1541
|
-
if (Math.abs(offsetY
|
|
1542
|
-
lastScrollOffset
|
|
1543
|
-
sendEvent('VIEWER_SCROLL',
|
|
1544
|
-
};
|
|
1545
|
-
const handleTouchStart = (event) => {
|
|
1546
|
-
touchStart = getTouchPoint(event);
|
|
1547
|
-
epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
|
|
1644
|
+
if (Math.abs(offsetY-lastScrollOffset)<1) return;
|
|
1645
|
+
lastScrollOffset=offsetY;
|
|
1646
|
+
sendEvent('VIEWER_SCROLL',{offsetY});
|
|
1548
1647
|
};
|
|
1549
|
-
const handleTouchMove = (event) => {
|
|
1550
|
-
epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
|
|
1551
|
-
};
|
|
1552
|
-
const handleTouchEnd = (event) => {
|
|
1553
|
-
const end = getTouchPoint(event);
|
|
1554
|
-
const start = touchStart;
|
|
1555
|
-
touchStart = null;
|
|
1556
|
-
const selectedText = event?.target?.ownerDocument?.defaultView?.getSelection?.()?.toString().trim() || '';
|
|
1557
|
-
if (!selectedText && start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
|
|
1558
|
-
sendEvent('VIEWER_TAP', {});
|
|
1559
|
-
}
|
|
1560
|
-
epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
|
|
1561
|
-
};
|
|
1562
|
-
|
|
1563
1648
|
manager.on('scroll', handleScroll);
|
|
1564
1649
|
const handleScrolled = (event) => {
|
|
1565
1650
|
epubScrollDiagnostics && epubScrollDiagnostics.settled(event);
|
|
1566
1651
|
};
|
|
1567
1652
|
manager.on('scrolled', handleScrolled);
|
|
1568
|
-
|
|
1569
|
-
rendition.on('touchmove', handleTouchMove);
|
|
1570
|
-
rendition.on('touchend', handleTouchEnd);
|
|
1653
|
+
|
|
1571
1654
|
epubInteractionCleanup = () => {
|
|
1572
1655
|
epubSelectionCleanups.forEach((cleanup) => cleanup());
|
|
1573
1656
|
epubSelectionCleanups.clear();
|
|
@@ -1576,16 +1659,9 @@
|
|
|
1576
1659
|
if (originalEnqueue) queue.enqueue = originalEnqueue;
|
|
1577
1660
|
if (typeof manager.off === 'function') manager.off('scroll', handleScroll);
|
|
1578
1661
|
if (typeof manager.off === 'function') manager.off('scrolled', handleScrolled);
|
|
1579
|
-
if (typeof rendition.off === 'function') {
|
|
1580
|
-
rendition.off('touchstart', handleTouchStart);
|
|
1581
|
-
rendition.off('touchmove', handleTouchMove);
|
|
1582
|
-
rendition.off('touchend', handleTouchEnd);
|
|
1583
|
-
}
|
|
1584
1662
|
epubScrollDiagnostics = null;
|
|
1585
1663
|
};
|
|
1586
1664
|
}
|
|
1587
|
-
applyEpubZoom();
|
|
1588
|
-
|
|
1589
1665
|
const outline = await withEpubStageTimeout(
|
|
1590
1666
|
buildOutline(),
|
|
1591
1667
|
'outline',
|
|
@@ -1599,6 +1675,8 @@
|
|
|
1599
1675
|
return { pageCount, outline };
|
|
1600
1676
|
};
|
|
1601
1677
|
|
|
1678
|
+
let epubNavigationQueue = Promise.resolve();
|
|
1679
|
+
let epubNavigationRequest = 0;
|
|
1602
1680
|
const displayEpubPage = async (pageIndex) => {
|
|
1603
1681
|
if (!rendition) return;
|
|
1604
1682
|
const item = spineItems[pageIndex];
|
|
@@ -1608,9 +1686,19 @@
|
|
|
1608
1686
|
target,
|
|
1609
1687
|
pageIndex,
|
|
1610
1688
|
});
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1689
|
+
const activeRendition = rendition;
|
|
1690
|
+
const session = documentSessionId;
|
|
1691
|
+
const request = ++epubNavigationRequest;
|
|
1692
|
+
const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
|
|
1693
|
+
if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
|
|
1694
|
+
epubResizeAnchor?.invalidate();
|
|
1695
|
+
await activeRendition.display(target);
|
|
1696
|
+
if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
|
|
1697
|
+
currentPage = pageIndex + 1;
|
|
1698
|
+
sendState();
|
|
1699
|
+
});
|
|
1700
|
+
epubNavigationQueue = navigation;
|
|
1701
|
+
await navigation;
|
|
1614
1702
|
};
|
|
1615
1703
|
|
|
1616
1704
|
const getTextContent = async (pageIndex) => {
|
|
@@ -1712,6 +1800,8 @@
|
|
|
1712
1800
|
};
|
|
1713
1801
|
|
|
1714
1802
|
let nativeMenuSelection = null;
|
|
1803
|
+
let epubViewMode = "single";
|
|
1804
|
+
let epubLocation = null;
|
|
1715
1805
|
|
|
1716
1806
|
const handleCommand = async (message) => {
|
|
1717
1807
|
const { id, kind, payload } = message;
|
|
@@ -1724,6 +1814,11 @@
|
|
|
1724
1814
|
epubAnnotations = [];
|
|
1725
1815
|
annotationDocumentId = undefined;
|
|
1726
1816
|
zoom = 1.0;
|
|
1817
|
+
epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
|
|
1818
|
+
epubLocation = null;
|
|
1819
|
+
nativeMenuSelection = null;
|
|
1820
|
+
epubNavigationRequest += 1;
|
|
1821
|
+
epubNavigationQueue = Promise.resolve();
|
|
1727
1822
|
|
|
1728
1823
|
if (currentType === 'text') {
|
|
1729
1824
|
const result = await loadText(
|
|
@@ -1778,17 +1873,24 @@
|
|
|
1778
1873
|
epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
1779
1874
|
} else {
|
|
1780
1875
|
const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
|
|
1781
|
-
if (!annotation) throw new Error('Annotation not available');
|
|
1876
|
+
if (!annotation || (annotation.anchor.documentId && annotation.anchor.documentId !== annotationDocumentId)) throw new Error('Annotation not available for this document');
|
|
1782
1877
|
// Navigation uses the CFI, never a synthetic percentage/page rectangle.
|
|
1783
1878
|
const navigationSession = documentSessionId;
|
|
1784
1879
|
const activeRendition = rendition;
|
|
1785
1880
|
// Open the confirmed chapter first, resolve its quote, then navigate its actual range.
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1881
|
+
epubNavigationRequest += 1;
|
|
1882
|
+
const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
|
|
1883
|
+
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
1884
|
+
epubResizeAnchor?.invalidate();
|
|
1885
|
+
await activeRendition.display(annotation.anchor.href);
|
|
1886
|
+
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
1887
|
+
const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
|
|
1888
|
+
const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
|
|
1889
|
+
if(!range)throw new Error('EPUB anchor unresolved');
|
|
1890
|
+
await activeRendition.display(contents.cfiFromRange(range));
|
|
1891
|
+
});
|
|
1892
|
+
epubNavigationQueue = navigation;
|
|
1893
|
+
await navigation;
|
|
1792
1894
|
}
|
|
1793
1895
|
sendResponse(id,true,null); return;
|
|
1794
1896
|
}
|
|
@@ -1815,12 +1917,55 @@
|
|
|
1815
1917
|
return;
|
|
1816
1918
|
}
|
|
1817
1919
|
|
|
1920
|
+
if (kind === 'epub-turn-page') {
|
|
1921
|
+
if (epubViewMode !== 'single') {sendResponse(id,true,null); return;}
|
|
1922
|
+
if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB navigation');
|
|
1923
|
+
const active = rendition;
|
|
1924
|
+
epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
|
|
1925
|
+
if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
|
|
1926
|
+
epubResizeAnchor?.invalidate();
|
|
1927
|
+
return payload.direction < 0 ? active.prev() : active.next();
|
|
1928
|
+
});
|
|
1929
|
+
await epubNavigationQueue;
|
|
1930
|
+
sendResponse(id,true,null); return;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
if (kind === 'set-epub-view-mode') {
|
|
1934
|
+
if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB layout session');
|
|
1935
|
+
const next=payload.epubMode ? (payload.epubMode==='paged'?'single':'continuous') : (payload.viewMode==='single'?'single':'continuous');
|
|
1936
|
+
{
|
|
1937
|
+
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
|
|
1938
|
+
if(payload.documentSessionId!==documentSessionId || next===epubViewMode)return;
|
|
1939
|
+
const target=epubResizeAnchor?.cfi() || epubLocation?.cfi;
|
|
1940
|
+
epubViewMode=next;
|
|
1941
|
+
await renderEpubRendition('layout',epubLoadGeneration,target);
|
|
1942
|
+
});
|
|
1943
|
+
await epubNavigationQueue;
|
|
1944
|
+
}
|
|
1945
|
+
sendResponse(id,true,null);return;
|
|
1946
|
+
}
|
|
1947
|
+
if(kind === 'epub-go-to-location'){
|
|
1948
|
+
if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
|
|
1949
|
+
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>{if(payload.documentSessionId!==documentSessionId)return;epubResizeAnchor?.invalidate();return rendition.display(payload.cfi);});
|
|
1950
|
+
await epubNavigationQueue;sendResponse(id,true,null);return;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1818
1953
|
if (kind === 'set-zoom') {
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1954
|
+
const requestedZoom=Math.max(0.5,Math.min(4.0,payload.zoom || 1.0));
|
|
1955
|
+
if(currentType === 'epub') {
|
|
1956
|
+
const session=documentSessionId;
|
|
1957
|
+
epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
|
|
1958
|
+
if(session!==documentSessionId)return;
|
|
1959
|
+
const active=rendition;
|
|
1960
|
+
const cfi=epubResizeAnchor?.cfi() || epubLocation?.cfi;
|
|
1961
|
+
zoom=requestedZoom;applyEpubZoom();
|
|
1962
|
+
// Let the content CSS update before redisplaying the logical anchor.
|
|
1963
|
+
await new Promise(resolve=>(window.requestAnimationFrame || (callback=>setTimeout(callback,0)))(resolve));
|
|
1964
|
+
if(cfi && active===rendition && session===documentSessionId)await active.display(cfi);
|
|
1965
|
+
});
|
|
1966
|
+
await epubNavigationQueue;
|
|
1967
|
+
} else {zoom=requestedZoom;applyZoom();}
|
|
1968
|
+
sendState();sendResponse(id,true,{zoom});return;
|
|
1824
1969
|
}
|
|
1825
1970
|
|
|
1826
1971
|
if (kind === 'set-rotation') {
|
|
@@ -24,11 +24,13 @@ const runtimeBodyStart = runtime.indexOf("\n(function () {", runtime.indexOf(com
|
|
|
24
24
|
if (runtimeBodyStart < 0) {
|
|
25
25
|
throw new Error("Main runtime body not found");
|
|
26
26
|
}
|
|
27
|
-
const
|
|
27
|
+
const readerSource=await readFile(resolve(runtimeDirectory,"epubReader.js"),"utf8");
|
|
28
|
+
const readerRuntime=replaceMarkedSource(runtime,"/* @papyrus-epub-reader:start */","/* @papyrus-epub-reader:end */",`\n${readerSource}\n`);
|
|
29
|
+
const runtimeBody = readerRuntime.slice(readerRuntime.indexOf("\n(function () {",readerRuntime.indexOf(comicRuntimeEnd))+1).trim();
|
|
28
30
|
await writeFile(
|
|
29
31
|
runtimePath,
|
|
30
32
|
replaceMarkedSource(
|
|
31
|
-
|
|
33
|
+
readerRuntime,
|
|
32
34
|
"/* @papyrus-comic-runtime:start */",
|
|
33
35
|
"/* @papyrus-comic-runtime:end */",
|
|
34
36
|
`\n${source.trim()}\n`
|
|
@@ -37,12 +39,13 @@ await writeFile(
|
|
|
37
39
|
|
|
38
40
|
const htmlPath = resolve(runtimeDirectory, "index.html");
|
|
39
41
|
const html = await readFile(htmlPath, "utf8");
|
|
40
|
-
|
|
42
|
+
let htmlWithComicRuntime = replaceMarkedSource(
|
|
41
43
|
html,
|
|
42
44
|
"<!-- @papyrus-comic-runtime:start -->",
|
|
43
45
|
"<!-- @papyrus-comic-runtime:end -->",
|
|
44
46
|
`\n <script>\n${source}\n </script>\n`
|
|
45
47
|
);
|
|
48
|
+
htmlWithComicRuntime=htmlWithComicRuntime.replace(/<!-- @papyrus-epub-reader:start -->[\s\S]*?<!-- @papyrus-epub-reader:end -->\n?/g,"");
|
|
46
49
|
const htmlComicRuntimeEnd = "<!-- @papyrus-comic-runtime:end -->";
|
|
47
50
|
const htmlBodyStart = htmlWithComicRuntime.indexOf(
|
|
48
51
|
"<script>",
|