@readium/navigator-html-injectables 2.6.3 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/helpers/locator.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getCssSelector as
|
|
1
|
+
import{getDomRange as u,getTextFragment as g,getCssSelector as a}from"@readium/shared";import{processTextFragmentDirective as d}from"@readium/helpers";import{TextQuoteAnchor as m}from"../vendor/hypothesis/anchoring/types.js";function h(n){const r=n.tagName.toUpperCase();return r==="IMG"||r==="VIDEO"||r==="AUDIO"||r==="IFRAME"||r==="OBJECT"||r==="EMBED"||r==="CANVAS"}function f(n,r){const o=r&&a(r);let l=null;if(o)try{l=n.querySelector(o)}catch(e){console.warn(`Invalid cssSelector: ${o}`,e)}return l??n.body??n.documentElement}function i(n,r){let o;try{o=n.querySelector(r.cssSelector)}catch(s){return console.error(`Invalid domRange cssSelector: ${r.cssSelector}`,s),null}if(!o)return console.error(`Can't resolve domRange cssSelector: ${r.cssSelector}`),null;const l=n.createTreeWalker(o,NodeFilter.SHOW_TEXT);let e=0,t;for(;t=l.nextNode();){if(e===r.textNodeIndex)return{node:t,offset:r.charOffset};e++}return console.error(`Can't resolve domRange textNodeIndex ${r.textNodeIndex} for selector: ${r.cssSelector}`),null}function E(n,r){try{const o=r.locations,l=r.text;if(o){const e=u(o);if(e){const t=i(n,e.start),s=e.end?i(n,e.end):t;if(t&&s)try{const c=n.createRange();return t.offset!==void 0?c.setStart(t.node,t.offset):c.setStartBefore(t.node),s.offset!==void 0?c.setEnd(s.node,s.offset):c.setEndBefore(s.node),c}catch(c){console.warn("Invalid domRange, falling back:",c)}}}if(o){const e=g(o);if(e){const t=f(n,o),s=d(e,n,t);if(s.length>0)return s[0]}}if(l&&l.highlight){const e=f(n,o),t=new m(e,l.highlight,{prefix:l.before,suffix:l.after});try{return t.toRange()}catch{return console.warn("Quote not found:",t),null}}if(o){let e=null;if(!e&&a(o)&&(e=n.querySelector(a(o))),!e&&o.fragments){for(const t of o.fragments)if(e=n.getElementById(t),e)break}if(e){const t=n.createRange();return e.childNodes.length===0||h(e)?(t.selectNode(e),t):(t.setStartBefore(e),t.setEndAfter(e),t)}}}catch(o){console.error(o)}return null}export{E as rangeFromLocator};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Module as l}from"../Module.js";const i=class i extends l{constructor(){super(...arguments),this.mediaPlayingCount=0,this.allAnimations=new Set}wndOnErr(e){this.comms?.send("error",{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}unblock(e){for(e._readium_blockEvents=!1;e._readium_blockedEvents?.length>0;){const t=e._readium_blockedEvents.shift();switch(t[0]){case 0:Reflect.apply(t[1],t[2],t[3]);break;case 1:const a=t[1],n=t[2];e.removeEventListener(a.type,e._readium_eventBlocker,!0);const s=new Event(a.type,{bubbles:a.bubbles,cancelable:a.cancelable});n?n.dispatchEvent(s):e.dispatchEvent(s);break}}}onMediaPlayEvent(){this.mediaPlayingCount++,this.comms?.send("media_play",this.mediaPlayingCount)}onMediaPauseEvent(){this.mediaPlayingCount>0&&this.mediaPlayingCount--,this.comms?.send("media_pause",this.mediaPlayingCount)}pauseAllMedia(e){const t=e.document.querySelectorAll("audio,video");for(let a=0;a<t.length;a++)t[a].pause()}mount(e,t){this.comms=t,e.addEventListener("error",this.wndOnErr,!1),Reflect.defineProperty(e.navigator,"epubReadingSystem",{value:{name:"readium-ts-toolkit",version:"2.
|
|
1
|
+
import{Module as l}from"../Module.js";const i=class i extends l{constructor(){super(...arguments),this.mediaPlayingCount=0,this.allAnimations=new Set}wndOnErr(e){this.comms?.send("error",{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}unblock(e){for(e._readium_blockEvents=!1;e._readium_blockedEvents?.length>0;){const t=e._readium_blockedEvents.shift();switch(t[0]){case 0:Reflect.apply(t[1],t[2],t[3]);break;case 1:const a=t[1],n=t[2];e.removeEventListener(a.type,e._readium_eventBlocker,!0);const s=new Event(a.type,{bubbles:a.bubbles,cancelable:a.cancelable});n?n.dispatchEvent(s):e.dispatchEvent(s);break}}}onMediaPlayEvent(){this.mediaPlayingCount++,this.comms?.send("media_play",this.mediaPlayingCount)}onMediaPauseEvent(){this.mediaPlayingCount>0&&this.mediaPlayingCount--,this.comms?.send("media_pause",this.mediaPlayingCount)}pauseAllMedia(e){const t=e.document.querySelectorAll("audio,video");for(let a=0;a<t.length;a++)t[a].pause()}mount(e,t){this.comms=t,e.addEventListener("error",this.wndOnErr,!1),Reflect.defineProperty(e.navigator,"epubReadingSystem",{value:{name:"readium-ts-toolkit",version:"2.7.0",hasFeature:(n,s="")=>{switch(n){case"dom-manipulation":return!0;case"layout-changes":return!0;case"touch-events":return!0;case"mouse-events":return!0;case"keyboard-events":return!0;case"spine-scripting":return!0;case"embedded-web-content":return!0;default:return!1}}},writable:!1}),"getAnimations"in e.document&&e.document.getAnimations().forEach(n=>{n.cancel(),this.allAnimations.add(n)}),t.register("activate",i.moduleName,(n,s)=>{this.allAnimations.forEach(r=>{r.cancel(),r.play()}),s(!0)}),t.register("unfocus",i.moduleName,(n,s)=>{this.pauseAllMedia(e),this.allAnimations.forEach(r=>r.pause()),s(!0)});const a=e.document.querySelectorAll("audio,video");for(let n=0;n<a.length;n++){const s=a[n];s.addEventListener("play",this.onMediaPlayEvent,{passive:!0}),s.addEventListener("pause",this.onMediaPauseEvent,{passive:!0})}return t.log("Setup Mounted"),!0}unmount(e,t){return e.removeEventListener("error",this.wndOnErr),e.removeEventListener("play",this.onMediaPlayEvent),e.removeEventListener("pause",this.onMediaPauseEvent),this.allAnimations.forEach(a=>a.cancel()),this.allAnimations.clear(),t.log("Setup Unmounted"),!0}};i.moduleName="setup";let o=i;export{o as Setup};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@readium/navigator-html-injectables",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "An embeddable solution for connecting frames of HTML publications with a Readium Navigator",
|
|
6
6
|
"author": "readium",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"node": ">=18"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@readium/helpers": "1.
|
|
48
|
-
"@readium/shared": "2.
|
|
47
|
+
"@readium/helpers": "1.1.0",
|
|
48
|
+
"@readium/shared": "2.5.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"css-selector-generator": "^3.8.0",
|
package/src/helpers/locator.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getCssSelector, Locator } from "@readium/shared";
|
|
1
|
+
import { DomRangePoint, getCssSelector, getDomRange, getTextFragment, Locator, LocatorLocations } from "@readium/shared";
|
|
2
|
+
import { processTextFragmentDirective } from "@readium/helpers";
|
|
2
3
|
import { TextQuoteAnchor } from "../vendor/hypothesis/anchoring/types.ts";
|
|
3
4
|
|
|
4
5
|
function isReplacedLikeElement(element: Element): boolean {
|
|
@@ -6,19 +7,104 @@ function isReplacedLikeElement(element: Element): boolean {
|
|
|
6
7
|
return tagName === "IMG" || tagName === "VIDEO" || tagName === "AUDIO" || tagName === "IFRAME" || tagName === "OBJECT" || tagName === "EMBED" || tagName === "CANVAS";
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
// Root used for text-based searches: the cssSelector-referenced element when
|
|
11
|
+
// present and resolvable, otherwise the whole document body.
|
|
12
|
+
function resolveTextSearchRoot(doc: Document, locations: LocatorLocations | undefined): Element {
|
|
13
|
+
const cssSelector = locations && getCssSelector(locations);
|
|
14
|
+
let root: Element | null = null;
|
|
15
|
+
if (cssSelector) {
|
|
16
|
+
try {
|
|
17
|
+
root = doc.querySelector(cssSelector);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.warn(`Invalid cssSelector: ${cssSelector}`, error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return root ?? doc.body ?? doc.documentElement;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveDomRangePoint(doc: Document, point: DomRangePoint): { node: Text; offset?: number } | null {
|
|
26
|
+
let container: Element | null;
|
|
27
|
+
try {
|
|
28
|
+
container = doc.querySelector(point.cssSelector);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(`Invalid domRange cssSelector: ${point.cssSelector}`, error);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (!container) {
|
|
34
|
+
console.error(`Can't resolve domRange cssSelector: ${point.cssSelector}`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const walker = doc.createTreeWalker(container, NodeFilter.SHOW_TEXT);
|
|
39
|
+
let index = 0;
|
|
40
|
+
let node: Node | null;
|
|
41
|
+
while ((node = walker.nextNode())) {
|
|
42
|
+
if (index === point.textNodeIndex) {
|
|
43
|
+
return { node: node as Text, offset: point.charOffset };
|
|
44
|
+
}
|
|
45
|
+
index++;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.error(`Can't resolve domRange textNodeIndex ${point.textNodeIndex} for selector: ${point.cssSelector}`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
9
52
|
// Based on the kotlin-toolkit code
|
|
10
53
|
export function rangeFromLocator(doc: Document, locator: Locator) {
|
|
11
54
|
try {
|
|
12
55
|
const locations = locator.locations;
|
|
13
56
|
const text = locator.text;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
57
|
+
|
|
58
|
+
// Tried before text.highlight: a domRange pinpoints an exact node/offset,
|
|
59
|
+
// which disambiguates between multiple occurrences of the same quote that
|
|
60
|
+
// a text-based search alone cannot tell apart.
|
|
61
|
+
if (locations) {
|
|
62
|
+
const domRange = getDomRange(locations);
|
|
63
|
+
if (domRange) {
|
|
64
|
+
const start = resolveDomRangePoint(doc, domRange.start);
|
|
65
|
+
const end = domRange.end ? resolveDomRangePoint(doc, domRange.end) : start;
|
|
66
|
+
if (start && end) {
|
|
67
|
+
try {
|
|
68
|
+
const range = doc.createRange();
|
|
69
|
+
|
|
70
|
+
if (start.offset !== undefined) {
|
|
71
|
+
range.setStart(start.node, start.offset);
|
|
72
|
+
} else {
|
|
73
|
+
range.setStartBefore(start.node);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (end.offset !== undefined) {
|
|
77
|
+
range.setEnd(end.node, end.offset);
|
|
78
|
+
} else {
|
|
79
|
+
range.setEndBefore(end.node);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return range;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.warn("Invalid domRange, falling back:", error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
18
87
|
}
|
|
19
|
-
|
|
20
|
-
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Tried before text.highlight for the same reason as domRange: a
|
|
91
|
+
// ":~:text=" fragment is a strict superset of what text.highlight can
|
|
92
|
+
// express (it also supports a textStart...textEnd range), so checking
|
|
93
|
+
// text.highlight first would mean this branch is rarely reached.
|
|
94
|
+
if (locations) {
|
|
95
|
+
const fragmentDirective = getTextFragment(locations);
|
|
96
|
+
|
|
97
|
+
if (fragmentDirective) {
|
|
98
|
+
const root = resolveTextSearchRoot(doc, locations);
|
|
99
|
+
const results = processTextFragmentDirective(fragmentDirective, doc, root);
|
|
100
|
+
if (results.length > 0) {
|
|
101
|
+
return results[0]!;
|
|
102
|
+
}
|
|
21
103
|
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (text && text.highlight) {
|
|
107
|
+
const root = resolveTextSearchRoot(doc, locations);
|
|
22
108
|
|
|
23
109
|
const anchor = new TextQuoteAnchor(root, text.highlight, {
|
|
24
110
|
prefix: text.before,
|