@papyrus-sdk/ui-react-native 0.2.35-beta.10 → 0.2.35-beta.11.epub.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.
@@ -20483,6 +20483,42 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20483
20483
  <!-- @papyrus-comic-runtime:end -->
20484
20484
  <script>
20485
20485
  (function () {
20486
+ /* @papyrus-epub-reader:start */
20487
+ (function(root){
20488
+ function mode(value){return value==='chapter-scroll'?'chapter-scroll':'paged';}
20489
+ function validCfi(value){return typeof value==='string' && value.length<=4096 && /^epubcfi\([^\n<>]+\)$/.test(value);}
20490
+ function options(value){return {manager:'default',flow:mode(value)==='paged'?'paginated':'scrolled-doc',spread:'none',width:'100%',height:'100%',allowScriptedContent:false};}
20491
+ function gesture(start,end,state){
20492
+ if(!start || !end || state.selected || state.interactive || state.multitouch)return null;
20493
+ var dx=end.x-start.x,dy=end.y-start.y,elapsed=end.time-start.time;
20494
+ 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';
20495
+ if(elapsed>=0 && elapsed<450 && Math.hypot(dx,dy)<=12)return 'tap';
20496
+ return null;
20497
+ }
20498
+ function location(raw,count,readingMode){
20499
+ var start=raw && raw.start;if(!start || !validCfi(start.cfi))return null;
20500
+ var displayed=start.displayed||{},chapter=Math.max(1,(Number(start.index)||0)+1);
20501
+ var visualPage=mode(readingMode)==='paged' && Number.isFinite(displayed.page)?displayed.page:null;
20502
+ var total=mode(readingMode)==='paged' && Number.isFinite(displayed.total)?displayed.total:null;
20503
+ var fraction=total?Math.max(0,(visualPage-1)/total):0;
20504
+ 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:Boolean(raw.atStart),atEnd:Boolean(raw.atEnd)};
20505
+ }
20506
+ function install(contents,config){
20507
+ var doc=contents.document,win=contents.window,start=null,blocked=false;
20508
+ 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;}
20509
+ function selected(){return Boolean(String(win.getSelection()||'').trim()) || config.blocked();}
20510
+ function interactive(target){return Boolean(target && target.closest && target.closest('a,button,input,textarea,select,[contenteditable="true"],[data-papyrus-overlay]'));}
20511
+ function begin(e){start=point(e);blocked=e.touches.length!==1 || selected() || interactive(e.target);}
20512
+ function move(e){if(e.touches.length!==1)blocked=true;}
20513
+ function end(e){var action=gesture(start,point(e),{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);}
20514
+ function cancel(){start=null;blocked=true;}
20515
+ doc.addEventListener('touchstart',begin,{passive:true});doc.addEventListener('touchmove',move,{passive:true});doc.addEventListener('touchend',end,{passive:true});doc.addEventListener('touchcancel',cancel,{passive:true});
20516
+ return function(){doc.removeEventListener('touchstart',begin);doc.removeEventListener('touchmove',move);doc.removeEventListener('touchend',end);doc.removeEventListener('touchcancel',cancel);};
20517
+ }
20518
+ root.PapyrusEpubReader={mode:mode,validCfi:validCfi,options:options,gesture:gesture,location:location,install:install};
20519
+ })(typeof window==='undefined'?globalThis:window);
20520
+
20521
+ /* @papyrus-epub-reader:end */
20486
20522
  /* @papyrus-epub-annotations:start */
20487
20523
  (function (root, factory) {
20488
20524
  if (typeof module === 'object' && module.exports) module.exports = factory();
@@ -20561,6 +20597,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20561
20597
  var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
20562
20598
  var style = annotation.markupStyle || annotation.type;
20563
20599
  var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
20600
+ if(style === 'highlight')opacity=Math.min(0.35,opacity);
20564
20601
  rects.forEach(function(rect) {
20565
20602
  var mark = doc.createElement('div');
20566
20603
  var bodyRect=doc.body.getBoundingClientRect();
@@ -20573,8 +20610,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20573
20610
  if (hasNote) {
20574
20611
  var last = rects[rects.length-1], button = doc.createElement('button');
20575
20612
  button.textContent = '●'; button.setAttribute('aria-label',options.labels().comment || '');
20576
- button.style.cssText = 'position:absolute;pointer-events:auto;border:0;background:transparent;color:'+color+';width:44px;height:44px;left:'+Math.max(0,Math.min(doc.documentElement.clientWidth-44,last.right-doc.body.getBoundingClientRect().left))+'px;top:'+(Math.floor(last.top/44)*44-doc.body.getBoundingClientRect().top)+'px';
20577
- var key = Math.floor(last.top/44);
20613
+ var pageWidth=Math.max(44, options.pageWidth ? options.pageWidth() : win.innerWidth);
20614
+ var bodyRect=doc.body.getBoundingClientRect(), column=Math.max(0,Math.floor((last.left-bodyRect.left)/pageWidth));
20615
+ 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';
20616
+ var key = column+":"+Math.floor(last.top/44);
20578
20617
  var neighbors=noteGroups.get(key);
20579
20618
  if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
20580
20619
  neighbors.ids.push(annotation.id);
@@ -20595,10 +20634,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
20595
20634
  doc.addEventListener('click',tapMarkup);
20596
20635
  var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
20597
20636
  if (observer) observer.observe(doc.body);
20598
- win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
20637
+ win.addEventListener('resize',schedule);win.addEventListener('scroll',schedule); doc.addEventListener('load',schedule,true);
20599
20638
  if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
20600
20639
  schedule();
20601
- 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();}};
20640
+ 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();}};
20602
20641
  }
20603
20642
  return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
20604
20643
  });
