@papyrus-sdk/ui-react-native 0.2.35-beta.1 → 0.2.35-beta.11
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/README.md +8 -2
- package/dist/index.d.mts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.js +2881 -1734
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2639 -1484
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -7
- package/runtime/epubAnnotations.js +118 -0
- package/runtime/epubAnnotations.test.ts +31 -0
- package/runtime/epubSelectionMenu.test.ts +39 -0
- package/runtime/index.html +310 -15
- package/runtime/runtime.js +310 -15
- package/runtime/syncComicRuntime.mjs +2 -1
package/runtime/index.html
CHANGED
|
@@ -20483,6 +20483,127 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
20483
20483
|
<!-- @papyrus-comic-runtime:end -->
|
|
20484
20484
|
<script>
|
|
20485
20485
|
(function () {
|
|
20486
|
+
/* @papyrus-epub-annotations:start */
|
|
20487
|
+
(function (root, factory) {
|
|
20488
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
20489
|
+
else root.PapyrusEpubAnnotations = factory();
|
|
20490
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
20491
|
+
function validAnchor(anchor) {
|
|
20492
|
+
return anchor && anchor.version === 1 && anchor.kind === 'epub-cfi' &&
|
|
20493
|
+
typeof anchor.cfiRange === 'string' && anchor.cfiRange.length <= 4096 && /^epubcfi\([^\n<>]+\)$/.test(anchor.cfiRange) &&
|
|
20494
|
+
typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
|
|
20495
|
+
typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
|
|
20496
|
+
}
|
|
20497
|
+
function textNodes(document) {
|
|
20498
|
+
var walker = document.createTreeWalker(document.body, 4);
|
|
20499
|
+
var nodes = [], text = '', node;
|
|
20500
|
+
while ((node = walker.nextNode())) {
|
|
20501
|
+
if (node.parentElement.closest('script,style,[data-papyrus-overlay]')) continue;
|
|
20502
|
+
nodes.push({node:node, start:text.length}); text += node.textContent;
|
|
20503
|
+
}
|
|
20504
|
+
return {nodes:nodes,text:text};
|
|
20505
|
+
}
|
|
20506
|
+
function recoverRange(document, anchor) {
|
|
20507
|
+
var source = textNodes(document), match = null, start = source.text.indexOf(anchor.quote);
|
|
20508
|
+
while (start >= 0) {
|
|
20509
|
+
var end = start + anchor.quote.length;
|
|
20510
|
+
if ((!anchor.prefix || source.text.slice(Math.max(0,start-anchor.prefix.length),start) === anchor.prefix) &&
|
|
20511
|
+
(!anchor.suffix || source.text.slice(end,end+anchor.suffix.length) === anchor.suffix)) {
|
|
20512
|
+
if (match) return null; match = {start:start,end:end};
|
|
20513
|
+
}
|
|
20514
|
+
start = source.text.indexOf(anchor.quote,start+1);
|
|
20515
|
+
}
|
|
20516
|
+
if (!match) return null;
|
|
20517
|
+
var range = document.createRange();
|
|
20518
|
+
var first = source.nodes.find(function(n){return n.start+n.node.length > match.start;});
|
|
20519
|
+
var last = source.nodes.find(function(n){return n.start+n.node.length >= match.end;});
|
|
20520
|
+
if (!first || !last) return null;
|
|
20521
|
+
range.setStart(first.node,match.start-first.start); range.setEnd(last.node,match.end-last.start);
|
|
20522
|
+
return range;
|
|
20523
|
+
}
|
|
20524
|
+
function resolveRange(contents, anchor) {
|
|
20525
|
+
if (!validAnchor(anchor)) return null;
|
|
20526
|
+
try { var range = contents.range(anchor.cfiRange); if (range && range.toString() === anchor.quote) return range; } catch (_) {}
|
|
20527
|
+
return recoverRange(contents.document,anchor);
|
|
20528
|
+
}
|
|
20529
|
+
function selection(contents, cfiRange, href, documentId) {
|
|
20530
|
+
var range;
|
|
20531
|
+
try { range = contents.range(cfiRange); } catch (_) { return null; }
|
|
20532
|
+
if (!range || !range.toString()) return null;
|
|
20533
|
+
var before = contents.document.createRange(); before.selectNodeContents(contents.document.body); before.setEnd(range.startContainer,range.startOffset);
|
|
20534
|
+
var after = contents.document.createRange(); after.selectNodeContents(contents.document.body); after.setStart(range.endContainer,range.endOffset);
|
|
20535
|
+
var anchor = {version:1,kind:'epub-cfi',cfiRange:cfiRange,href:href,spineIndex:contents.sectionIndex,documentId:documentId,quote:range.toString(),prefix:before.toString().slice(-48),suffix:after.toString().slice(0,48)};
|
|
20536
|
+
return validAnchor(anchor) ? anchor : null;
|
|
20537
|
+
}
|
|
20538
|
+
function install(contents, options) {
|
|
20539
|
+
var doc = contents.document, win = contents.window, alive = true, frame = 0;
|
|
20540
|
+
var overlay = doc.createElement('div'); overlay.dataset.papyrusOverlay = 'annotations';
|
|
20541
|
+
overlay.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:0;pointer-events:none;z-index:3';
|
|
20542
|
+
var originalPosition=doc.body.style.position;
|
|
20543
|
+
if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
|
|
20544
|
+
doc.body.appendChild(overlay);
|
|
20545
|
+
var registry = new Map(), hitRegions = [];
|
|
20546
|
+
function render() {
|
|
20547
|
+
frame = 0; if (!alive || !options.isCurrent()) return;
|
|
20548
|
+
var keep = new Set(), noteGroups = new Map();
|
|
20549
|
+
hitRegions = [];
|
|
20550
|
+
options.annotations().forEach(function(annotation) {
|
|
20551
|
+
var anchor = annotation.anchor;
|
|
20552
|
+
if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
|
|
20553
|
+
var range = resolveRange(contents,anchor); if (!range) return;
|
|
20554
|
+
var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
|
|
20555
|
+
if (!rects.length) return;
|
|
20556
|
+
keep.add(annotation.id);
|
|
20557
|
+
var group = registry.get(annotation.id);
|
|
20558
|
+
if (!group) { group = doc.createElement('div'); group.dataset.annotationId = annotation.id; overlay.appendChild(group); registry.set(annotation.id,group); if (!doc.defaultView.matchMedia || !doc.defaultView.matchMedia('(prefers-reduced-motion: reduce)').matches) { if (typeof group.animate === 'function') group.animate([{opacity:0},{opacity:1}],{duration:180,easing:'ease-out'}); } }
|
|
20559
|
+
group.replaceChildren();
|
|
20560
|
+
hitRegions.push({id:annotation.id,rects:rects});
|
|
20561
|
+
var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
|
|
20562
|
+
var style = annotation.markupStyle || annotation.type;
|
|
20563
|
+
var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
|
|
20564
|
+
rects.forEach(function(rect) {
|
|
20565
|
+
var mark = doc.createElement('div');
|
|
20566
|
+
var bodyRect=doc.body.getBoundingClientRect();
|
|
20567
|
+
var height = style === 'highlight' ? rect.height : 2;
|
|
20568
|
+
var y = style === 'strikeout' ? rect.top+rect.height/2 : style === 'underline' ? rect.bottom-2 : rect.top;
|
|
20569
|
+
mark.style.cssText = 'position:absolute;pointer-events:none;left:'+(rect.left-bodyRect.left)+'px;top:'+(y-bodyRect.top)+'px;width:'+rect.width+'px;height:'+height+'px;background:'+color+';opacity:'+opacity;
|
|
20570
|
+
if (['highlight','underline','strikeout'].includes(style)) group.appendChild(mark);
|
|
20571
|
+
});
|
|
20572
|
+
var hasNote = annotation.noteContent !== undefined || annotation.type === 'comment' || annotation.type === 'text';
|
|
20573
|
+
if (hasNote) {
|
|
20574
|
+
var last = rects[rects.length-1], button = doc.createElement('button');
|
|
20575
|
+
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);
|
|
20578
|
+
var neighbors=noteGroups.get(key);
|
|
20579
|
+
if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
|
|
20580
|
+
neighbors.ids.push(annotation.id);
|
|
20581
|
+
}
|
|
20582
|
+
});
|
|
20583
|
+
noteGroups.forEach(function(group){
|
|
20584
|
+
group.button.textContent=group.ids.length>1?String(group.ids.length):'●';
|
|
20585
|
+
group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
|
|
20586
|
+
});
|
|
20587
|
+
registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
|
|
20588
|
+
}
|
|
20589
|
+
function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
|
|
20590
|
+
function tapMarkup(event) {
|
|
20591
|
+
if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
|
|
20592
|
+
var hit=hitRegions.find(function(item){return item.rects.some(function(r){return event.clientX>=r.left && event.clientX<=r.right && event.clientY>=r.top && event.clientY<=r.bottom;});});
|
|
20593
|
+
if(hit) {event.stopPropagation();options.onTap(hit.id);}
|
|
20594
|
+
}
|
|
20595
|
+
doc.addEventListener('click',tapMarkup);
|
|
20596
|
+
var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
|
|
20597
|
+
if (observer) observer.observe(doc.body);
|
|
20598
|
+
win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
|
|
20599
|
+
if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
|
|
20600
|
+
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();}};
|
|
20602
|
+
}
|
|
20603
|
+
return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
|
|
20604
|
+
});
|
|
20605
|
+
|
|
20606
|
+
/* @papyrus-epub-annotations:end */
|
|
20486
20607
|
const viewer = document.getElementById('viewer');
|
|
20487
20608
|
const DEFAULT_FONT_SIZE = 16;
|
|
20488
20609
|
const TEXT_PAGE_CHUNK = 1600;
|
|
@@ -20490,6 +20611,11 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
20490
20611
|
let currentType = null;
|
|
20491
20612
|
let book = null;
|
|
20492
20613
|
let rendition = null;
|
|
20614
|
+
let documentSessionId = null;
|
|
20615
|
+
let annotationDocumentId;
|
|
20616
|
+
let annotationLabels = {};
|
|
20617
|
+
let epubAnnotations = [];
|
|
20618
|
+
const epubAnnotationRenderers = new Map();
|
|
20493
20619
|
let spineItems = [];
|
|
20494
20620
|
let textPages = [];
|
|
20495
20621
|
let textContainer = null;
|
|
@@ -20897,6 +21023,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
20897
21023
|
};
|
|
20898
21024
|
|
|
20899
21025
|
const clearViewer = () => {
|
|
21026
|
+
epubAnnotationRenderers.forEach(renderer => renderer.destroy());
|
|
21027
|
+
epubAnnotationRenderers.clear();
|
|
20900
21028
|
if (epubInteractionCleanup) {
|
|
20901
21029
|
epubInteractionCleanup();
|
|
20902
21030
|
epubInteractionCleanup = null;
|
|
@@ -21554,6 +21682,7 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21554
21682
|
clearViewer();
|
|
21555
21683
|
rendition = book.renderTo(viewer, {
|
|
21556
21684
|
manager: 'continuous',
|
|
21685
|
+
allowScriptedContent: false,
|
|
21557
21686
|
width: '100%',
|
|
21558
21687
|
height: '100%',
|
|
21559
21688
|
flow: 'scrolled-continuous',
|
|
@@ -21562,10 +21691,22 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21562
21691
|
sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
|
|
21563
21692
|
|
|
21564
21693
|
if (rendition && rendition.hooks && rendition.hooks.content) {
|
|
21694
|
+
const session = documentSessionId;
|
|
21695
|
+
const activeRendition = rendition;
|
|
21565
21696
|
rendition.hooks.content.register((contents) => {
|
|
21697
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
21698
|
+
if (href && window.PapyrusEpubAnnotations) {
|
|
21699
|
+
const renderer = window.PapyrusEpubAnnotations.install(contents, {
|
|
21700
|
+
href, get documentId() { return annotationDocumentId; },
|
|
21701
|
+
isCurrent: () => session === documentSessionId && activeRendition === rendition,
|
|
21702
|
+
annotations: () => epubAnnotations, labels: () => annotationLabels,
|
|
21703
|
+
onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
|
|
21704
|
+
});
|
|
21705
|
+
epubAnnotationRenderers.set(contents,renderer);
|
|
21706
|
+
}
|
|
21566
21707
|
const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
|
|
21567
21708
|
if (frame) {
|
|
21568
|
-
frame.setAttribute('sandbox', 'allow-
|
|
21709
|
+
frame.setAttribute('sandbox', 'allow-same-origin');
|
|
21569
21710
|
}
|
|
21570
21711
|
const selectionDocument = contents && contents.document;
|
|
21571
21712
|
const selectionWindow = contents && contents.window;
|
|
@@ -21575,13 +21716,46 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21575
21716
|
selectionWindow &&
|
|
21576
21717
|
typeof selectionWindow.getSelection === 'function'
|
|
21577
21718
|
) {
|
|
21719
|
+
let selectionTimer = null;
|
|
21720
|
+
let lastSelectionKey = '';
|
|
21721
|
+
let lastSelectionAt = 0;
|
|
21722
|
+
const reportSelection = () => {
|
|
21723
|
+
selectionTimer = null;
|
|
21724
|
+
if (session !== documentSessionId || activeRendition !== rendition) return;
|
|
21725
|
+
const selected = selectionWindow.getSelection();
|
|
21726
|
+
if (!selected || selected.isCollapsed || selected.rangeCount === 0 ||
|
|
21727
|
+
typeof contents.cfiFromRange !== 'function') return;
|
|
21728
|
+
const range = selected.getRangeAt(0).cloneRange();
|
|
21729
|
+
if (!range.toString().trim()) return;
|
|
21730
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
21731
|
+
if (!href) return;
|
|
21732
|
+
let cfi;
|
|
21733
|
+
try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
|
|
21734
|
+
const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
|
|
21735
|
+
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;
|
|
21741
|
+
const payload = {documentSessionId: session, text: anchor.quote,
|
|
21742
|
+
pageIndex: contents.sectionIndex + 1, anchor};
|
|
21743
|
+
nativeMenuSelection = {contents, payload};
|
|
21744
|
+
sendEvent('EPUB_TEXT_SELECTED', payload);
|
|
21745
|
+
};
|
|
21578
21746
|
const handleSelectionChange = () => {
|
|
21579
21747
|
const selection = selectionWindow.getSelection();
|
|
21580
21748
|
const text = selection ? selection.toString().trim() : '';
|
|
21581
21749
|
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(Boolean(text));
|
|
21750
|
+
if (selectionTimer !== null) clearTimeout(selectionTimer);
|
|
21751
|
+
// epub.js "selected" is not reliable on every iOS WKWebView path.
|
|
21752
|
+
// The content document still emits selectionchange, so resolve its
|
|
21753
|
+
// live Range to a CFI before the native menu action can fire.
|
|
21754
|
+
if (text) reportSelection();
|
|
21582
21755
|
};
|
|
21583
21756
|
selectionDocument.addEventListener('selectionchange', handleSelectionChange);
|
|
21584
21757
|
const selectionCleanup = () => {
|
|
21758
|
+
if (selectionTimer !== null) clearTimeout(selectionTimer);
|
|
21585
21759
|
selectionDocument.removeEventListener('selectionchange', handleSelectionChange);
|
|
21586
21760
|
epubSelectionCleanups.delete(selectionCleanup);
|
|
21587
21761
|
};
|
|
@@ -21602,22 +21776,30 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21602
21776
|
rendition.hooks.unloaded.register((view) => {
|
|
21603
21777
|
const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
|
|
21604
21778
|
if (typeof cleanup === 'function') cleanup();
|
|
21779
|
+
const renderer = epubAnnotationRenderers.get(view?.contents);
|
|
21780
|
+
if (renderer) renderer.destroy();
|
|
21781
|
+
epubAnnotationRenderers.delete(view?.contents);
|
|
21605
21782
|
});
|
|
21606
21783
|
}
|
|
21607
21784
|
|
|
21608
21785
|
if (rendition && typeof rendition.on === 'function') {
|
|
21786
|
+
const selectionSession = documentSessionId;
|
|
21787
|
+
const selectionRendition = rendition;
|
|
21609
21788
|
rendition.on('selected', (cfiRange, contents) => {
|
|
21610
|
-
|
|
21611
|
-
const
|
|
21612
|
-
|
|
21613
|
-
if (
|
|
21614
|
-
|
|
21615
|
-
}
|
|
21616
|
-
|
|
21617
|
-
|
|
21618
|
-
}
|
|
21789
|
+
if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
|
|
21790
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
21791
|
+
const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
|
|
21792
|
+
if (!anchor) return;
|
|
21793
|
+
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
|
|
21794
|
+
const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
|
|
21795
|
+
nativeMenuSelection = {contents, payload};
|
|
21796
|
+
sendEvent('EPUB_TEXT_SELECTED', payload);
|
|
21619
21797
|
});
|
|
21798
|
+
['rendered','resized','layout'].forEach(name => rendition.on(name, () => {
|
|
21799
|
+
if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
21800
|
+
}));
|
|
21620
21801
|
rendition.on('relocated', (location) => {
|
|
21802
|
+
if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
|
|
21621
21803
|
const start = location && location.start;
|
|
21622
21804
|
let index = start && Number.isInteger(start.index) ? start.index : -1;
|
|
21623
21805
|
if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
|
|
@@ -21736,17 +21918,27 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21736
21918
|
sendEvent('VIEWER_SCROLL', { offsetY });
|
|
21737
21919
|
};
|
|
21738
21920
|
const handleTouchStart = (event) => {
|
|
21739
|
-
touchStart = getTouchPoint(event);
|
|
21921
|
+
touchStart = event?.touches?.length > 1 ? null : getTouchPoint(event);
|
|
21740
21922
|
epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
|
|
21741
21923
|
};
|
|
21742
21924
|
const handleTouchMove = (event) => {
|
|
21925
|
+
if (event?.touches?.length > 1) touchStart = null;
|
|
21743
21926
|
epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
|
|
21744
21927
|
};
|
|
21745
21928
|
const handleTouchEnd = (event) => {
|
|
21746
21929
|
const end = getTouchPoint(event);
|
|
21747
21930
|
const start = touchStart;
|
|
21748
21931
|
touchStart = null;
|
|
21749
|
-
|
|
21932
|
+
const selectedText = event?.target?.ownerDocument?.defaultView?.getSelection?.()?.toString().trim() || '';
|
|
21933
|
+
if (!selectedText && start && end && epubViewMode === 'single' && Math.abs(end.x-start.x) >= 48 && Math.abs(end.x-start.x) > Math.abs(end.y-start.y)*1.5) {
|
|
21934
|
+
const active = rendition;
|
|
21935
|
+
const interactionSession = documentSessionId;
|
|
21936
|
+
epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
|
|
21937
|
+
if (active !== rendition || interactionSession !== documentSessionId) return;
|
|
21938
|
+
return end.x < start.x ? active.next() : active.prev();
|
|
21939
|
+
});
|
|
21940
|
+
epubNavigationQueue.catch(error => sendEvent('EPUB_NAVIGATION_ERROR', {message:String(error)}));
|
|
21941
|
+
} else if (!selectedText && start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
|
|
21750
21942
|
sendEvent('VIEWER_TAP', {});
|
|
21751
21943
|
}
|
|
21752
21944
|
epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
|
|
@@ -21791,6 +21983,8 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21791
21983
|
return { pageCount, outline };
|
|
21792
21984
|
};
|
|
21793
21985
|
|
|
21986
|
+
let epubNavigationQueue = Promise.resolve();
|
|
21987
|
+
let epubNavigationRequest = 0;
|
|
21794
21988
|
const displayEpubPage = async (pageIndex) => {
|
|
21795
21989
|
if (!rendition) return;
|
|
21796
21990
|
const item = spineItems[pageIndex];
|
|
@@ -21800,9 +21994,18 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21800
21994
|
target,
|
|
21801
21995
|
pageIndex,
|
|
21802
21996
|
});
|
|
21803
|
-
|
|
21804
|
-
|
|
21805
|
-
|
|
21997
|
+
const activeRendition = rendition;
|
|
21998
|
+
const session = documentSessionId;
|
|
21999
|
+
const request = ++epubNavigationRequest;
|
|
22000
|
+
const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
|
|
22001
|
+
if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
|
|
22002
|
+
await activeRendition.display(target);
|
|
22003
|
+
if (request !== epubNavigationRequest || session !== documentSessionId || activeRendition !== rendition) return;
|
|
22004
|
+
currentPage = pageIndex + 1;
|
|
22005
|
+
sendState();
|
|
22006
|
+
});
|
|
22007
|
+
epubNavigationQueue = navigation;
|
|
22008
|
+
await navigation;
|
|
21806
22009
|
};
|
|
21807
22010
|
|
|
21808
22011
|
const getTextContent = async (pageIndex) => {
|
|
@@ -21903,6 +22106,9 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21903
22106
|
}
|
|
21904
22107
|
};
|
|
21905
22108
|
|
|
22109
|
+
let nativeMenuSelection = null;
|
|
22110
|
+
let epubViewMode = "continuous";
|
|
22111
|
+
|
|
21906
22112
|
const handleCommand = async (message) => {
|
|
21907
22113
|
const { id, kind, payload } = message;
|
|
21908
22114
|
|
|
@@ -21910,7 +22116,13 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21910
22116
|
if (kind === 'load') {
|
|
21911
22117
|
const generation = (epubLoadGeneration += 1);
|
|
21912
22118
|
currentType = payload.type;
|
|
22119
|
+
documentSessionId = payload.documentSessionId || null;
|
|
22120
|
+
epubAnnotations = [];
|
|
22121
|
+
annotationDocumentId = undefined;
|
|
21913
22122
|
zoom = 1.0;
|
|
22123
|
+
epubViewMode = "continuous";
|
|
22124
|
+
epubNavigationRequest += 1;
|
|
22125
|
+
epubNavigationQueue = Promise.resolve();
|
|
21914
22126
|
|
|
21915
22127
|
if (currentType === 'text') {
|
|
21916
22128
|
const result = await loadText(
|
|
@@ -21950,6 +22162,42 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21950
22162
|
throw new Error('Unsupported document type');
|
|
21951
22163
|
}
|
|
21952
22164
|
|
|
22165
|
+
if (kind === 'epub-annotations' || kind === 'epub-annotation-navigate') {
|
|
22166
|
+
if (currentType !== 'epub' || !documentSessionId || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB annotation session');
|
|
22167
|
+
if (kind === 'epub-annotations') {
|
|
22168
|
+
const incoming = payload.annotations;
|
|
22169
|
+
if (!Array.isArray(incoming) || incoming.length > 10000 || incoming.some(a => !a || typeof a.id !== 'string' || a.id.length > 256)) throw new Error('Invalid EPUB annotations');
|
|
22170
|
+
const ids = new Set();
|
|
22171
|
+
epubAnnotations = incoming.filter(a => {
|
|
22172
|
+
if (ids.has(a.id) || !window.PapyrusEpubAnnotations.validAnchor(a.anchor)) return false;
|
|
22173
|
+
ids.add(a.id); return true;
|
|
22174
|
+
});
|
|
22175
|
+
annotationDocumentId = typeof payload.documentId === 'string' ? payload.documentId : undefined;
|
|
22176
|
+
annotationLabels = {comment: typeof payload.labels?.comment === 'string' ? payload.labels.comment.slice(0,128) : ''};
|
|
22177
|
+
epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
22178
|
+
} else {
|
|
22179
|
+
const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
|
|
22180
|
+
if (!annotation || (annotation.anchor.documentId && annotation.anchor.documentId !== annotationDocumentId)) throw new Error('Annotation not available for this document');
|
|
22181
|
+
// Navigation uses the CFI, never a synthetic percentage/page rectangle.
|
|
22182
|
+
const navigationSession = documentSessionId;
|
|
22183
|
+
const activeRendition = rendition;
|
|
22184
|
+
// Open the confirmed chapter first, resolve its quote, then navigate its actual range.
|
|
22185
|
+
epubNavigationRequest += 1;
|
|
22186
|
+
const navigation = epubNavigationQueue.catch(() => {}).then(async () => {
|
|
22187
|
+
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
22188
|
+
await activeRendition.display(annotation.anchor.href);
|
|
22189
|
+
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
22190
|
+
const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
|
|
22191
|
+
const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
|
|
22192
|
+
if(!range)throw new Error('EPUB anchor unresolved');
|
|
22193
|
+
await activeRendition.display(contents.cfiFromRange(range));
|
|
22194
|
+
});
|
|
22195
|
+
epubNavigationQueue = navigation;
|
|
22196
|
+
await navigation;
|
|
22197
|
+
}
|
|
22198
|
+
sendResponse(id,true,null); return;
|
|
22199
|
+
}
|
|
22200
|
+
|
|
21953
22201
|
if (kind === 'go-to-page') {
|
|
21954
22202
|
const page = Math.max(1, payload.page || 1);
|
|
21955
22203
|
if (currentType === 'text') {
|
|
@@ -21972,6 +22220,33 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
21972
22220
|
return;
|
|
21973
22221
|
}
|
|
21974
22222
|
|
|
22223
|
+
if (kind === 'epub-turn-page') {
|
|
22224
|
+
if (epubViewMode !== 'single') {sendResponse(id,true,null); return;}
|
|
22225
|
+
if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB navigation');
|
|
22226
|
+
const active = rendition;
|
|
22227
|
+
epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
|
|
22228
|
+
if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
|
|
22229
|
+
return payload.direction < 0 ? active.prev() : active.next();
|
|
22230
|
+
});
|
|
22231
|
+
await epubNavigationQueue;
|
|
22232
|
+
sendResponse(id,true,null); return;
|
|
22233
|
+
}
|
|
22234
|
+
|
|
22235
|
+
if (kind === 'set-epub-view-mode') {
|
|
22236
|
+
if (currentType !== 'epub' || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB layout session');
|
|
22237
|
+
epubViewMode = payload.viewMode === 'single' ? 'single' : 'continuous';
|
|
22238
|
+
const active = rendition;
|
|
22239
|
+
epubNavigationQueue = epubNavigationQueue.catch(() => {}).then(() => {
|
|
22240
|
+
if (active !== rendition || payload.documentSessionId !== documentSessionId) return;
|
|
22241
|
+
const flow = epubViewMode === 'single' ? 'paginated' : 'scrolled-continuous';
|
|
22242
|
+
if (active.settings.flow === flow) return;
|
|
22243
|
+
active.flow(flow);
|
|
22244
|
+
return active.q.enqueue(() => Promise.resolve());
|
|
22245
|
+
});
|
|
22246
|
+
await epubNavigationQueue;
|
|
22247
|
+
sendResponse(id,true,null); return;
|
|
22248
|
+
}
|
|
22249
|
+
|
|
21975
22250
|
if (kind === 'set-zoom') {
|
|
21976
22251
|
zoom = Math.max(0.5, Math.min(4.0, payload.zoom || 1.0));
|
|
21977
22252
|
applyZoom();
|
|
@@ -22119,6 +22394,26 @@ https://github.com/nodeca/pako/blob/main/LICENSE
|
|
|
22119
22394
|
return;
|
|
22120
22395
|
}
|
|
22121
22396
|
|
|
22397
|
+
if (message.type === 'epub-selection-action') {
|
|
22398
|
+
const selected = nativeMenuSelection;
|
|
22399
|
+
if (!selected || message.documentSessionId !== documentSessionId ||
|
|
22400
|
+
selected.payload.documentSessionId !== documentSessionId) return;
|
|
22401
|
+
if (message.key === 'selectAll') {
|
|
22402
|
+
const doc = selected.contents.document;
|
|
22403
|
+
const range = doc.createRange();
|
|
22404
|
+
range.selectNodeContents(doc.body);
|
|
22405
|
+
const selection = selected.contents.window.getSelection();
|
|
22406
|
+
selection.removeAllRanges();
|
|
22407
|
+
selection.addRange(range);
|
|
22408
|
+
return;
|
|
22409
|
+
}
|
|
22410
|
+
if (!['copy','define','highlight','underline','strikeout','comment'].includes(message.key)) return;
|
|
22411
|
+
sendEvent('EPUB_SELECTION_ACTION', {...selected.payload, action: message.key});
|
|
22412
|
+
selected.contents.window.getSelection()?.removeAllRanges();
|
|
22413
|
+
nativeMenuSelection = null;
|
|
22414
|
+
return;
|
|
22415
|
+
}
|
|
22416
|
+
|
|
22122
22417
|
if (!message.kind) return;
|
|
22123
22418
|
handleCommand(message);
|
|
22124
22419
|
};
|