@papyrus-sdk/ui-react-native 0.2.35-beta.8 → 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/LICENSE +21 -0
- package/dist/index.d.mts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +2423 -1793
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2033 -1409
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -13
- package/runtime/comicRuntime.test.ts +9 -9
- package/runtime/epubAnnotations.js +121 -0
- package/runtime/epubAnnotations.test.ts +35 -0
- package/runtime/epubReader.js +58 -0
- package/runtime/epubReader.test.ts +74 -0
- package/runtime/epubRuntime.test.ts +48 -0
- package/runtime/epubSelectionMenu.test.ts +39 -0
- package/runtime/index.html +449 -67
- package/runtime/runtime.js +449 -67
- package/runtime/syncComicRuntime.mjs +8 -4
package/runtime/runtime.js
CHANGED
|
@@ -109,6 +109,191 @@
|
|
|
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 */
|
|
173
|
+
/* @papyrus-epub-annotations:start */
|
|
174
|
+
(function (root, factory) {
|
|
175
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
176
|
+
else root.PapyrusEpubAnnotations = factory();
|
|
177
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
178
|
+
function validAnchor(anchor) {
|
|
179
|
+
return anchor && anchor.version === 1 && anchor.kind === 'epub-cfi' &&
|
|
180
|
+
typeof anchor.cfiRange === 'string' && anchor.cfiRange.length <= 4096 && /^epubcfi\([^\n<>]+\)$/.test(anchor.cfiRange) &&
|
|
181
|
+
typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
|
|
182
|
+
typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
|
|
183
|
+
}
|
|
184
|
+
function textNodes(document) {
|
|
185
|
+
var walker = document.createTreeWalker(document.body, 4);
|
|
186
|
+
var nodes = [], text = '', node;
|
|
187
|
+
while ((node = walker.nextNode())) {
|
|
188
|
+
if (node.parentElement.closest('script,style,[data-papyrus-overlay]')) continue;
|
|
189
|
+
nodes.push({node:node, start:text.length}); text += node.textContent;
|
|
190
|
+
}
|
|
191
|
+
return {nodes:nodes,text:text};
|
|
192
|
+
}
|
|
193
|
+
function recoverRange(document, anchor) {
|
|
194
|
+
var source = textNodes(document), match = null, start = source.text.indexOf(anchor.quote);
|
|
195
|
+
while (start >= 0) {
|
|
196
|
+
var end = start + anchor.quote.length;
|
|
197
|
+
if ((!anchor.prefix || source.text.slice(Math.max(0,start-anchor.prefix.length),start) === anchor.prefix) &&
|
|
198
|
+
(!anchor.suffix || source.text.slice(end,end+anchor.suffix.length) === anchor.suffix)) {
|
|
199
|
+
if (match) return null; match = {start:start,end:end};
|
|
200
|
+
}
|
|
201
|
+
start = source.text.indexOf(anchor.quote,start+1);
|
|
202
|
+
}
|
|
203
|
+
if (!match) return null;
|
|
204
|
+
var range = document.createRange();
|
|
205
|
+
var first = source.nodes.find(function(n){return n.start+n.node.length > match.start;});
|
|
206
|
+
var last = source.nodes.find(function(n){return n.start+n.node.length >= match.end;});
|
|
207
|
+
if (!first || !last) return null;
|
|
208
|
+
range.setStart(first.node,match.start-first.start); range.setEnd(last.node,match.end-last.start);
|
|
209
|
+
return range;
|
|
210
|
+
}
|
|
211
|
+
function resolveRange(contents, anchor) {
|
|
212
|
+
if (!validAnchor(anchor)) return null;
|
|
213
|
+
try { var range = contents.range(anchor.cfiRange); if (range && range.toString() === anchor.quote) return range; } catch (_) {}
|
|
214
|
+
return recoverRange(contents.document,anchor);
|
|
215
|
+
}
|
|
216
|
+
function selection(contents, cfiRange, href, documentId) {
|
|
217
|
+
var range;
|
|
218
|
+
try { range = contents.range(cfiRange); } catch (_) { return null; }
|
|
219
|
+
if (!range || !range.toString()) return null;
|
|
220
|
+
var before = contents.document.createRange(); before.selectNodeContents(contents.document.body); before.setEnd(range.startContainer,range.startOffset);
|
|
221
|
+
var after = contents.document.createRange(); after.selectNodeContents(contents.document.body); after.setStart(range.endContainer,range.endOffset);
|
|
222
|
+
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)};
|
|
223
|
+
return validAnchor(anchor) ? anchor : null;
|
|
224
|
+
}
|
|
225
|
+
function install(contents, options) {
|
|
226
|
+
var doc = contents.document, win = contents.window, alive = true, frame = 0;
|
|
227
|
+
var overlay = doc.createElement('div'); overlay.dataset.papyrusOverlay = 'annotations';
|
|
228
|
+
overlay.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:0;pointer-events:none;z-index:3';
|
|
229
|
+
var originalPosition=doc.body.style.position;
|
|
230
|
+
if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
|
|
231
|
+
doc.body.appendChild(overlay);
|
|
232
|
+
var registry = new Map(), hitRegions = [];
|
|
233
|
+
function render() {
|
|
234
|
+
frame = 0; if (!alive || !options.isCurrent()) return;
|
|
235
|
+
var keep = new Set(), noteGroups = new Map();
|
|
236
|
+
hitRegions = [];
|
|
237
|
+
options.annotations().forEach(function(annotation) {
|
|
238
|
+
var anchor = annotation.anchor;
|
|
239
|
+
if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
|
|
240
|
+
var range = resolveRange(contents,anchor); if (!range) return;
|
|
241
|
+
var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
|
|
242
|
+
if (!rects.length) return;
|
|
243
|
+
keep.add(annotation.id);
|
|
244
|
+
var group = registry.get(annotation.id);
|
|
245
|
+
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'}); } }
|
|
246
|
+
group.replaceChildren();
|
|
247
|
+
hitRegions.push({id:annotation.id,rects:rects});
|
|
248
|
+
var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
|
|
249
|
+
var style = annotation.markupStyle || annotation.type;
|
|
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);
|
|
252
|
+
rects.forEach(function(rect) {
|
|
253
|
+
var mark = doc.createElement('div');
|
|
254
|
+
var bodyRect=doc.body.getBoundingClientRect();
|
|
255
|
+
var height = style === 'highlight' ? rect.height : 2;
|
|
256
|
+
var y = style === 'strikeout' ? rect.top+rect.height/2 : style === 'underline' ? rect.bottom-2 : rect.top;
|
|
257
|
+
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;
|
|
258
|
+
if (['highlight','underline','strikeout'].includes(style)) group.appendChild(mark);
|
|
259
|
+
});
|
|
260
|
+
var hasNote = annotation.noteContent !== undefined || annotation.type === 'comment' || annotation.type === 'text';
|
|
261
|
+
if (hasNote) {
|
|
262
|
+
var last = rects[rects.length-1], button = doc.createElement('button');
|
|
263
|
+
button.textContent = '●'; button.setAttribute('aria-label',options.labels().comment || '');
|
|
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);
|
|
268
|
+
var neighbors=noteGroups.get(key);
|
|
269
|
+
if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
|
|
270
|
+
neighbors.ids.push(annotation.id);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
noteGroups.forEach(function(group){
|
|
274
|
+
group.button.textContent=group.ids.length>1?String(group.ids.length):'●';
|
|
275
|
+
group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
|
|
276
|
+
});
|
|
277
|
+
registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
|
|
278
|
+
}
|
|
279
|
+
function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
|
|
280
|
+
function tapMarkup(event) {
|
|
281
|
+
if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
|
|
282
|
+
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;});});
|
|
283
|
+
if(hit) {event.stopPropagation();options.onTap(hit.id);}
|
|
284
|
+
}
|
|
285
|
+
doc.addEventListener('click',tapMarkup);
|
|
286
|
+
var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
|
|
287
|
+
if (observer) observer.observe(doc.body);
|
|
288
|
+
win.addEventListener('resize',schedule);win.addEventListener('scroll',schedule); doc.addEventListener('load',schedule,true);
|
|
289
|
+
if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
|
|
290
|
+
schedule();
|
|
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();}};
|
|
292
|
+
}
|
|
293
|
+
return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
/* @papyrus-epub-annotations:end */
|
|
112
297
|
const viewer = document.getElementById('viewer');
|
|
113
298
|
const DEFAULT_FONT_SIZE = 16;
|
|
114
299
|
const TEXT_PAGE_CHUNK = 1600;
|
|
@@ -116,6 +301,12 @@
|
|
|
116
301
|
let currentType = null;
|
|
117
302
|
let book = null;
|
|
118
303
|
let rendition = null;
|
|
304
|
+
let epubResizeAnchor = null;
|
|
305
|
+
let documentSessionId = null;
|
|
306
|
+
let annotationDocumentId;
|
|
307
|
+
let annotationLabels = {};
|
|
308
|
+
let epubAnnotations = [];
|
|
309
|
+
const epubAnnotationRenderers = new Map();
|
|
119
310
|
let spineItems = [];
|
|
120
311
|
let textPages = [];
|
|
121
312
|
let textContainer = null;
|
|
@@ -523,6 +714,9 @@
|
|
|
523
714
|
};
|
|
524
715
|
|
|
525
716
|
const clearViewer = () => {
|
|
717
|
+
if(epubResizeAnchor){epubResizeAnchor.destroy();epubResizeAnchor=null;}
|
|
718
|
+
epubAnnotationRenderers.forEach(renderer => renderer.destroy());
|
|
719
|
+
epubAnnotationRenderers.clear();
|
|
526
720
|
if (epubInteractionCleanup) {
|
|
527
721
|
epubInteractionCleanup();
|
|
528
722
|
epubInteractionCleanup = null;
|
|
@@ -1177,21 +1371,65 @@
|
|
|
1177
1371
|
pageCount,
|
|
1178
1372
|
});
|
|
1179
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();
|
|
1180
1391
|
clearViewer();
|
|
1181
|
-
rendition = book.renderTo(viewer,
|
|
1182
|
-
manager: 'continuous',
|
|
1183
|
-
width: '100%',
|
|
1184
|
-
height: '100%',
|
|
1185
|
-
flow: 'scrolled-continuous',
|
|
1186
|
-
spread: 'none',
|
|
1187
|
-
});
|
|
1392
|
+
rendition = book.renderTo(viewer, window.PapyrusEpubReader.options(epubViewMode === 'single' ? 'paged' : 'chapter-scroll'));
|
|
1188
1393
|
sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
|
|
1189
1394
|
|
|
1190
1395
|
if (rendition && rendition.hooks && rendition.hooks.content) {
|
|
1396
|
+
const session = documentSessionId;
|
|
1397
|
+
const activeRendition = rendition;
|
|
1191
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;
|
|
1419
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
1420
|
+
if (href && window.PapyrusEpubAnnotations) {
|
|
1421
|
+
const renderer = window.PapyrusEpubAnnotations.install(contents, {
|
|
1422
|
+
href, get documentId() { return annotationDocumentId; },
|
|
1423
|
+
isCurrent: () => session === documentSessionId && activeRendition === rendition,
|
|
1424
|
+
pageWidth: () => activeRendition.layout?.width || viewer.clientWidth,
|
|
1425
|
+
annotations: () => epubAnnotations, labels: () => annotationLabels,
|
|
1426
|
+
onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
|
|
1427
|
+
});
|
|
1428
|
+
epubAnnotationRenderers.set(contents,renderer);
|
|
1429
|
+
}
|
|
1192
1430
|
const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
|
|
1193
1431
|
if (frame) {
|
|
1194
|
-
frame.setAttribute('sandbox', 'allow-
|
|
1432
|
+
frame.setAttribute('sandbox', 'allow-same-origin');
|
|
1195
1433
|
}
|
|
1196
1434
|
const selectionDocument = contents && contents.document;
|
|
1197
1435
|
const selectionWindow = contents && contents.window;
|
|
@@ -1201,13 +1439,42 @@
|
|
|
1201
1439
|
selectionWindow &&
|
|
1202
1440
|
typeof selectionWindow.getSelection === 'function'
|
|
1203
1441
|
) {
|
|
1442
|
+
let selectionTimer = null;
|
|
1443
|
+
|
|
1444
|
+
const reportSelection = () => {
|
|
1445
|
+
selectionTimer = null;
|
|
1446
|
+
if (session !== documentSessionId || activeRendition !== rendition) return;
|
|
1447
|
+
const selected = selectionWindow.getSelection();
|
|
1448
|
+
if (!selected || selected.isCollapsed || selected.rangeCount === 0 ||
|
|
1449
|
+
typeof contents.cfiFromRange !== 'function') return;
|
|
1450
|
+
const range = selected.getRangeAt(0).cloneRange();
|
|
1451
|
+
if (!range.toString().trim()) return;
|
|
1452
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
1453
|
+
if (!href) return;
|
|
1454
|
+
let cfi;
|
|
1455
|
+
try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
|
|
1456
|
+
const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
|
|
1457
|
+
if (!anchor) return;
|
|
1458
|
+
if (nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
|
|
1459
|
+
const payload = {documentSessionId: session, text: anchor.quote,
|
|
1460
|
+
pageIndex: contents.sectionIndex + 1, anchor};
|
|
1461
|
+
nativeMenuSelection = {contents, payload};
|
|
1462
|
+
sendEvent('EPUB_TEXT_SELECTED', payload);
|
|
1463
|
+
};
|
|
1204
1464
|
const handleSelectionChange = () => {
|
|
1205
1465
|
const selection = selectionWindow.getSelection();
|
|
1206
1466
|
const text = selection ? selection.toString().trim() : '';
|
|
1207
1467
|
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(Boolean(text));
|
|
1468
|
+
if (selectionTimer !== null) clearTimeout(selectionTimer);
|
|
1469
|
+
// epub.js "selected" is not reliable on every iOS WKWebView path.
|
|
1470
|
+
// The content document still emits selectionchange, so resolve its
|
|
1471
|
+
// live Range to a CFI before the native menu action can fire.
|
|
1472
|
+
if (text) reportSelection();
|
|
1473
|
+
else if (nativeMenuSelection?.contents === contents) nativeMenuSelection = null;
|
|
1208
1474
|
};
|
|
1209
1475
|
selectionDocument.addEventListener('selectionchange', handleSelectionChange);
|
|
1210
1476
|
const selectionCleanup = () => {
|
|
1477
|
+
if (selectionTimer !== null) clearTimeout(selectionTimer);
|
|
1211
1478
|
selectionDocument.removeEventListener('selectionchange', handleSelectionChange);
|
|
1212
1479
|
epubSelectionCleanups.delete(selectionCleanup);
|
|
1213
1480
|
};
|
|
@@ -1226,29 +1493,53 @@
|
|
|
1226
1493
|
typeof rendition.hooks.unloaded.register === 'function'
|
|
1227
1494
|
) {
|
|
1228
1495
|
rendition.hooks.unloaded.register((view) => {
|
|
1496
|
+
view?.contents?.__papyrusGestureCleanup?.();
|
|
1229
1497
|
const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
|
|
1230
1498
|
if (typeof cleanup === 'function') cleanup();
|
|
1499
|
+
const renderer = epubAnnotationRenderers.get(view?.contents);
|
|
1500
|
+
if (renderer) renderer.destroy();
|
|
1501
|
+
epubAnnotationRenderers.delete(view?.contents);
|
|
1231
1502
|
});
|
|
1232
1503
|
}
|
|
1233
1504
|
|
|
1234
1505
|
if (rendition && typeof rendition.on === 'function') {
|
|
1506
|
+
const selectionSession = documentSessionId;
|
|
1507
|
+
const selectionRendition = rendition;
|
|
1235
1508
|
rendition.on('selected', (cfiRange, contents) => {
|
|
1236
|
-
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
if (
|
|
1240
|
-
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
}
|
|
1509
|
+
if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
|
|
1510
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
1511
|
+
const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
|
|
1512
|
+
if (!anchor || nativeMenuSelection?.payload?.anchor?.cfiRange === anchor.cfiRange && nativeMenuSelection?.payload?.anchor?.quote === anchor.quote) return;
|
|
1513
|
+
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
|
|
1514
|
+
const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
|
|
1515
|
+
nativeMenuSelection = {contents, payload};
|
|
1516
|
+
sendEvent('EPUB_TEXT_SELECTED', payload);
|
|
1245
1517
|
});
|
|
1518
|
+
['rendered','resized','layout','relocated'].forEach(name => rendition.on(name, () => {
|
|
1519
|
+
if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
1520
|
+
}));
|
|
1246
1521
|
rendition.on('relocated', (location) => {
|
|
1522
|
+
if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
|
|
1247
1523
|
const start = location && location.start;
|
|
1248
1524
|
let index = start && Number.isInteger(start.index) ? start.index : -1;
|
|
1249
1525
|
if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
|
|
1250
1526
|
if (index < 0 || index >= spineItems.length) return;
|
|
1251
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
|
+
}
|
|
1252
1543
|
const nextPage = index + 1;
|
|
1253
1544
|
if (nextPage === currentPage) return;
|
|
1254
1545
|
currentPage = nextPage;
|
|
@@ -1256,21 +1547,23 @@
|
|
|
1256
1547
|
});
|
|
1257
1548
|
}
|
|
1258
1549
|
|
|
1550
|
+
applyEpubZoom();
|
|
1259
1551
|
sendEpubDiagnostic('epub.display.start', diagnosticPayload);
|
|
1260
1552
|
await withEpubStageTimeout(
|
|
1261
|
-
|
|
1553
|
+
rendition.display(target || spineItems[0]?.href),
|
|
1262
1554
|
'rendition.display',
|
|
1263
1555
|
diagnosticPayload,
|
|
1264
1556
|
);
|
|
1265
1557
|
if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
|
|
1266
1558
|
sendEpubDiagnostic('epub.display.end', diagnosticPayload);
|
|
1559
|
+
const resizeRendition=rendition;
|
|
1560
|
+
epubResizeAnchor=window.PapyrusEpubReader.installResizeAnchor(resizeRendition.manager,()=>resizeRendition.location?.start?.cfi);
|
|
1267
1561
|
if (
|
|
1268
1562
|
rendition &&
|
|
1269
1563
|
rendition.manager &&
|
|
1270
1564
|
typeof rendition.manager.on === 'function' &&
|
|
1271
1565
|
typeof rendition.on === 'function'
|
|
1272
1566
|
) {
|
|
1273
|
-
let touchStart = null;
|
|
1274
1567
|
let lastScrollOffset = -1;
|
|
1275
1568
|
const manager = rendition.manager;
|
|
1276
1569
|
epubScrollDiagnostics = createEpubScrollDiagnostics(manager);
|
|
@@ -1345,47 +1638,19 @@
|
|
|
1345
1638
|
return result;
|
|
1346
1639
|
};
|
|
1347
1640
|
}
|
|
1348
|
-
const
|
|
1349
|
-
const touch =
|
|
1350
|
-
(event && event.changedTouches && event.changedTouches[0]) ||
|
|
1351
|
-
(event && event.touches && event.touches[0]);
|
|
1352
|
-
if (!touch) return null;
|
|
1353
|
-
const x = Number(touch.screenX ?? touch.clientX);
|
|
1354
|
-
const y = Number(touch.screenY ?? touch.clientY);
|
|
1355
|
-
return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
|
|
1356
|
-
};
|
|
1357
|
-
const handleScroll = (event) => {
|
|
1641
|
+
const handleScroll = event => {
|
|
1358
1642
|
const offsetY = Math.max(0, Number(event && event.top) || 0);
|
|
1359
1643
|
epubScrollDiagnostics && epubScrollDiagnostics.scroll(event);
|
|
1360
|
-
if (Math.abs(offsetY
|
|
1361
|
-
lastScrollOffset
|
|
1362
|
-
sendEvent('VIEWER_SCROLL',
|
|
1644
|
+
if (Math.abs(offsetY-lastScrollOffset)<1) return;
|
|
1645
|
+
lastScrollOffset=offsetY;
|
|
1646
|
+
sendEvent('VIEWER_SCROLL',{offsetY});
|
|
1363
1647
|
};
|
|
1364
|
-
const handleTouchStart = (event) => {
|
|
1365
|
-
touchStart = getTouchPoint(event);
|
|
1366
|
-
epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
|
|
1367
|
-
};
|
|
1368
|
-
const handleTouchMove = (event) => {
|
|
1369
|
-
epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
|
|
1370
|
-
};
|
|
1371
|
-
const handleTouchEnd = (event) => {
|
|
1372
|
-
const end = getTouchPoint(event);
|
|
1373
|
-
const start = touchStart;
|
|
1374
|
-
touchStart = null;
|
|
1375
|
-
if (start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
|
|
1376
|
-
sendEvent('VIEWER_TAP', {});
|
|
1377
|
-
}
|
|
1378
|
-
epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
|
|
1379
|
-
};
|
|
1380
|
-
|
|
1381
1648
|
manager.on('scroll', handleScroll);
|
|
1382
1649
|
const handleScrolled = (event) => {
|
|
1383
1650
|
epubScrollDiagnostics && epubScrollDiagnostics.settled(event);
|
|
1384
1651
|
};
|
|
1385
1652
|
manager.on('scrolled', handleScrolled);
|
|
1386
|
-
|
|
1387
|
-
rendition.on('touchmove', handleTouchMove);
|
|
1388
|
-
rendition.on('touchend', handleTouchEnd);
|
|
1653
|
+
|
|
1389
1654
|
epubInteractionCleanup = () => {
|
|
1390
1655
|
epubSelectionCleanups.forEach((cleanup) => cleanup());
|
|
1391
1656
|
epubSelectionCleanups.clear();
|
|
@@ -1394,16 +1659,9 @@
|
|
|
1394
1659
|
if (originalEnqueue) queue.enqueue = originalEnqueue;
|
|
1395
1660
|
if (typeof manager.off === 'function') manager.off('scroll', handleScroll);
|
|
1396
1661
|
if (typeof manager.off === 'function') manager.off('scrolled', handleScrolled);
|
|
1397
|
-
if (typeof rendition.off === 'function') {
|
|
1398
|
-
rendition.off('touchstart', handleTouchStart);
|
|
1399
|
-
rendition.off('touchmove', handleTouchMove);
|
|
1400
|
-
rendition.off('touchend', handleTouchEnd);
|
|
1401
|
-
}
|
|
1402
1662
|
epubScrollDiagnostics = null;
|
|
1403
1663
|
};
|
|
1404
1664
|
}
|
|
1405
|
-
applyEpubZoom();
|
|
1406
|
-
|
|
1407
1665
|
const outline = await withEpubStageTimeout(
|
|
1408
1666
|
buildOutline(),
|
|
1409
1667
|
'outline',
|
|
@@ -1417,6 +1675,8 @@
|
|
|
1417
1675
|
return { pageCount, outline };
|
|
1418
1676
|
};
|
|
1419
1677
|
|
|
1678
|
+
let epubNavigationQueue = Promise.resolve();
|
|
1679
|
+
let epubNavigationRequest = 0;
|
|
1420
1680
|
const displayEpubPage = async (pageIndex) => {
|
|
1421
1681
|
if (!rendition) return;
|
|
1422
1682
|
const item = spineItems[pageIndex];
|
|
@@ -1426,9 +1686,19 @@
|
|
|
1426
1686
|
target,
|
|
1427
1687
|
pageIndex,
|
|
1428
1688
|
});
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
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;
|
|
1432
1702
|
};
|
|
1433
1703
|
|
|
1434
1704
|
const getTextContent = async (pageIndex) => {
|
|
@@ -1529,6 +1799,10 @@
|
|
|
1529
1799
|
}
|
|
1530
1800
|
};
|
|
1531
1801
|
|
|
1802
|
+
let nativeMenuSelection = null;
|
|
1803
|
+
let epubViewMode = "single";
|
|
1804
|
+
let epubLocation = null;
|
|
1805
|
+
|
|
1532
1806
|
const handleCommand = async (message) => {
|
|
1533
1807
|
const { id, kind, payload } = message;
|
|
1534
1808
|
|
|
@@ -1536,7 +1810,15 @@
|
|
|
1536
1810
|
if (kind === 'load') {
|
|
1537
1811
|
const generation = (epubLoadGeneration += 1);
|
|
1538
1812
|
currentType = payload.type;
|
|
1813
|
+
documentSessionId = payload.documentSessionId || null;
|
|
1814
|
+
epubAnnotations = [];
|
|
1815
|
+
annotationDocumentId = undefined;
|
|
1539
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();
|
|
1540
1822
|
|
|
1541
1823
|
if (currentType === 'text') {
|
|
1542
1824
|
const result = await loadText(
|
|
@@ -1576,6 +1858,43 @@
|
|
|
1576
1858
|
throw new Error('Unsupported document type');
|
|
1577
1859
|
}
|
|
1578
1860
|
|
|
1861
|
+
if (kind === 'epub-annotations' || kind === 'epub-annotation-navigate') {
|
|
1862
|
+
if (currentType !== 'epub' || !documentSessionId || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB annotation session');
|
|
1863
|
+
if (kind === 'epub-annotations') {
|
|
1864
|
+
const incoming = payload.annotations;
|
|
1865
|
+
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');
|
|
1866
|
+
const ids = new Set();
|
|
1867
|
+
epubAnnotations = incoming.filter(a => {
|
|
1868
|
+
if (ids.has(a.id) || !window.PapyrusEpubAnnotations.validAnchor(a.anchor)) return false;
|
|
1869
|
+
ids.add(a.id); return true;
|
|
1870
|
+
});
|
|
1871
|
+
annotationDocumentId = typeof payload.documentId === 'string' ? payload.documentId : undefined;
|
|
1872
|
+
annotationLabels = {comment: typeof payload.labels?.comment === 'string' ? payload.labels.comment.slice(0,128) : ''};
|
|
1873
|
+
epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
1874
|
+
} else {
|
|
1875
|
+
const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
|
|
1876
|
+
if (!annotation || (annotation.anchor.documentId && annotation.anchor.documentId !== annotationDocumentId)) throw new Error('Annotation not available for this document');
|
|
1877
|
+
// Navigation uses the CFI, never a synthetic percentage/page rectangle.
|
|
1878
|
+
const navigationSession = documentSessionId;
|
|
1879
|
+
const activeRendition = rendition;
|
|
1880
|
+
// Open the confirmed chapter first, resolve its quote, then navigate its actual range.
|
|
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;
|
|
1894
|
+
}
|
|
1895
|
+
sendResponse(id,true,null); return;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1579
1898
|
if (kind === 'go-to-page') {
|
|
1580
1899
|
const page = Math.max(1, payload.page || 1);
|
|
1581
1900
|
if (currentType === 'text') {
|
|
@@ -1598,12 +1917,55 @@
|
|
|
1598
1917
|
return;
|
|
1599
1918
|
}
|
|
1600
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
|
+
|
|
1601
1953
|
if (kind === 'set-zoom') {
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
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;
|
|
1607
1969
|
}
|
|
1608
1970
|
|
|
1609
1971
|
if (kind === 'set-rotation') {
|
|
@@ -1745,6 +2107,26 @@
|
|
|
1745
2107
|
return;
|
|
1746
2108
|
}
|
|
1747
2109
|
|
|
2110
|
+
if (message.type === 'epub-selection-action') {
|
|
2111
|
+
const selected = nativeMenuSelection;
|
|
2112
|
+
if (!selected || message.documentSessionId !== documentSessionId ||
|
|
2113
|
+
selected.payload.documentSessionId !== documentSessionId) return;
|
|
2114
|
+
if (message.key === 'selectAll') {
|
|
2115
|
+
const doc = selected.contents.document;
|
|
2116
|
+
const range = doc.createRange();
|
|
2117
|
+
range.selectNodeContents(doc.body);
|
|
2118
|
+
const selection = selected.contents.window.getSelection();
|
|
2119
|
+
selection.removeAllRanges();
|
|
2120
|
+
selection.addRange(range);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
if (!['copy','define','highlight','underline','strikeout','comment'].includes(message.key)) return;
|
|
2124
|
+
sendEvent('EPUB_SELECTION_ACTION', {...selected.payload, action: message.key});
|
|
2125
|
+
selected.contents.window.getSelection()?.removeAllRanges();
|
|
2126
|
+
nativeMenuSelection = null;
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
1748
2130
|
if (!message.kind) return;
|
|
1749
2131
|
handleCommand(message);
|
|
1750
2132
|
};
|