@@ -21679,26 +21718,55 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21679
21718
  pageCount,
21680
21719
  });
21681
21720
 
21721
+ if (book.locations?.generate) {
21722
+ const activeBook=book;
21723
+ activeBook.locations.generate(150).then(() => {
21724
+ if (isCurrentLoad() && activeBook===book && epubLocation) {
21725
+ epubLocation.progress=activeBook.locations.percentageFromCfi(epubLocation.cfi);
21726
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
21727
+ }
21728
+ }).catch(() => {});
21729
+ }
21730
+ return renderEpubRendition(loadId,generation,null);
21731
+ };
21732
+
21733
+ const renderEpubRendition = async (loadId,generation,target) => {
21734
+ const diagnosticPayload={loadId};
21735
+ const isCurrentLoad=()=>generation===epubLoadGeneration;
21736
+ if(epubInteractionCleanup)epubInteractionCleanup();
21737
+ if(rendition)rendition.destroy();
21682
21738
  clearViewer();
21683
- rendition = book.renderTo(viewer, {
21684
- manager: 'continuous',
21685
- allowScriptedContent: false,
21686
- width: '100%',
21687
- height: '100%',
21688
- flow: 'scrolled-continuous',
21689
- spread: 'none',
21690
- });
21739
+ rendition = book.renderTo(viewer, window.PapyrusEpubReader.options(epubViewMode === 'single' ? 'paged' : 'chapter-scroll'));
21691
21740
  sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
21692
21741
 
21693
21742
  if (rendition && rendition.hooks && rendition.hooks.content) {
21694
21743
  const session = documentSessionId;
21695
21744
  const activeRendition = rendition;
21696
21745
  rendition.hooks.content.register((contents) => {
21746
+ const removeGestureListeners = window.PapyrusEpubReader.install(contents, {
21747
+ mode: () => epubViewMode === 'single' ? 'paged' : 'chapter-scroll',
21748
+ rtl: () => book.package?.metadata?.direction === 'rtl' || book.spine?.direction === 'rtl',
21749
+ blocked: () => Boolean(nativeMenuSelection && String(nativeMenuSelection.contents?.window?.getSelection() || '').trim()),
21750
+ annotationHit: point => epubAnnotationRenderers.get(contents)?.hit(point.x,point.y),
21751
+ current: () => session === documentSessionId && activeRendition === rendition,
21752
+ tap: () => sendEvent('EPUB_CONTENT_TAP',{documentSessionId:session}),
21753
+ turn: direction => {
21754
+ epubNavigationQueue = epubNavigationQueue.catch(()=>{}).then(() => {
21755
+ if(session !== documentSessionId || activeRendition !== rendition)return;
21756
+ return direction<0?activeRendition.prev():activeRendition.next();
21757
+ });
21758
+ epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR',{message:String(error)}));
21759
+ },
21760
+ });
21761
+ const gestureCleanup = () => {removeGestureListeners();epubSelectionCleanups.delete(gestureCleanup);};
21762
+ epubSelectionCleanups.add(gestureCleanup);
21763
+ contents.__papyrusGestureCleanup=gestureCleanup;
21697
21764
  const href = spineItems[contents.sectionIndex]?.href;
21698
21765
  if (href && window.PapyrusEpubAnnotations) {
21699
21766
  const renderer = window.PapyrusEpubAnnotations.install(contents, {
21700
21767
  href, get documentId() { return annotationDocumentId; },
21701
21768
  isCurrent: () => session === documentSessionId && activeRendition === rendition,
21769
+ pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
21702
21770
  annotations: () => epubAnnotations, labels: () => annotationLabels,
21703
21771
  onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
21704
21772
  });
@@ -21717,8 +21785,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21717
21785
  typeof selectionWindow.getSelection === 'function'
21718
21786
  ) {
21719
21787
  let selectionTimer = null;
21720
- let lastSelectionKey = '';
21721
- let lastSelectionAt = 0;
21788
+
21722
21789
  const reportSelection = () => {
21723
21790
  selectionTimer = null;
21724
21791
  if (session !== documentSessionId || activeRendition !== rendition) return;
@@ -21733,11 +21800,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21733
21800
  try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
21734
21801
  const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
21735
21802
  if (!anchor) return;
21736
- const key = anchor.cfiRange + ':' + anchor.quote;
21737
- const now = Date.now();
21738
- if (key === lastSelectionKey && now - lastSelectionAt < 750) return;
21739
- lastSelectionKey = key;
21740
- lastSelectionAt = now;
21803
+ if (nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21741
21804
  const payload = {documentSessionId: session, text: anchor.quote,
21742
21805
  pageIndex: contents.sectionIndex + 1, anchor};
21743
21806
  nativeMenuSelection = {contents, payload};
@@ -21752,6 +21815,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21752
21815
  // The content document still emits selectionchange, so resolve its
21753
21816
  // live Range to a CFI before the native menu action can fire.
21754
21817
  if (text) reportSelection();
21818
+ else if (nativeMenuSelection?.contents === contents) nativeMenuSelection = null;
21755
21819
  };
21756
21820
  selectionDocument.addEventListener('selectionchange', handleSelectionChange);
21757
21821
  const selectionCleanup = () => {
@@ -21774,6 +21838,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21774
21838
  typeof rendition.hooks.unloaded.register === 'function'
21775
21839
  ) {
21776
21840
  rendition.hooks.unloaded.register((view) => {
21841
+ view?.contents?.__papyrusGestureCleanup?.();
21777
21842
  const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
21778
21843
  if (typeof cleanup === 'function') cleanup();
21779
21844
  const renderer = epubAnnotationRenderers.get(view?.contents);
@@ -21789,21 +21854,27 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21789
21854
  if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21790
21855
  const href = spineItems[contents.sectionIndex]?.href;
21791
21856
  const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
21792
- if (!anchor) return;
21857
+ if (!anchor || nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
21793
21858
  epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
21794
21859
  const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
21795
21860
  nativeMenuSelection = {contents, payload};
21796
21861
  sendEvent('EPUB_TEXT_SELECTED', payload);
21797
21862
  });
21798
- ['rendered','resized','layout'].forEach(name => rendition.on(name, () => {
21863
+ ['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
21799
21864
  if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
21800
21865
  }));
21801
21866
  rendition.on('relocated', (location) => {
21867
+ if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
21802
21868
  const start = location && location.start;
21803
21869
  let index = start && Number.isInteger(start.index) ? start.index : -1;
21804
21870
  if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
21805
21871
  if (index < 0 || index >= spineItems.length) return;
21806
21872
  epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
21873
+ epubLocation=window.PapyrusEpubReader.location(location,spineItems.length,epubViewMode==='single'?'paged':'chapter-scroll');
21874
+ if(epubLocation){
21875
+ if(book.locations?.length?.()>0)epubLocation.progress=book.locations.percentageFromCfi(epubLocation.cfi);
21876
+ sendEvent('EPUB_LOCATION',{documentSessionId,location:epubLocation});
21877
+ }
21807
21878
  const nextPage = index + 1;
21808
21879
  if (nextPage === currentPage) return;
21809
21880
  currentPage = nextPage;
@@ -21811,9 +21882,10 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21811
21882
  });
21812
21883
  }
21813
21884
 
21885
+ applyEpubZoom();
21814
21886
  sendEpubDiagnostic('epub.display.start', diagnosticPayload);
21815
21887
  await withEpubStageTimeout(
21816
- displayEpubPage(0),
21888
+ rendition.display(target || spineItems[0]?.href),
21817
21889
  'rendition.display',
21818
21890
  diagnosticPayload,
21819
21891
  );
@@ -21825,7 +21897,6 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21825
21897
  typeof rendition.manager.on === 'function' &&
21826
21898
  typeof rendition.on === 'function'
21827
21899
  ) {
21828
- let touchStart = null;
21829
21900
  let lastScrollOffset = -1;
21830
21901
  const manager = rendition.manager;
21831
21902
  epubScrollDiagnostics = createEpubScrollDiagnostics(manager);
@@ -21900,48 +21971,19 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21900
21971
  return result;
21901
21972
  };
21902
21973
  }
21903
- const getTouchPoint = (event) => {
21904
- const touch =
21905
- (event && event.changedTouches && event.changedTouches[0]) ||
21906
- (event && event.touches && event.touches[0]);
21907
- if (!touch) return null;
21908
- const x = Number(touch.screenX ?? touch.clientX);
21909
- const y = Number(touch.screenY ?? touch.clientY);
21910
- return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
21911
- };
21912
- const handleScroll = (event) => {
21974
+ const handleScroll = event => {
21913
21975
  const offsetY = Math.max(0, Number(event && event.top) || 0);
21914
21976
  epubScrollDiagnostics && epubScrollDiagnostics.scroll(event);
21915
- if (Math.abs(offsetY - lastScrollOffset) < 1) return;
21916
- lastScrollOffset = offsetY;
21917
- sendEvent('VIEWER_SCROLL', { offsetY });
21918
- };
21919
- const handleTouchStart = (event) => {
21920
- touchStart = getTouchPoint(event);
21921
- epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
21922
- };
21923
- const handleTouchMove = (event) => {
21924
- epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
21925
- };
21926
- const handleTouchEnd = (event) => {
21927
- const end = getTouchPoint(event);
21928
- const start = touchStart;
21929
- touchStart = null;
21930
- const selectedText = event?.target?.ownerDocument?.defaultView?.getSelection?.()?.toString().trim() || '';
21931
- if (!selectedText && start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
21932
- sendEvent('VIEWER_TAP', {});
21933
- }
21934
- epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
21977
+ if (Math.abs(offsetY-lastScrollOffset)<1) return;
21978
+ lastScrollOffset=offsetY;
21979
+ sendEvent('VIEWER_SCROLL',{offsetY});
21935
21980
  };
21936
-
21937
21981
  manager.on('scroll', handleScroll);
21938
21982
  const handleScrolled = (event) => {
21939
21983
  epubScrollDiagnostics && epubScrollDiagnostics.settled(event);
21940
21984
  };
21941
21985
  manager.on('scrolled', handleScrolled);
21942
- rendition.on('touchstart', handleTouchStart);
21943
- rendition.on('touchmove', handleTouchMove);
21944
- rendition.on('touchend', handleTouchEnd);
21986
+
21945
21987
  epubInteractionCleanup = () => {
21946
21988
  epubSelectionCleanups.forEach((cleanup) => cleanup());
21947
21989
  epubSelectionCleanups.clear();
@@ -21950,16 +21992,9 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21950
21992
  if (originalEnqueue) queue.enqueue = originalEnqueue;
21951
21993
  if (typeof manager.off === 'function') manager.off('scroll', handleScroll);
21952
21994
  if (typeof manager.off === 'function') manager.off('scrolled', handleScrolled);
21953
- if (typeof rendition.off === 'function') {
21954
- rendition.off('touchstart', handleTouchStart);
21955
- rendition.off('touchmove', handleTouchMove);
21956
- rendition.off('touchend', handleTouchEnd);
21957
- }
21958
21995
  epubScrollDiagnostics = null;
21959
21996
  };
21960
21997
  }
21961
- applyEpubZoom();
21962
-
21963
21998
  const outline = await withEpubStageTimeout(
21964
21999
  buildOutline(),
21965
22000
  'outline',
@@ -21973,6 +22008,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21973
22008
  return { pageCount, outline };
21974
22009
  };
21975
22010
 
22011
+ let epubNavigationQueue = Promise.resolve();
22012
+ let epubNavigationRequest = 0;
21976
22013
  const displayEpubPage = async (pageIndex) => {
21977
22014
  if (!rendition) return;
21978
22015
  const item = spineItems[pageIndex];
@@ -21982,9 +22019,18 @@ https://github.com/nodeca/pako/blob/main/LICENSE
21982
22019
  target,
21983
22020
  pageIndex,
21984
22021
  });
21985
- await rendition.display(target);
21986
- currentPage = pageIndex + 1;
21987
- sendState();
22022
+ const activeRendition = rendition;
22023
+ const session = documentSessionId;
22024
+ const request = ++epubNavigationRequest;
22025
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22026
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22027
+ await activeRendition.display(target);
22028
+ if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
22029
+ currentPage = pageIndex + 1;
22030
+ sendState();
22031
+ });
22032
+ epubNavigationQueue = navigation;
22033
+ await navigation;
21988
22034
  };
21989
22035
 
21990
22036
  const getTextContent = async (pageIndex) => {
@@ -22086,6 +22132,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22086
22132
  };
22087
22133
 
22088
22134
  let nativeMenuSelection = null;
22135
+ let epubViewMode = "single";
22136
+ let epubLocation = null;
22089
22137
 
22090
22138
  const handleCommand = async (message) => {
22091
22139
  const { id, kind, payload } = message;
@@ -22098,6 +22146,11 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22098
22146
  epubAnnotations = [];
22099
22147
  annotationDocumentId = undefined;
22100
22148
  zoom = 1.0;
22149
+ epubViewMode = payload.epubMode === "chapter-scroll" ? "continuous" : "single";
22150
+ epubLocation = null;
22151
+ nativeMenuSelection = null;
22152
+ epubNavigationRequest += 1;
22153
+ epubNavigationQueue = Promise.resolve();
22101
22154
 
22102
22155
  if (currentType === 'text') {
22103
22156
  const result = await loadText(
@@ -22152,17 +22205,23 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22152
22205
  epubAnnotationRenderers.forEach(renderer => renderer.schedule());
22153
22206
  } else {
22154
22207
  const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
22155
- if (!annotation) throw new Error('Annotation not available');
22208
+ if (!annotation || (annotation.anchor.documentId && annotation.anchor.documentId !== annotationDocumentId)) throw new Error('Annotation not available for this document');
22156
22209
  // Navigation uses the CFI, never a synthetic percentage/page rectangle.
22157
22210
  const navigationSession = documentSessionId;
22158
22211
  const activeRendition = rendition;
22159
22212
  // Open the confirmed chapter first, resolve its quote, then navigate its actual range.
22160
- await activeRendition.display(annotation.anchor.href);
22161
- if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22162
- const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22163
- const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22164
- if(!range)throw new Error('EPUB anchor unresolved');
22165
- await activeRendition.display(contents.cfiFromRange(range));
22213
+ epubNavigationRequest += 1;
22214
+ const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
22215
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22216
+ await activeRendition.display(annotation.anchor.href);
22217
+ if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
22218
+ const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
22219
+ const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
22220
+ if(!range)throw new Error('EPUB anchor unresolved');
22221
+ await activeRendition.display(contents.cfiFromRange(range));
22222
+ });
22223
+ epubNavigationQueue = navigation;
22224
+ await navigation;
22166
22225
  }
22167
22226
  sendResponse(id,true,null); return;
22168
22227
  }
@@ -22189,12 +22248,54 @@ https://github.com/nodeca/pako/blob/main/LICENSE
22189
22248
  return;
22190
22249
  }
