@papyrus-sdk/ui-react-native 0.2.35-beta.7 → 0.2.35-beta.9
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/README.md +4 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +1820 -1472
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1614 -1273
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -12
- package/runtime/epubAnnotations.js +118 -0
- package/runtime/epubAnnotations.test.ts +31 -0
- package/runtime/epubSelectionMenu.test.ts +39 -0
- package/runtime/index.html +248 -11
- package/runtime/runtime.js +248 -11
- package/runtime/syncComicRuntime.mjs +2 -1
package/runtime/runtime.js
CHANGED
|
@@ -109,6 +109,127 @@
|
|
|
109
109
|
/* @papyrus-comic-runtime:end */
|
|
110
110
|
|
|
111
111
|
(function () {
|
|
112
|
+
/* @papyrus-epub-annotations:start */
|
|
113
|
+
(function (root, factory) {
|
|
114
|
+
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
115
|
+
else root.PapyrusEpubAnnotations = factory();
|
|
116
|
+
})(typeof window !== 'undefined' ? window : globalThis, function () {
|
|
117
|
+
function validAnchor(anchor) {
|
|
118
|
+
return anchor && anchor.version === 1 && anchor.kind === 'epub-cfi' &&
|
|
119
|
+
typeof anchor.cfiRange === 'string' && anchor.cfiRange.length <= 4096 && /^epubcfi\([^\n<>]+\)$/.test(anchor.cfiRange) &&
|
|
120
|
+
typeof anchor.href === 'string' && !/^(javascript|data):/i.test(anchor.href) &&
|
|
121
|
+
typeof anchor.quote === 'string' && anchor.quote.length > 0 && anchor.quote.length <= 100000;
|
|
122
|
+
}
|
|
123
|
+
function textNodes(document) {
|
|
124
|
+
var walker = document.createTreeWalker(document.body, 4);
|
|
125
|
+
var nodes = [], text = '', node;
|
|
126
|
+
while ((node = walker.nextNode())) {
|
|
127
|
+
if (node.parentElement.closest('script,style,[data-papyrus-overlay]')) continue;
|
|
128
|
+
nodes.push({node:node, start:text.length}); text += node.textContent;
|
|
129
|
+
}
|
|
130
|
+
return {nodes:nodes,text:text};
|
|
131
|
+
}
|
|
132
|
+
function recoverRange(document, anchor) {
|
|
133
|
+
var source = textNodes(document), match = null, start = source.text.indexOf(anchor.quote);
|
|
134
|
+
while (start >= 0) {
|
|
135
|
+
var end = start + anchor.quote.length;
|
|
136
|
+
if ((!anchor.prefix || source.text.slice(Math.max(0,start-anchor.prefix.length),start) === anchor.prefix) &&
|
|
137
|
+
(!anchor.suffix || source.text.slice(end,end+anchor.suffix.length) === anchor.suffix)) {
|
|
138
|
+
if (match) return null; match = {start:start,end:end};
|
|
139
|
+
}
|
|
140
|
+
start = source.text.indexOf(anchor.quote,start+1);
|
|
141
|
+
}
|
|
142
|
+
if (!match) return null;
|
|
143
|
+
var range = document.createRange();
|
|
144
|
+
var first = source.nodes.find(function(n){return n.start+n.node.length > match.start;});
|
|
145
|
+
var last = source.nodes.find(function(n){return n.start+n.node.length >= match.end;});
|
|
146
|
+
if (!first || !last) return null;
|
|
147
|
+
range.setStart(first.node,match.start-first.start); range.setEnd(last.node,match.end-last.start);
|
|
148
|
+
return range;
|
|
149
|
+
}
|
|
150
|
+
function resolveRange(contents, anchor) {
|
|
151
|
+
if (!validAnchor(anchor)) return null;
|
|
152
|
+
try { var range = contents.range(anchor.cfiRange); if (range && range.toString() === anchor.quote) return range; } catch (_) {}
|
|
153
|
+
return recoverRange(contents.document,anchor);
|
|
154
|
+
}
|
|
155
|
+
function selection(contents, cfiRange, href, documentId) {
|
|
156
|
+
var range;
|
|
157
|
+
try { range = contents.range(cfiRange); } catch (_) { return null; }
|
|
158
|
+
if (!range || !range.toString()) return null;
|
|
159
|
+
var before = contents.document.createRange(); before.selectNodeContents(contents.document.body); before.setEnd(range.startContainer,range.startOffset);
|
|
160
|
+
var after = contents.document.createRange(); after.selectNodeContents(contents.document.body); after.setStart(range.endContainer,range.endOffset);
|
|
161
|
+
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)};
|
|
162
|
+
return validAnchor(anchor) ? anchor : null;
|
|
163
|
+
}
|
|
164
|
+
function install(contents, options) {
|
|
165
|
+
var doc = contents.document, win = contents.window, alive = true, frame = 0;
|
|
166
|
+
var overlay = doc.createElement('div'); overlay.dataset.papyrusOverlay = 'annotations';
|
|
167
|
+
overlay.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:0;pointer-events:none;z-index:3';
|
|
168
|
+
var originalPosition=doc.body.style.position;
|
|
169
|
+
if(win.getComputedStyle(doc.body).position==='static')doc.body.style.position='relative';
|
|
170
|
+
doc.body.appendChild(overlay);
|
|
171
|
+
var registry = new Map(), hitRegions = [];
|
|
172
|
+
function render() {
|
|
173
|
+
frame = 0; if (!alive || !options.isCurrent()) return;
|
|
174
|
+
var keep = new Set(), noteGroups = new Map();
|
|
175
|
+
hitRegions = [];
|
|
176
|
+
options.annotations().forEach(function(annotation) {
|
|
177
|
+
var anchor = annotation.anchor;
|
|
178
|
+
if (!validAnchor(anchor) || anchor.href !== options.href || anchor.documentId && options.documentId && anchor.documentId !== options.documentId) return;
|
|
179
|
+
var range = resolveRange(contents,anchor); if (!range) return;
|
|
180
|
+
var rects = Array.from(range.getClientRects()).filter(function(r){return r.width>0 && r.height>0;});
|
|
181
|
+
if (!rects.length) return;
|
|
182
|
+
keep.add(annotation.id);
|
|
183
|
+
var group = registry.get(annotation.id);
|
|
184
|
+
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'}); } }
|
|
185
|
+
group.replaceChildren();
|
|
186
|
+
hitRegions.push({id:annotation.id,rects:rects});
|
|
187
|
+
var color = /^#[0-9a-f]{6}$/i.test(annotation.color) ? annotation.color : '#FFC928';
|
|
188
|
+
var style = annotation.markupStyle || annotation.type;
|
|
189
|
+
var opacity = Number.isFinite(annotation.opacity) ? Math.max(0,Math.min(1,annotation.opacity)) : 0.35;
|
|
190
|
+
rects.forEach(function(rect) {
|
|
191
|
+
var mark = doc.createElement('div');
|
|
192
|
+
var bodyRect=doc.body.getBoundingClientRect();
|
|
193
|
+
var height = style === 'highlight' ? rect.height : 2;
|
|
194
|
+
var y = style === 'strikeout' ? rect.top+rect.height/2 : style === 'underline' ? rect.bottom-2 : rect.top;
|
|
195
|
+
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;
|
|
196
|
+
if (['highlight','underline','strikeout'].includes(style)) group.appendChild(mark);
|
|
197
|
+
});
|
|
198
|
+
var hasNote = annotation.noteContent !== undefined || annotation.type === 'comment' || annotation.type === 'text';
|
|
199
|
+
if (hasNote) {
|
|
200
|
+
var last = rects[rects.length-1], button = doc.createElement('button');
|
|
201
|
+
button.textContent = '●'; button.setAttribute('aria-label',options.labels().comment || '');
|
|
202
|
+
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';
|
|
203
|
+
var key = Math.floor(last.top/44);
|
|
204
|
+
var neighbors=noteGroups.get(key);
|
|
205
|
+
if(!neighbors){neighbors={button:button,ids:[]};noteGroups.set(key,neighbors);group.appendChild(button);}
|
|
206
|
+
neighbors.ids.push(annotation.id);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
noteGroups.forEach(function(group){
|
|
210
|
+
group.button.textContent=group.ids.length>1?String(group.ids.length):'●';
|
|
211
|
+
group.button.addEventListener('click',function(e){e.stopPropagation();if(options.isCurrent())options.onTap(group.ids[0],group.ids);});
|
|
212
|
+
});
|
|
213
|
+
registry.forEach(function(group,id){if (!keep.has(id)) {group.remove();registry.delete(id);}});
|
|
214
|
+
}
|
|
215
|
+
function schedule() { if (alive && !frame) frame = win.requestAnimationFrame(render); }
|
|
216
|
+
function tapMarkup(event) {
|
|
217
|
+
if(!alive || !options.isCurrent() || String(win.getSelection() || '').length || event.target.closest('[data-papyrus-overlay]'))return;
|
|
218
|
+
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;});});
|
|
219
|
+
if(hit) {event.stopPropagation();options.onTap(hit.id);}
|
|
220
|
+
}
|
|
221
|
+
doc.addEventListener('click',tapMarkup);
|
|
222
|
+
var observer = typeof win.ResizeObserver === 'function' ? new win.ResizeObserver(schedule) : null;
|
|
223
|
+
if (observer) observer.observe(doc.body);
|
|
224
|
+
win.addEventListener('resize',schedule); doc.addEventListener('load',schedule,true);
|
|
225
|
+
if (doc.fonts && doc.fonts.ready) doc.fonts.ready.then(schedule);
|
|
226
|
+
schedule();
|
|
227
|
+
return {schedule:schedule, range:function(anchor){return resolveRange(contents,anchor);}, destroy:function(){alive=false;if(frame)win.cancelAnimationFrame(frame);if(observer)observer.disconnect();win.removeEventListener('resize',schedule);doc.removeEventListener('load',schedule,true);doc.removeEventListener('click',tapMarkup);overlay.remove();doc.body.style.position=originalPosition;registry.clear();}};
|
|
228
|
+
}
|
|
229
|
+
return {validAnchor:validAnchor,recoverRange:recoverRange,resolveRange:resolveRange,selection:selection,install:install};
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
/* @papyrus-epub-annotations:end */
|
|
112
233
|
const viewer = document.getElementById('viewer');
|
|
113
234
|
const DEFAULT_FONT_SIZE = 16;
|
|
114
235
|
const TEXT_PAGE_CHUNK = 1600;
|
|
@@ -116,6 +237,11 @@
|
|
|
116
237
|
let currentType = null;
|
|
117
238
|
let book = null;
|
|
118
239
|
let rendition = null;
|
|
240
|
+
let documentSessionId = null;
|
|
241
|
+
let annotationDocumentId;
|
|
242
|
+
let annotationLabels = {};
|
|
243
|
+
let epubAnnotations = [];
|
|
244
|
+
const epubAnnotationRenderers = new Map();
|
|
119
245
|
let spineItems = [];
|
|
120
246
|
let textPages = [];
|
|
121
247
|
let textContainer = null;
|
|
@@ -523,6 +649,8 @@
|
|
|
523
649
|
};
|
|
524
650
|
|
|
525
651
|
const clearViewer = () => {
|
|
652
|
+
epubAnnotationRenderers.forEach(renderer => renderer.destroy());
|
|
653
|
+
epubAnnotationRenderers.clear();
|
|
526
654
|
if (epubInteractionCleanup) {
|
|
527
655
|
epubInteractionCleanup();
|
|
528
656
|
epubInteractionCleanup = null;
|
|
@@ -1180,6 +1308,7 @@
|
|
|
1180
1308
|
clearViewer();
|
|
1181
1309
|
rendition = book.renderTo(viewer, {
|
|
1182
1310
|
manager: 'continuous',
|
|
1311
|
+
allowScriptedContent: false,
|
|
1183
1312
|
width: '100%',
|
|
1184
1313
|
height: '100%',
|
|
1185
1314
|
flow: 'scrolled-continuous',
|
|
@@ -1188,10 +1317,22 @@
|
|
|
1188
1317
|
sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
|
|
1189
1318
|
|
|
1190
1319
|
if (rendition && rendition.hooks && rendition.hooks.content) {
|
|
1320
|
+
const session = documentSessionId;
|
|
1321
|
+
const activeRendition = rendition;
|
|
1191
1322
|
rendition.hooks.content.register((contents) => {
|
|
1323
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
1324
|
+
if (href && window.PapyrusEpubAnnotations) {
|
|
1325
|
+
const renderer = window.PapyrusEpubAnnotations.install(contents, {
|
|
1326
|
+
href, get documentId() { return annotationDocumentId; },
|
|
1327
|
+
isCurrent: () => session === documentSessionId && activeRendition === rendition,
|
|
1328
|
+
annotations: () => epubAnnotations, labels: () => annotationLabels,
|
|
1329
|
+
onTap: (id, ids) => sendEvent("EPUB_ANNOTATION_TAP", {documentSessionId: session, id, ids}),
|
|
1330
|
+
});
|
|
1331
|
+
epubAnnotationRenderers.set(contents,renderer);
|
|
1332
|
+
}
|
|
1192
1333
|
const frame = contents && contents.document ? contents.document.defaultView.frameElement : null;
|
|
1193
1334
|
if (frame) {
|
|
1194
|
-
frame.setAttribute('sandbox', 'allow-
|
|
1335
|
+
frame.setAttribute('sandbox', 'allow-same-origin');
|
|
1195
1336
|
}
|
|
1196
1337
|
const selectionDocument = contents && contents.document;
|
|
1197
1338
|
const selectionWindow = contents && contents.window;
|
|
@@ -1201,13 +1342,46 @@
|
|
|
1201
1342
|
selectionWindow &&
|
|
1202
1343
|
typeof selectionWindow.getSelection === 'function'
|
|
1203
1344
|
) {
|
|
1345
|
+
let selectionTimer = null;
|
|
1346
|
+
let lastSelectionKey = '';
|
|
1347
|
+
let lastSelectionAt = 0;
|
|
1348
|
+
const reportSelection = () => {
|
|
1349
|
+
selectionTimer = null;
|
|
1350
|
+
if (session !== documentSessionId || activeRendition !== rendition) return;
|
|
1351
|
+
const selected = selectionWindow.getSelection();
|
|
1352
|
+
if (!selected || selected.isCollapsed || selected.rangeCount === 0 ||
|
|
1353
|
+
typeof contents.cfiFromRange !== 'function') return;
|
|
1354
|
+
const range = selected.getRangeAt(0).cloneRange();
|
|
1355
|
+
if (!range.toString().trim()) return;
|
|
1356
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
1357
|
+
if (!href) return;
|
|
1358
|
+
let cfi;
|
|
1359
|
+
try { cfi = contents.cfiFromRange(range); } catch (_) { return; }
|
|
1360
|
+
const anchor = window.PapyrusEpubAnnotations?.selection(contents, cfi, href, annotationDocumentId);
|
|
1361
|
+
if (!anchor) return;
|
|
1362
|
+
const key = anchor.cfiRange + ':' + anchor.quote;
|
|
1363
|
+
const now = Date.now();
|
|
1364
|
+
if (key === lastSelectionKey && now - lastSelectionAt < 750) return;
|
|
1365
|
+
lastSelectionKey = key;
|
|
1366
|
+
lastSelectionAt = now;
|
|
1367
|
+
const payload = {documentSessionId: session, text: anchor.quote,
|
|
1368
|
+
pageIndex: contents.sectionIndex + 1, anchor};
|
|
1369
|
+
nativeMenuSelection = {contents, payload};
|
|
1370
|
+
sendEvent('EPUB_TEXT_SELECTED', payload);
|
|
1371
|
+
};
|
|
1204
1372
|
const handleSelectionChange = () => {
|
|
1205
1373
|
const selection = selectionWindow.getSelection();
|
|
1206
1374
|
const text = selection ? selection.toString().trim() : '';
|
|
1207
1375
|
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(Boolean(text));
|
|
1376
|
+
if (selectionTimer !== null) clearTimeout(selectionTimer);
|
|
1377
|
+
// epub.js "selected" is not reliable on every iOS WKWebView path.
|
|
1378
|
+
// The content document still emits selectionchange, so resolve its
|
|
1379
|
+
// live Range to a CFI before the native menu action can fire.
|
|
1380
|
+
if (text) reportSelection();
|
|
1208
1381
|
};
|
|
1209
1382
|
selectionDocument.addEventListener('selectionchange', handleSelectionChange);
|
|
1210
1383
|
const selectionCleanup = () => {
|
|
1384
|
+
if (selectionTimer !== null) clearTimeout(selectionTimer);
|
|
1211
1385
|
selectionDocument.removeEventListener('selectionchange', handleSelectionChange);
|
|
1212
1386
|
epubSelectionCleanups.delete(selectionCleanup);
|
|
1213
1387
|
};
|
|
@@ -1228,21 +1402,28 @@
|
|
|
1228
1402
|
rendition.hooks.unloaded.register((view) => {
|
|
1229
1403
|
const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
|
|
1230
1404
|
if (typeof cleanup === 'function') cleanup();
|
|
1405
|
+
const renderer = epubAnnotationRenderers.get(view?.contents);
|
|
1406
|
+
if (renderer) renderer.destroy();
|
|
1407
|
+
epubAnnotationRenderers.delete(view?.contents);
|
|
1231
1408
|
});
|
|
1232
1409
|
}
|
|
1233
1410
|
|
|
1234
1411
|
if (rendition && typeof rendition.on === 'function') {
|
|
1412
|
+
const selectionSession = documentSessionId;
|
|
1413
|
+
const selectionRendition = rendition;
|
|
1235
1414
|
rendition.on('selected', (cfiRange, contents) => {
|
|
1236
|
-
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
if (
|
|
1240
|
-
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
}
|
|
1415
|
+
if (selectionSession !== documentSessionId || selectionRendition !== rendition) return;
|
|
1416
|
+
const href = spineItems[contents.sectionIndex]?.href;
|
|
1417
|
+
const anchor = href && window.PapyrusEpubAnnotations?.selection(contents,cfiRange,href,annotationDocumentId);
|
|
1418
|
+
if (!anchor) return;
|
|
1419
|
+
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(true);
|
|
1420
|
+
const payload = {documentSessionId: selectionSession, text: anchor.quote, pageIndex: contents.sectionIndex+1, anchor};
|
|
1421
|
+
nativeMenuSelection = {contents, payload};
|
|
1422
|
+
sendEvent('EPUB_TEXT_SELECTED', payload);
|
|
1245
1423
|
});
|
|
1424
|
+
['rendered','resized','layout'].forEach(name => rendition.on(name, () => {
|
|
1425
|
+
if (selectionSession === documentSessionId && selectionRendition === rendition) epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
1426
|
+
}));
|
|
1246
1427
|
rendition.on('relocated', (location) => {
|
|
1247
1428
|
const start = location && location.start;
|
|
1248
1429
|
let index = start && Number.isInteger(start.index) ? start.index : -1;
|
|
@@ -1372,7 +1553,8 @@
|
|
|
1372
1553
|
const end = getTouchPoint(event);
|
|
1373
1554
|
const start = touchStart;
|
|
1374
1555
|
touchStart = null;
|
|
1375
|
-
|
|
1556
|
+
const selectedText = event?.target?.ownerDocument?.defaultView?.getSelection?.()?.toString().trim() || '';
|
|
1557
|
+
if (!selectedText && start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
|
|
1376
1558
|
sendEvent('VIEWER_TAP', {});
|
|
1377
1559
|
}
|
|
1378
1560
|
epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
|
|
@@ -1529,6 +1711,8 @@
|
|
|
1529
1711
|
}
|
|
1530
1712
|
};
|
|
1531
1713
|
|
|
1714
|
+
let nativeMenuSelection = null;
|
|
1715
|
+
|
|
1532
1716
|
const handleCommand = async (message) => {
|
|
1533
1717
|
const { id, kind, payload } = message;
|
|
1534
1718
|
|
|
@@ -1536,6 +1720,9 @@
|
|
|
1536
1720
|
if (kind === 'load') {
|
|
1537
1721
|
const generation = (epubLoadGeneration += 1);
|
|
1538
1722
|
currentType = payload.type;
|
|
1723
|
+
documentSessionId = payload.documentSessionId || null;
|
|
1724
|
+
epubAnnotations = [];
|
|
1725
|
+
annotationDocumentId = undefined;
|
|
1539
1726
|
zoom = 1.0;
|
|
1540
1727
|
|
|
1541
1728
|
if (currentType === 'text') {
|
|
@@ -1576,6 +1763,36 @@
|
|
|
1576
1763
|
throw new Error('Unsupported document type');
|
|
1577
1764
|
}
|
|
1578
1765
|
|
|
1766
|
+
if (kind === 'epub-annotations' || kind === 'epub-annotation-navigate') {
|
|
1767
|
+
if (currentType !== 'epub' || !documentSessionId || payload.documentSessionId !== documentSessionId) throw new Error('Stale EPUB annotation session');
|
|
1768
|
+
if (kind === 'epub-annotations') {
|
|
1769
|
+
const incoming = payload.annotations;
|
|
1770
|
+
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');
|
|
1771
|
+
const ids = new Set();
|
|
1772
|
+
epubAnnotations = incoming.filter(a => {
|
|
1773
|
+
if (ids.has(a.id) || !window.PapyrusEpubAnnotations.validAnchor(a.anchor)) return false;
|
|
1774
|
+
ids.add(a.id); return true;
|
|
1775
|
+
});
|
|
1776
|
+
annotationDocumentId = typeof payload.documentId === 'string' ? payload.documentId : undefined;
|
|
1777
|
+
annotationLabels = {comment: typeof payload.labels?.comment === 'string' ? payload.labels.comment.slice(0,128) : ''};
|
|
1778
|
+
epubAnnotationRenderers.forEach(renderer => renderer.schedule());
|
|
1779
|
+
} else {
|
|
1780
|
+
const annotation = epubAnnotations.find(a => a.id === payload.annotationId);
|
|
1781
|
+
if (!annotation) throw new Error('Annotation not available');
|
|
1782
|
+
// Navigation uses the CFI, never a synthetic percentage/page rectangle.
|
|
1783
|
+
const navigationSession = documentSessionId;
|
|
1784
|
+
const activeRendition = rendition;
|
|
1785
|
+
// Open the confirmed chapter first, resolve its quote, then navigate its actual range.
|
|
1786
|
+
await activeRendition.display(annotation.anchor.href);
|
|
1787
|
+
if(navigationSession !== documentSessionId || activeRendition !== rendition)throw new Error('Stale EPUB navigation');
|
|
1788
|
+
const contents = activeRendition.getContents().find(c => spineItems[c.sectionIndex]?.href === annotation.anchor.href);
|
|
1789
|
+
const range = contents && window.PapyrusEpubAnnotations.resolveRange(contents,annotation.anchor);
|
|
1790
|
+
if(!range)throw new Error('EPUB anchor unresolved');
|
|
1791
|
+
await activeRendition.display(contents.cfiFromRange(range));
|
|
1792
|
+
}
|
|
1793
|
+
sendResponse(id,true,null); return;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1579
1796
|
if (kind === 'go-to-page') {
|
|
1580
1797
|
const page = Math.max(1, payload.page || 1);
|
|
1581
1798
|
if (currentType === 'text') {
|
|
@@ -1745,6 +1962,26 @@
|
|
|
1745
1962
|
return;
|
|
1746
1963
|
}
|
|
1747
1964
|
|
|
1965
|
+
if (message.type === 'epub-selection-action') {
|
|
1966
|
+
const selected = nativeMenuSelection;
|
|
1967
|
+
if (!selected || message.documentSessionId !== documentSessionId ||
|
|
1968
|
+
selected.payload.documentSessionId !== documentSessionId) return;
|
|
1969
|
+
if (message.key === 'selectAll') {
|
|
1970
|
+
const doc = selected.contents.document;
|
|
1971
|
+
const range = doc.createRange();
|
|
1972
|
+
range.selectNodeContents(doc.body);
|
|
1973
|
+
const selection = selected.contents.window.getSelection();
|
|
1974
|
+
selection.removeAllRanges();
|
|
1975
|
+
selection.addRange(range);
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
if (!['copy','define','highlight','underline','strikeout','comment'].includes(message.key)) return;
|
|
1979
|
+
sendEvent('EPUB_SELECTION_ACTION', {...selected.payload, action: message.key});
|
|
1980
|
+
selected.contents.window.getSelection()?.removeAllRanges();
|
|
1981
|
+
nativeMenuSelection = null;
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1748
1985
|
if (!message.kind) return;
|
|
1749
1986
|
handleCommand(message);
|
|
1750
1987
|
};
|
|
@@ -17,7 +17,8 @@ const replaceMarkedSource = (content, start, end, replacement) => {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const runtimePath = resolve(runtimeDirectory, "runtime.js");
|
|
20
|
-
const
|
|
20
|
+
const epubSource = await readFile(resolve(runtimeDirectory,"epubAnnotations.js"),"utf8");
|
|
21
|
+
const runtime = replaceMarkedSource(await readFile(runtimePath, "utf8"), "/* @papyrus-epub-annotations:start */", "/* @papyrus-epub-annotations:end */", `\n${epubSource}\n`);
|
|
21
22
|
const comicRuntimeEnd = "/* @papyrus-comic-runtime:end */";
|
|
22
23
|
const runtimeBodyStart = runtime.indexOf("\n(function () {", runtime.indexOf(comicRuntimeEnd));
|
|
23
24
|
if (runtimeBodyStart < 0) {
|