22191
22250
 
22251
+ if (kind === 'epub-turn-page') {
22252
+ if (epubViewMode !== 'single') {sendResponse(id,true,null); return;}
22253
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB navigation');
22254
+ const active = rendition;
22255
+ epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
22256
+ if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
22257
+ return payload.direction < 0 ? active.prev() : active.next();
22258
+ });
22259
+ await epubNavigationQueue;
22260
+ sendResponse(id,true,null); return;
22261
+ }
22262
+
22263
+ if (kind === 'set-epub-view-mode') {
22264
+ if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB layout session');
22265
+ const next=payload.epubMode ? (payload.epubMode==='paged'?'single':'continuous') : (payload.viewMode==='single'?'single':'continuous');
22266
+ {
22267
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22268
+ if(payload.documentSessionId!==documentSessionId || next===epubViewMode)return;
22269
+ const target=epubLocation?.cfi;
22270
+ epubViewMode=next;
22271
+ await renderEpubRendition('layout',epubLoadGeneration,target);
22272
+ });
22273
+ await epubNavigationQueue;
22274
+ }
22275
+ sendResponse(id,true,null);return;
22276
+ }
22277
+ if(kind === 'epub-go-to-location'){
22278
+ if(currentType!=='epub' || payload.documentSessionId!==documentSessionId || !window.PapyrusEpubReader.validCfi(payload.cfi))throw new Error('Invalid EPUB location');
22279
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(()=>payload.documentSessionId===documentSessionId?rendition.display(payload.cfi):undefined);
22280
+ await epubNavigationQueue;sendResponse(id,true,null);return;
22281
+ }
22282
+
22192
22283
  if (kind === 'set-zoom') {
22193
- zoom = Math.max(0.5, Math.min(4.0, payload.zoom || 1.0));
22194
- applyZoom();
22195
- sendState();
22196
- sendResponse(id, true, { zoom });
22197
- return;
22284
+ const requestedZoom=Math.max(0.5,Math.min(4.0,payload.zoom || 1.0));
22285
+ if(currentType === 'epub') {
22286
+ const session=documentSessionId;
22287
+ epubNavigationQueue=epubNavigationQueue.catch(()=>{}).then(async()=>{
22288
+ if(session!==documentSessionId)return;
22289
+ const active=rendition;
22290
+ const cfi=epubLocation?.cfi;
22291
+ zoom=requestedZoom;applyEpubZoom();
22292
+ // Let the content CSS update before redisplaying the logical anchor.
22293
+ await new Promise(resolve=>(window.requestAnimationFrame || (callback=>setTimeout(callback,0)))(resolve));
22294
+ if(cfi && active===rendition && session===documentSessionId)await active.display(cfi);
22295
+ });
22296
+ await epubNavigationQueue;
22297
+ } else {zoom=requestedZoom;applyZoom();}
22298
+ sendState();sendResponse(id,true,{zoom});return;
22198
22299
  }
22199
22300
 
22200
22301
  if (kind === 'set-rotation') {