@readium/navigator 2.7.2 → 2.8.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/epub/EpubNavigator.js +1 -1
- package/dist/webpub/WebPubNavigator.js +1 -1
- package/package.json +4 -4
- package/src/Navigator.ts +1 -0
- package/src/epub/EpubNavigator.ts +53 -2
- package/src/webpub/WebPubNavigator.ts +52 -2
- package/types/src/Navigator.d.ts +1 -0
- package/types/src/epub/EpubNavigator.d.ts +3 -0
- package/types/src/webpub/WebPubNavigator.d.ts +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Layout as d,Profile as E,Link as _,ReadingProgression as g,Locator as v,LocatorText as D,getCssSelector as O,getHtmlId as A}from"@readium/shared";import{VisualNavigator as M}from"../Navigator.js";import{FramePoolManager as I}from"./frame/FramePoolManager.js";import{FXLFramePoolManager as H}from"./fxl/FXLFramePoolManager.js";import{ModuleLibrary as T,FXLModules as F,ReflowableModules as R}from"@readium/navigator-html-injectables";import{p as y}from"../_virtual/index.js";import{FrameManager as N}from"./frame/FrameManager.js";import{EpubPreferences as V}from"./preferences/EpubPreferences.js";import{EpubDefaults as W}from"./preferences/EpubDefaults.js";import{EpubPreferencesEditor as b}from"./preferences/EpubPreferencesEditor.js";import{ReadiumCSS as j}from"./css/ReadiumCSS.js";import{UserProperties as z,RSProperties as B}from"./css/Properties.js";import{getContentWidth as K}from"../helpers/dimensions.js";import{Injector as G}from"../injection/Injector.js";import{createReadiumEpubRules as X}from"../injection/epubInjectables.js";import{NavigatorProtector as J,NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT as x}from"../protection/NavigatorProtector.js";import{KeyboardPeripherals as U,NAVIGATOR_KEYBOARD_PERIPHERAL_EVENT as C}from"../peripherals/KeyboardPeripherals.js";import{getScriptMode as w}from"../helpers/scriptMode.js";import{EpubSettings as k}from"./preferences/EpubSettings.js";import{LineLengths as $}from"../helpers/lineLength.js";import{supportsDecorationStyle as q,decorationsEqual as Y,resolveDecorationForWire as P}from"@readium/decorator";const Q=p=>({frameLoaded:p.frameLoaded||(()=>{}),positionChanged:p.positionChanged||(()=>{}),timelineItemChanged:p.timelineItemChanged||(()=>{}),tap:p.tap||(()=>!1),click:p.click||(()=>!1),zoom:p.zoom||(()=>{}),miscPointer:p.miscPointer||(()=>{}),scroll:p.scroll||(()=>{}),customEvent:p.customEvent||(()=>{}),handleLocator:p.handleLocator||(()=>!1),textSelected:p.textSelected||(()=>{}),contentProtection:p.contentProtection||(()=>{}),contextMenu:p.contextMenu||(()=>{}),peripheral:p.peripheral||(()=>{})});class S extends M{constructor(e,t,i,s=[],r=void 0,o={preferences:{},defaults:{}}){super(),this._timelineAugmented=!1,this._preferencesEditor=null,this._injector=null,this._isNavigating=!1,this._navigatorProtector=null,this._keyboardPeripheralsManager=null,this._suspiciousActivityListener=null,this._keyboardPeripheralListener=null,this._decorations=new Map,this._decorationObservers=new Map,this._decorationHoveredDecorations=new Map,this._decorationActivationState=new Map,this._decorationHoverState=new Map,this._decorationActivationConsumed=!1,this.reflowViewport={readingOrder:[],progressions:new Map,positions:null},this.pub=t,this.container=e,this.listeners=Q(i),this.currentLocation=r,s.length&&(this.positions=s),this._preferences=new V(o.preferences),this._defaults=new W(o.defaults),this._settings=new k(this._preferences,this._defaults);const h=w(t.metadata),f=h==="cjk-horizontal",n=h==="cjk-vertical",c=n||h==="mongolian-vertical",l=f||n;this._css=new j({rsProperties:new B({noVerticalPagination:c||void 0}),userProperties:new z({}),lineLengths:new $({optimalChars:this._settings.optimalLineLength,minChars:this._settings.minimalLineLength,maxChars:this._settings.maximalLineLength,padding:this._settings.scroll?(this._settings.scrollPaddingLeft||0)+(this._settings.scrollPaddingRight||0):(this._settings.pageGutter||0)*2,fontFace:this._settings.fontFamily,letterSpacing:this._settings.letterSpacing,wordSpacing:this._settings.wordSpacing,isCJK:l}),container:e,constraint:this._settings.constraint,isCJKVertical:c}),this._layout=S.determineLayout(t,!!this._settings.scroll),this.currentProgression=t.metadata.effectiveReadingProgression,this._injectablesConfig=o.injectables||{rules:[],allowedDomains:[]},this._readiumRulesPromise=X(t.metadata,t.readingOrder.items),this._contentProtection=o.contentProtection||{},this._decoratorConfig=o.decoratorConfig||{},this._keyboardPeripherals=this.mergeKeyboardPeripherals(this._contentProtection,o.keyboardPeripherals||[]),(this._contentProtection.disableContextMenu||this._contentProtection.checkAutomation||this._contentProtection.checkIFrameEmbedding||this._contentProtection.monitorDevTools||this._contentProtection.protectPrinting?.disable)&&(this._navigatorProtector=new J(this._contentProtection),this._suspiciousActivityListener=u=>{const{type:m,...L}=u.detail;m==="context_menu"?this.listeners.contextMenu(L):this.listeners.contentProtection(m,L)},window.addEventListener(x,this._suspiciousActivityListener)),this._keyboardPeripherals.length>0&&(this._keyboardPeripheralsManager=new U({keyboardPeripherals:this._keyboardPeripherals}),this._keyboardPeripheralListener=u=>{const m=u.detail;this.listeners.peripheral(m)},window.addEventListener(C,this._keyboardPeripheralListener)),this.resizeObserver=new ResizeObserver(()=>this.ownerWindow.requestAnimationFrame(async()=>await this.resizeHandler())),this.resizeObserver.observe(this.container.parentElement||document.documentElement)}static determineLayout(e,t){const i=e.metadata.effectiveLayout;if(i===d.fixed)return d.fixed;if(e.metadata.otherMetadata&&"http://openmangaformat.org/schema/1.0#version"in e.metadata.otherMetadata)return d.fixed;if(e.metadata?.conformsTo?.includes(E.DIVINA))return d.fixed;if(i===d.scrolled)return d.scrolled;const s=w(e.metadata);return s==="cjk-vertical"||s==="mongolian-vertical"?d.scrolled:i===d.reflowable&&t?d.scrolled:d.reflowable}async load(){if(this.positions?.length||(this.positions=await this.pub.positionsFromManifest()),!this._injector){const e=await this._readiumRulesPromise;this._injector=new G({rules:[...e,...this._injectablesConfig.rules],allowedDomains:this._injectablesConfig.allowedDomains})}if(this._layout===d.fixed)this.framePool=new H(this.container,this.positions,this.pub,this._injector,this._contentProtection,this._keyboardPeripherals),this.framePool.listener=(e,t)=>{this.eventListener(e,t)};else{await this.updateCSS(!1);const e=this.compileCSSProperties(this._css);this.framePool=new I(this.container,this.positions,e,this._injector,this._contentProtection,this._keyboardPeripherals,t=>this.pub.timeline.segmentsForHref(t).flatMap(i=>i.references).map(i=>{const s=i.indexOf("#");return s>=0?i.slice(s+1):""}).filter(Boolean))}this.currentLocation===void 0&&(this.currentLocation=this.positions[0]),await this.resizeHandler(),await this.apply()}get settings(){if(this._layout===d.fixed)return Object.freeze({...this._settings});{const e=this._css.userProperties.colCount||this._css.rsProperties.colCount||this._settings.columnCount;return Object.freeze({...this._settings,columnCount:e})}}get preferencesEditor(){return this._preferencesEditor===null&&(this._preferencesEditor=new b(this._preferences,this.settings,this.pub.metadata)),this._preferencesEditor}async submitPreferences(e){this._preferences=this._preferences.merging(e),await this.applyPreferences()}async applyPreferences(){const e=this._settings;this._settings=new k(this._preferences,this._defaults),this._preferencesEditor!==null&&(this._preferencesEditor=new b(this._preferences,this.settings,this.pub.metadata)),this._layout===d.fixed?this.handleFXLPrefs(e,this._settings):await this.updateCSS(!0)}handleFXLPrefs(e,t){e.columnCount!==t.columnCount&&this.framePool.setPerPage(t.columnCount)}async updateCSS(e){this._css.update(this._settings),e&&await this.commitCSS(this._css)}compileCSSProperties(e){const t={};for(const[i,s]of Object.entries(e.rsProperties.toCSSProperties()))t[i]=s;for(const[i,s]of Object.entries(e.userProperties.toCSSProperties()))t[i]=s;return t}async commitCSS(e){if(!this.framePool)return;const t=this.compileCSSProperties(e);this.framePool.setCSSProperties(t),this._css.userProperties.view==="paged"&&this._layout===d.scrolled?await this.setLayout(d.reflowable):this._css.userProperties.view==="scroll"&&this._layout===d.reflowable&&await this.setLayout(d.scrolled),this._css.setContainerWidth()}async resizeHandler(){const e=this.container.parentElement||document.documentElement;if(this._layout===d.fixed){if(this.container.style.width=`${K(e)-this._settings.constraint}px`,!this.framePool)return;this.framePool.resizeHandler()}else{const t=this._css.userProperties.colCount,i=this._css.userProperties.lineLength;this._css.resizeHandler(),(this._css.userProperties.view!=="scroll"&&t!==this._css.userProperties.colCount||i!==this._css.userProperties.lineLength)&&await this.commitCSS(this._css)}}get layout(){return this._layout}get ownerWindow(){return this.container.ownerDocument.defaultView||window}get _cframes(){return(this.framePool?.currentFrames??[]).filter(e=>!(e instanceof N&&e.isDestroyed))}get pool(){return this.framePool}eventListener(e,t,i){switch(e){case"_pong":if(this.listeners.frameLoaded(this._cframes[0].iframe.contentWindow),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation),i){const a=this._cframes.filter(l=>!!l).indexOf(i),c=a>=0?this.viewport.readingOrder[a]:void 0;c&&this._reapplyDecorationsToFrame(i,c)}else this._reapplyDecorationsToCurrentFrames();break;case"first_visible_locator":const s=v.deserialize(t);if(!s)break;this.currentLocation=new v({href:this.currentLocation.href,type:this.currentLocation.type,title:this.currentLocation.title,locations:s?.locations,text:s?.text}),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation);break;case"text_selected":{const n=t;if(i){const c=this._cframes.filter(u=>!!u).indexOf(i),l=c>=0?this.viewport.readingOrder[c]:void 0;if(l){const u=this.pub.readingOrder.findWithHref(l);n.locator=new v({href:l,type:u.type||"application/xhtml+xml",text:new D({highlight:n.text})})}}this.listeners.textSelected(n);break}case"decoration_activated":{this._handleDecorationActivated(t)&&(this._decorationActivationConsumed=!0);break}case"decoration_pointer_enter":this._handleDecorationPointerEnter(t);break;case"decoration_pointer_leave":this._handleDecorationPointerLeave(t);break;case"click":case"tap":if(this._decorationActivationConsumed){this._decorationActivationConsumed=!1;break}const r=t;if(r.interactiveElement){const n=new DOMParser().parseFromString(r.interactiveElement,"text/html").body.children[0];if(n.nodeType===n.ELEMENT_NODE&&n.nodeName==="A"&&n.hasAttribute("href")){const a=n.attributes.getNamedItem("href")?.value;if(a.startsWith("#"))this.go(this.currentLocation.copyWithLocations({fragments:[a.substring(1)]}),!1,()=>{});else if(a.startsWith("http://")||a.startsWith("https://")||a.startsWith("mailto:")||a.startsWith("tel:"))this.listeners.handleLocator(new _({href:a}).locator);else try{this.goLink(new _({href:y.join(y.dirname(this.currentLocation.href),a)}),!1,()=>{})}catch(c){console.warn(`Couldn't go to link for ${a}: ${c}`),this.listeners.handleLocator(new _({href:a}).locator)}}else console.log("Clicked on",n)}else{if(this._layout===d.fixed&&this.framePool.doNotDisturb&&(r.doNotDisturb=!0),this._layout===d.fixed&&(this.currentProgression===g.rtl||this.currentProgression===g.ltr)&&this.framePool.currentFrames.length>1){const c=this.framePool.currentFrames;r.targetFrameSrc===c[this.currentProgression===g.rtl?0:1]?.source&&(r.x+=(c[this.currentProgression===g.rtl?1:0]?.iframe.contentWindow?.innerWidth??0)*window.devicePixelRatio)}if(e==="click"?this.listeners.click(r):this.listeners.tap(r))break;const a=(this._cframes.length===2?this._cframes[0].window.innerWidth+this._cframes[1].window.innerWidth:this._cframes[0].window.innerWidth)*window.devicePixelRatio/4;r.x>=a&&r.x<=a*3&&this.listeners.miscPointer(1),r.x<a?this.goLeft(!1,()=>{}):r.x>a*3&&this.goRight(!1,()=>{})}break;case"tap_more":this.listeners.miscPointer(t);break;case"no_more":this.changeResource(1);break;case"no_less":this.changeResource(-1);break;case"swipe":break;case"scroll":this.listeners.scroll(t);break;case"zoom":this.listeners.zoom(t);break;case"progress":this.syncLocation(t);break;case"content_protection":const o=t;this.listeners.contentProtection(o.type,o);break;case"context_menu":this.listeners.contextMenu(t);break;case"keyboard_peripherals":const h=t,f={...h,interactiveElement:void 0};h.interactiveElement&&(f.interactiveElement=new DOMParser().parseFromString(h.interactiveElement,"text/html").body.children[0]),this.listeners.peripheral(f);break;case"log":console.log(this._cframes[0]?.source?.split("/")[3],...t);break;default:this.listeners.customEvent(e,t);break}}determineModules(){let e=Array.from(T.keys());if(this._layout===d.fixed)return e.filter(s=>F.includes(s));e=e.filter(s=>R.includes(s));const t=w(this.pub.metadata);if(t==="cjk-vertical"||t==="mongolian-vertical")return e.filter(s=>s!=="column_snapper"&&s!=="scroll_snapper");const i=e;return this._layout===d.scrolled?e=i.filter(s=>s!=="column_snapper"&&s!=="cjk_vertical_snapper"):e=i.filter(s=>s!=="scroll_snapper"&&s!=="cjk_vertical_snapper"),e}attachListener(){const e=this._cframes.filter(t=>!!t);if(e.length===0)throw Error("no cframe to attach listener to");e.forEach(t=>{t.msg&&(t.msg.listener=(i,s)=>{this.eventListener(i,s,t)})}),this._reapplyDecorationsToCurrentFrames()}async apply(){if(await this.framePool.update(this.pub,this.currentLocator,this.determineModules()),this.attachListener(),this.pub.readingOrder.findIndexWithHref(this.currentLocation.href)<0)throw Error("Link for "+this.currentLocation.href+" not found!")}supportsDecorationStyle(e){return q(e,this._decoratorConfig.decorationTemplates)}registerDecorationObserver(e,t){this._decorationObservers.has(e)||this._decorationObservers.set(e,new Set),this._decorationObservers.get(e).add(t),t.onDecorationActivated&&(this._decorationActivationState.set(e,!0),this._sendDecorationActivationToFrames(e,!0)),(t.onDecorationPointerEnter||t.onDecorationPointerLeave)&&(this._decorationHoverState.set(e,!0),this._sendDecorationHoverToFrames(e,!0))}unregisterDecorationObserver(e){this._decorationObservers.forEach((t,i)=>{if(!t.has(e))return;t.delete(e);const s=[...t].some(o=>o.onDecorationActivated);this._decorationActivationState.has(i)&&!s&&(this._decorationActivationState.delete(i),this._sendDecorationActivationToFrames(i,!1));const r=[...t].some(o=>o.onDecorationPointerEnter||o.onDecorationPointerLeave);this._decorationHoverState.has(i)&&!r&&(this._decorationHoverState.delete(i),this._sendDecorationHoverToFrames(i,!1))})}_sendDecorationActivationToFrames(e,t){this._cframes.filter(s=>!!s).forEach(s=>{s.msg&&s.msg.send("decoration_activatable",{group:e,activatable:t})})}_sendDecorationHoverToFrames(e,t){this._cframes.filter(s=>!!s).forEach(s=>{s.msg&&s.msg.send("decoration_hoverable",{group:e,hoverable:t})})}applyDecorations(e,t){const i=this._decorations.get(t)??[],s=new Map(i.map(c=>[c.id,c])),r=new Map(e.map(c=>[c.id,c])),o=[],h=[],f=[];for(const[c,l]of s)r.has(c)?Y(l,r.get(c))||f.push(r.get(c)):o.push(c);for(const[c,l]of r)s.has(c)||h.push(l);this._decorations.set(t,e),this._sendDecorationOps(t,o,h,f,i);const n=this._decorationActivationState.get(t);n!==void 0&&this._sendDecorationActivationToFrames(t,n);const a=this._decorationHoverState.get(t);a!==void 0&&this._sendDecorationHoverToFrames(t,a)}_sendDecorationOps(e,t,i,s,r){const o=this._cframes.filter(n=>!!n),h=new Map(r.map(n=>[n.id,n])),f=this.viewport.readingOrder;o.forEach((n,a)=>{if(!n.msg)return;const c=f[a];if(c){for(const l of t){const u=h.get(l);!u||u.locator.href!==c||n.msg.send("decorate",{group:e,action:"remove",decoration:{id:l}})}for(const l of i)l.locator.href===c&&n.msg.send("decorate",{group:e,action:"add",decoration:P(l,this._decoratorConfig.decorationTemplates)});for(const l of s)l.locator.href===c&&n.msg.send("decorate",{group:e,action:"update",decoration:P(l,this._decoratorConfig.decorationTemplates)})}})}_reapplyDecorationsToFrame(e,t){if(e.msg){for(const[i,s]of this._decorations){const r=s.filter(o=>o.locator.href===t);if(r.length!==0){e.msg.send("decorate",{group:i,action:"clear"});for(const o of r)e.msg.send("decorate",{group:i,action:"add",decoration:P(o,this._decoratorConfig.decorationTemplates)})}}for(const[i,s]of this._decorationActivationState)e.msg.send("decoration_activatable",{group:i,activatable:s});for(const[i,s]of this._decorationHoverState)e.msg.send("decoration_hoverable",{group:i,hoverable:s})}}_reapplyDecorationsToCurrentFrames(){const e=this._cframes.filter(i=>!!i),t=this.viewport.readingOrder;e.forEach((i,s)=>{const r=t[s];r&&this._reapplyDecorationsToFrame(i,r)})}_handleDecorationActivated(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return!1;const i=(this._decorations.get(e.group)??[]).find(o=>o.id===e.decorationId);if(!i)return!1;const s={decoration:i,group:e.group,rect:e.rect,point:e.point};let r=!1;for(const o of t)o.onDecorationActivated?.(s)&&(r=!0);return r}_handleDecorationPointerEnter(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return;const i=(this._decorations.get(e.group)??[]).find(r=>r.id===e.decorationId);if(!i)return;this._decorationHoveredDecorations.set(e.group,i);const s={decoration:i,group:e.group,rect:e.rect,point:e.point};for(const r of t)r.onDecorationPointerEnter?.(s)}_handleDecorationPointerLeave(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return;const i=(this._decorations.get(e.group)??[]).find(r=>r.id===e.decorationId)??this._decorationHoveredDecorations.get(e.group);if(this._decorationHoveredDecorations.delete(e.group),!i)return;const s={decoration:i,group:e.group,rect:e.rect,point:e.point};for(const r of t)r.onDecorationPointerLeave?.(s)}async destroy(){this._suspiciousActivityListener&&window.removeEventListener(x,this._suspiciousActivityListener),this._keyboardPeripheralListener&&window.removeEventListener(C,this._keyboardPeripheralListener),this._navigatorProtector?.destroy(),this._keyboardPeripheralsManager?.destroy(),await this.framePool?.destroy(),this._decorations.clear(),this._decorationObservers.clear(),this._decorationHoveredDecorations.clear(),this._decorationActivationState.clear(),this._decorationHoverState.clear()}async changeResource(e){if(e===0)return!1;if(this._layout===d.fixed){const s=this.framePool,r=s.viewport.positions[0];if(e===1){if(!s.next(s.perPage))return!1}else if(e===-1){if(!s.prev(s.perPage))return!1}else throw Error("Invalid relative value for FXL");const o=s.viewport.positions[0];if(r>o){for(let h=this.positions.length-1;h>=0;h--)if(this.positions[h].href===this.pub.readingOrder.items[o-1].href){this.currentLocation=this.positions[h].copyWithLocations({progression:.999999999999});break}}else if(r<o){for(let h=0;h<this.positions.length;h++)if(this.positions[h].href===this.pub.readingOrder.items[o-1].href){this.currentLocation=this.positions[h];break}}return await this.apply(),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation),!0}const t=this.pub.readingOrder.findIndexWithHref(this.currentLocation.href),i=Math.max(0,Math.min(this.pub.readingOrder.items.length-1,t+e));if(i===t)return this._cframes[0]?.msg?.send("shake",void 0,async s=>{}),!1;if(t>i){for(let s=this.positions.length-1;s>=0;s--)if(this.positions[s].href===this.pub.readingOrder.items[i].href){this.currentLocation=this.positions[s].copyWithLocations({progression:.999999999999});break}}else for(let s=0;s<this.positions.length;s++)if(this.positions[s].href===this.pub.readingOrder.items[i].href){this.currentLocation=this.positions[s];break}return await this.apply(),!0}findLastPositionInProgressionRange(e,t){const i=e.findLastIndex(s=>{const r=s.locations.progression;return!!(r&&r>t.start&&r<=t.end)});return i!==-1?e[i]:void 0}findNearestPositions(e){const t=this.positions.filter(o=>o.href===this.currentLocation.href);let i=this.currentLocation,s;const r=t.findLastIndex(o=>(o.locations.progression??0)<=e.start);if(r!==-1){i=t[r];const o=t.slice(r+1);s=this.findLastPositionInProgressionRange(o,e)}return{first:i,last:s}}updateViewport(e){this.reflowViewport.readingOrder=[],this.reflowViewport.progressions.clear(),this.reflowViewport.positions=null,this.currentLocation&&(this.reflowViewport.readingOrder.push(this.currentLocation.href),this.reflowViewport.progressions.set(this.currentLocation.href,e),this.currentLocation.locations?.position!==void 0&&(this.reflowViewport.positions=[this.currentLocation.locations.position],this.lastLocationInView?.locations?.position!==void 0&&this.reflowViewport.positions.push(this.lastLocationInView.locations.position)))}async syncLocation(e){const t=e,i=this.findNearestPositions(t);this.currentLocation=i.first.copyWithLocations({progression:t.start}),this.lastLocationInView=i.last,this.updateViewport(t),this.listeners.positionChanged(this.currentLocation);const s=t.fragmentId?this.currentLocation.copyWithLocations({fragments:[`#${t.fragmentId}`]}):this.currentLocation;this._notifyTimelineChange(s),await this.framePool.update(this.pub,this.currentLocation,this.determineModules())}goBackward(e,t){if(this._isNavigating){t(!1);return}this._isNavigating=!0,this._layout===d.fixed?this.changeResource(-1).then(i=>{this._isNavigating=!1,t(i)}):this._cframes[0]?.msg?.send("go_prev",void 0,async i=>{if(i)this._isNavigating=!1,t(!0);else{const s=await this.changeResource(-1);this._isNavigating=!1,t(s)}})}goForward(e,t){if(this._isNavigating){t(!1);return}this._isNavigating=!0,this._layout===d.fixed?this.changeResource(1).then(i=>{this._isNavigating=!1,t(i)}):this._cframes[0]?.msg?.send("go_next",void 0,async i=>{if(i)this._isNavigating=!1,t(!0);else{const s=await this.changeResource(1);this._isNavigating=!1,t(s)}})}get currentLocator(){return this.currentLocation}get viewport(){return this._layout===d.fixed?this.framePool?this.framePool.viewport:{readingOrder:[],progressions:new Map,positions:null}:this.reflowViewport}get isScrollStart(){const e=this.viewport.readingOrder[0];return this.viewport.progressions.get(e)?.start===0}get isScrollEnd(){const e=this.viewport.readingOrder[this.viewport.readingOrder.length-1];return this.viewport.progressions.get(e)?.end===1}get canGoBackward(){const e=this.pub.readingOrder.items[0]?.href;return!(this.viewport.progressions.has(e)&&this.viewport.progressions.get(e)?.start===0)}get canGoForward(){const e=this.pub.readingOrder.items[this.pub.readingOrder.items.length-1]?.href;return!(this.viewport.progressions.has(e)&&this.viewport.progressions.get(e)?.end===1)}get readingProgression(){return this.currentProgression}async setLayout(e){this._layout!==e&&(this._layout=e,await this.framePool.update(this.pub,this.currentLocator,this.determineModules(),!0),this.attachListener())}get publication(){return this.pub}get timeline(){const e=this.pub.timeline;if(!this._timelineAugmented&&this.positions?.length){const t=this.positions,i=new Set;e.augment((s,r)=>{if(s.children?.length)return{};const o=r.href.indexOf("#"),h=o>=0?r.href.slice(0,o):r.href,f=o>=0?r.href.slice(o+1):void 0,n=t.filter(l=>l.href===h);if(!n.length)return{};const a=f?n.find(l=>l.locations.fragments[0]===f):void 0;let c;return a?c=a:i.has(h)?c=void 0:(i.add(h),c=n.reduce((l,u)=>(u.locations.position??1/0)<(l.locations.position??1/0)?u:l)),c?{position:c.locations.position,scroll:a?.locations.progression}:{}}),this._timelineAugmented=!0}return e}async loadLocator(e,t){let i=!1,s=O(e.locations);if(e.text?.highlight?i=await new Promise((f,n)=>{this._cframes[0].msg.send("go_text",s?[e.text?.serialize(),s]:e.text?.serialize(),a=>f(a))}):s&&(i=await new Promise((f,n)=>{this._cframes[0].msg.send("go_text",["",s],a=>f(a))})),i){t(i);return}const r=A(e.locations);if(r&&(i=await new Promise((f,n)=>{this._cframes[0].msg.send("go_id",r,a=>f(a))})),i){t(i);return}const o=e?.locations?.progression;o&&o>0?i=await new Promise((f,n)=>{this._cframes[0].msg.send("go_progression",o,a=>f(a))}):i=!0,t(i)}go(e,t,i){const s=e.href.split("#")[0];let r=this.pub.readingOrder.findWithHref(s);if(!r)return i(this.listeners.handleLocator(e));if(this._isNavigating){i(!1);return}this._isNavigating=!0,this.currentLocation=this.positions.find(o=>o.href===r.href),this.apply().then(()=>this.loadLocator(e,o=>{this._isNavigating=!1,i(o)})).then(()=>{this.attachListener()})}goLink(e,t,i){return this.go(e.locator,t,i)}_notifyTimelineChange(e){const t=this.timeline.locate(e);t!==this._currentTimelineItem&&(this._currentTimelineItem=t,this.listeners.timelineItemChanged(t))}}export{S as EpubNavigator};
|
|
1
|
+
import{Layout as d,Profile as E,Link as _,ReadingProgression as g,Locator as v,LocatorText as D,getCssSelector as O,getHtmlId as I}from"@readium/shared";import{VisualNavigator as F}from"../Navigator.js";import{FramePoolManager as A}from"./frame/FramePoolManager.js";import{FXLFramePoolManager as M}from"./fxl/FXLFramePoolManager.js";import{ModuleLibrary as T,FXLModules as H,ReflowableModules as R}from"@readium/navigator-html-injectables";import{p as y}from"../_virtual/index.js";import{FrameManager as V}from"./frame/FrameManager.js";import{EpubPreferences as N}from"./preferences/EpubPreferences.js";import{EpubDefaults as W}from"./preferences/EpubDefaults.js";import{EpubPreferencesEditor as b}from"./preferences/EpubPreferencesEditor.js";import{ReadiumCSS as j}from"./css/ReadiumCSS.js";import{UserProperties as z,RSProperties as B}from"./css/Properties.js";import{getContentWidth as K}from"../helpers/dimensions.js";import{Injector as $}from"../injection/Injector.js";import{createReadiumEpubRules as G}from"../injection/epubInjectables.js";import{NavigatorProtector as X,NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT as x}from"../protection/NavigatorProtector.js";import{KeyboardPeripherals as J,NAVIGATOR_KEYBOARD_PERIPHERAL_EVENT as C}from"../peripherals/KeyboardPeripherals.js";import{getScriptMode as w}from"../helpers/scriptMode.js";import{EpubSettings as S}from"./preferences/EpubSettings.js";import{LineLengths as U}from"../helpers/lineLength.js";import{supportsDecorationStyle as q,decorationsEqual as Y,resolveDecorationForWire as P}from"@readium/decorator";const Q=p=>({frameLoaded:p.frameLoaded||(()=>{}),positionChanged:p.positionChanged||(()=>{}),timelineItemChanged:p.timelineItemChanged||(()=>{}),tap:p.tap||(()=>!1),click:p.click||(()=>!1),zoom:p.zoom||(()=>{}),miscPointer:p.miscPointer||(()=>{}),scroll:p.scroll||(()=>{}),customEvent:p.customEvent||(()=>{}),handleLocator:p.handleLocator||(()=>!1),textSelected:p.textSelected||(()=>{}),contentProtection:p.contentProtection||(()=>{}),contextMenu:p.contextMenu||(()=>{}),peripheral:p.peripheral||(()=>{})});function Z(p,e){return p.length===e.length&&p.every((t,i)=>t===e[i])}class k extends F{constructor(e,t,i,s=[],r=void 0,o={preferences:{},defaults:{}}){super(),this._visibleFragmentIds=[],this._notifiedVisibleFragmentIds=[],this._timelineAugmented=!1,this._preferencesEditor=null,this._injector=null,this._isNavigating=!1,this._navigatorProtector=null,this._keyboardPeripheralsManager=null,this._suspiciousActivityListener=null,this._keyboardPeripheralListener=null,this._decorations=new Map,this._decorationObservers=new Map,this._decorationHoveredDecorations=new Map,this._decorationActivationState=new Map,this._decorationHoverState=new Map,this._decorationActivationConsumed=!1,this.reflowViewport={readingOrder:[],progressions:new Map,positions:null},this.pub=t,this.container=e,this.listeners=Q(i),this.currentLocation=r,s.length&&(this.positions=s),this._preferences=new N(o.preferences),this._defaults=new W(o.defaults),this._settings=new S(this._preferences,this._defaults);const h=w(t.metadata),f=h==="cjk-horizontal",a=h==="cjk-vertical",c=a||h==="mongolian-vertical",l=f||a;this._css=new j({rsProperties:new B({noVerticalPagination:c||void 0}),userProperties:new z({}),lineLengths:new U({optimalChars:this._settings.optimalLineLength,minChars:this._settings.minimalLineLength,maxChars:this._settings.maximalLineLength,padding:this._settings.scroll?(this._settings.scrollPaddingLeft||0)+(this._settings.scrollPaddingRight||0):(this._settings.pageGutter||0)*2,fontFace:this._settings.fontFamily,letterSpacing:this._settings.letterSpacing,wordSpacing:this._settings.wordSpacing,isCJK:l}),container:e,constraint:this._settings.constraint,isCJKVertical:c}),this._layout=k.determineLayout(t,!!this._settings.scroll),this.currentProgression=t.metadata.effectiveReadingProgression,this._injectablesConfig=o.injectables||{rules:[],allowedDomains:[]},this._readiumRulesPromise=G(t.metadata,t.readingOrder.items),this._contentProtection=o.contentProtection||{},this._decoratorConfig=o.decoratorConfig||{},this._keyboardPeripherals=this.mergeKeyboardPeripherals(this._contentProtection,o.keyboardPeripherals||[]),(this._contentProtection.disableContextMenu||this._contentProtection.checkAutomation||this._contentProtection.checkIFrameEmbedding||this._contentProtection.monitorDevTools||this._contentProtection.protectPrinting?.disable)&&(this._navigatorProtector=new X(this._contentProtection),this._suspiciousActivityListener=u=>{const{type:m,...L}=u.detail;m==="context_menu"?this.listeners.contextMenu(L):this.listeners.contentProtection(m,L)},window.addEventListener(x,this._suspiciousActivityListener)),this._keyboardPeripherals.length>0&&(this._keyboardPeripheralsManager=new J({keyboardPeripherals:this._keyboardPeripherals}),this._keyboardPeripheralListener=u=>{const m=u.detail;this.listeners.peripheral(m)},window.addEventListener(C,this._keyboardPeripheralListener)),this.resizeObserver=new ResizeObserver(()=>this.ownerWindow.requestAnimationFrame(async()=>await this.resizeHandler())),this.resizeObserver.observe(this.container.parentElement||document.documentElement)}static determineLayout(e,t){const i=e.metadata.effectiveLayout;if(i===d.fixed)return d.fixed;if(e.metadata.otherMetadata&&"http://openmangaformat.org/schema/1.0#version"in e.metadata.otherMetadata)return d.fixed;if(e.metadata?.conformsTo?.includes(E.DIVINA))return d.fixed;if(i===d.scrolled)return d.scrolled;const s=w(e.metadata);return s==="cjk-vertical"||s==="mongolian-vertical"?d.scrolled:i===d.reflowable&&t?d.scrolled:d.reflowable}async load(){if(this.positions?.length||(this.positions=await this.pub.positionsFromManifest()),!this._injector){const e=await this._readiumRulesPromise;this._injector=new $({rules:[...e,...this._injectablesConfig.rules],allowedDomains:this._injectablesConfig.allowedDomains})}if(this._layout===d.fixed)this.framePool=new M(this.container,this.positions,this.pub,this._injector,this._contentProtection,this._keyboardPeripherals),this.framePool.listener=(e,t)=>{this.eventListener(e,t)};else{await this.updateCSS(!1);const e=this.compileCSSProperties(this._css);this.framePool=new A(this.container,this.positions,e,this._injector,this._contentProtection,this._keyboardPeripherals,t=>this.pub.timeline.segmentsForHref(t).flatMap(i=>i.references).map(i=>{const s=i.indexOf("#");return s>=0?i.slice(s+1):""}).filter(Boolean))}this.currentLocation===void 0&&(this.currentLocation=this.positions[0]),await this.resizeHandler(),await this.apply()}get settings(){if(this._layout===d.fixed)return Object.freeze({...this._settings});{const e=this._css.userProperties.colCount||this._css.rsProperties.colCount||this._settings.columnCount;return Object.freeze({...this._settings,columnCount:e})}}get preferencesEditor(){return this._preferencesEditor===null&&(this._preferencesEditor=new b(this._preferences,this.settings,this.pub.metadata)),this._preferencesEditor}async submitPreferences(e){this._preferences=this._preferences.merging(e),await this.applyPreferences()}async applyPreferences(){const e=this._settings;this._settings=new S(this._preferences,this._defaults),this._preferencesEditor!==null&&(this._preferencesEditor=new b(this._preferences,this.settings,this.pub.metadata)),this._layout===d.fixed?this.handleFXLPrefs(e,this._settings):await this.updateCSS(!0)}handleFXLPrefs(e,t){e.columnCount!==t.columnCount&&this.framePool.setPerPage(t.columnCount)}async updateCSS(e){this._css.update(this._settings),e&&await this.commitCSS(this._css)}compileCSSProperties(e){const t={};for(const[i,s]of Object.entries(e.rsProperties.toCSSProperties()))t[i]=s;for(const[i,s]of Object.entries(e.userProperties.toCSSProperties()))t[i]=s;return t}async commitCSS(e){if(!this.framePool)return;const t=this.compileCSSProperties(e);this.framePool.setCSSProperties(t),this._css.userProperties.view==="paged"&&this._layout===d.scrolled?await this.setLayout(d.reflowable):this._css.userProperties.view==="scroll"&&this._layout===d.reflowable&&await this.setLayout(d.scrolled),this._css.setContainerWidth()}async resizeHandler(){const e=this.container.parentElement||document.documentElement;if(this._layout===d.fixed){if(this.container.style.width=`${K(e)-this._settings.constraint}px`,!this.framePool)return;this.framePool.resizeHandler()}else{const t=this._css.userProperties.colCount,i=this._css.userProperties.lineLength;this._css.resizeHandler(),(this._css.userProperties.view!=="scroll"&&t!==this._css.userProperties.colCount||i!==this._css.userProperties.lineLength)&&await this.commitCSS(this._css)}}get layout(){return this._layout}get ownerWindow(){return this.container.ownerDocument.defaultView||window}get _cframes(){return(this.framePool?.currentFrames??[]).filter(e=>!(e instanceof V&&e.isDestroyed))}get pool(){return this.framePool}eventListener(e,t,i){switch(e){case"_pong":if(this.listeners.frameLoaded(this._cframes[0].iframe.contentWindow),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation),i){const n=this._cframes.filter(l=>!!l).indexOf(i),c=n>=0?this.viewport.readingOrder[n]:void 0;c&&this._reapplyDecorationsToFrame(i,c)}else this._reapplyDecorationsToCurrentFrames();break;case"first_visible_locator":const s=v.deserialize(t);if(!s)break;this.currentLocation=new v({href:this.currentLocation.href,type:this.currentLocation.type,title:this.currentLocation.title,locations:s?.locations,text:s?.text}),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation);break;case"text_selected":{const a=t;if(i){const c=this._cframes.filter(u=>!!u).indexOf(i),l=c>=0?this.viewport.readingOrder[c]:void 0;if(l){const u=this.pub.readingOrder.findWithHref(l);a.locator=new v({href:l,type:u.type||"application/xhtml+xml",text:new D({highlight:a.text})})}}this.listeners.textSelected(a);break}case"decoration_activated":{this._handleDecorationActivated(t)&&(this._decorationActivationConsumed=!0);break}case"decoration_pointer_enter":this._handleDecorationPointerEnter(t);break;case"decoration_pointer_leave":this._handleDecorationPointerLeave(t);break;case"click":case"tap":if(this._decorationActivationConsumed){this._decorationActivationConsumed=!1;break}const r=t;if(r.interactiveElement){const a=new DOMParser().parseFromString(r.interactiveElement,"text/html").body.children[0];if(a.nodeType===a.ELEMENT_NODE&&a.nodeName==="A"&&a.hasAttribute("href")){const n=a.attributes.getNamedItem("href")?.value;if(n.startsWith("#"))this.go(this.currentLocation.copyWithLocations({fragments:[n.substring(1)]}),!1,()=>{});else if(n.startsWith("http://")||n.startsWith("https://")||n.startsWith("mailto:")||n.startsWith("tel:"))this.listeners.handleLocator(new _({href:n}).locator);else try{this.goLink(new _({href:y.join(y.dirname(this.currentLocation.href),n)}),!1,()=>{})}catch(c){console.warn(`Couldn't go to link for ${n}: ${c}`),this.listeners.handleLocator(new _({href:n}).locator)}}else console.log("Clicked on",a)}else{if(this._layout===d.fixed&&this.framePool.doNotDisturb&&(r.doNotDisturb=!0),this._layout===d.fixed&&(this.currentProgression===g.rtl||this.currentProgression===g.ltr)&&this.framePool.currentFrames.length>1){const c=this.framePool.currentFrames;r.targetFrameSrc===c[this.currentProgression===g.rtl?0:1]?.source&&(r.x+=(c[this.currentProgression===g.rtl?1:0]?.iframe.contentWindow?.innerWidth??0)*window.devicePixelRatio)}if(e==="click"?this.listeners.click(r):this.listeners.tap(r))break;const n=(this._cframes.length===2?this._cframes[0].window.innerWidth+this._cframes[1].window.innerWidth:this._cframes[0].window.innerWidth)*window.devicePixelRatio/4;r.x>=n&&r.x<=n*3&&this.listeners.miscPointer(1),r.x<n?this.goLeft(!1,()=>{}):r.x>n*3&&this.goRight(!1,()=>{})}break;case"tap_more":this.listeners.miscPointer(t);break;case"no_more":this.changeResource(1);break;case"no_less":this.changeResource(-1);break;case"swipe":break;case"scroll":this.listeners.scroll(t);break;case"zoom":this.listeners.zoom(t);break;case"progress":this.syncLocation(t);break;case"content_protection":const o=t;this.listeners.contentProtection(o.type,o);break;case"context_menu":this.listeners.contextMenu(t);break;case"keyboard_peripherals":const h=t,f={...h,interactiveElement:void 0};h.interactiveElement&&(f.interactiveElement=new DOMParser().parseFromString(h.interactiveElement,"text/html").body.children[0]),this.listeners.peripheral(f);break;case"log":console.log(this._cframes[0]?.source?.split("/")[3],...t);break;default:this.listeners.customEvent(e,t);break}}determineModules(){let e=Array.from(T.keys());if(this._layout===d.fixed)return e.filter(s=>H.includes(s));e=e.filter(s=>R.includes(s));const t=w(this.pub.metadata);if(t==="cjk-vertical"||t==="mongolian-vertical")return e.filter(s=>s!=="column_snapper"&&s!=="scroll_snapper");const i=e;return this._layout===d.scrolled?e=i.filter(s=>s!=="column_snapper"&&s!=="cjk_vertical_snapper"):e=i.filter(s=>s!=="scroll_snapper"&&s!=="cjk_vertical_snapper"),e}attachListener(){const e=this._cframes.filter(t=>!!t);if(e.length===0)throw Error("no cframe to attach listener to");e.forEach(t=>{t.msg&&(t.msg.listener=(i,s)=>{this.eventListener(i,s,t)})}),this._reapplyDecorationsToCurrentFrames()}async apply(){if(await this.framePool.update(this.pub,this.currentLocator,this.determineModules()),this.attachListener(),this.pub.readingOrder.findIndexWithHref(this.currentLocation.href)<0)throw Error("Link for "+this.currentLocation.href+" not found!")}supportsDecorationStyle(e){return q(e,this._decoratorConfig.decorationTemplates)}registerDecorationObserver(e,t){this._decorationObservers.has(e)||this._decorationObservers.set(e,new Set),this._decorationObservers.get(e).add(t),t.onDecorationActivated&&(this._decorationActivationState.set(e,!0),this._sendDecorationActivationToFrames(e,!0)),(t.onDecorationPointerEnter||t.onDecorationPointerLeave)&&(this._decorationHoverState.set(e,!0),this._sendDecorationHoverToFrames(e,!0))}unregisterDecorationObserver(e){this._decorationObservers.forEach((t,i)=>{if(!t.has(e))return;t.delete(e);const s=[...t].some(o=>o.onDecorationActivated);this._decorationActivationState.has(i)&&!s&&(this._decorationActivationState.delete(i),this._sendDecorationActivationToFrames(i,!1));const r=[...t].some(o=>o.onDecorationPointerEnter||o.onDecorationPointerLeave);this._decorationHoverState.has(i)&&!r&&(this._decorationHoverState.delete(i),this._sendDecorationHoverToFrames(i,!1))})}_sendDecorationActivationToFrames(e,t){this._cframes.filter(s=>!!s).forEach(s=>{s.msg&&s.msg.send("decoration_activatable",{group:e,activatable:t})})}_sendDecorationHoverToFrames(e,t){this._cframes.filter(s=>!!s).forEach(s=>{s.msg&&s.msg.send("decoration_hoverable",{group:e,hoverable:t})})}applyDecorations(e,t){const i=this._decorations.get(t)??[],s=new Map(i.map(c=>[c.id,c])),r=new Map(e.map(c=>[c.id,c])),o=[],h=[],f=[];for(const[c,l]of s)r.has(c)?Y(l,r.get(c))||f.push(r.get(c)):o.push(c);for(const[c,l]of r)s.has(c)||h.push(l);this._decorations.set(t,e),this._sendDecorationOps(t,o,h,f,i);const a=this._decorationActivationState.get(t);a!==void 0&&this._sendDecorationActivationToFrames(t,a);const n=this._decorationHoverState.get(t);n!==void 0&&this._sendDecorationHoverToFrames(t,n)}_sendDecorationOps(e,t,i,s,r){const o=this._cframes.filter(a=>!!a),h=new Map(r.map(a=>[a.id,a])),f=this.viewport.readingOrder;o.forEach((a,n)=>{if(!a.msg)return;const c=f[n];if(c){for(const l of t){const u=h.get(l);!u||u.locator.href!==c||a.msg.send("decorate",{group:e,action:"remove",decoration:{id:l}})}for(const l of i)l.locator.href===c&&a.msg.send("decorate",{group:e,action:"add",decoration:P(l,this._decoratorConfig.decorationTemplates)});for(const l of s)l.locator.href===c&&a.msg.send("decorate",{group:e,action:"update",decoration:P(l,this._decoratorConfig.decorationTemplates)})}})}_reapplyDecorationsToFrame(e,t){if(e.msg){for(const[i,s]of this._decorations){const r=s.filter(o=>o.locator.href===t);if(r.length!==0){e.msg.send("decorate",{group:i,action:"clear"});for(const o of r)e.msg.send("decorate",{group:i,action:"add",decoration:P(o,this._decoratorConfig.decorationTemplates)})}}for(const[i,s]of this._decorationActivationState)e.msg.send("decoration_activatable",{group:i,activatable:s});for(const[i,s]of this._decorationHoverState)e.msg.send("decoration_hoverable",{group:i,hoverable:s})}}_reapplyDecorationsToCurrentFrames(){const e=this._cframes.filter(i=>!!i),t=this.viewport.readingOrder;e.forEach((i,s)=>{const r=t[s];r&&this._reapplyDecorationsToFrame(i,r)})}_handleDecorationActivated(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return!1;const i=(this._decorations.get(e.group)??[]).find(o=>o.id===e.decorationId);if(!i)return!1;const s={decoration:i,group:e.group,rect:e.rect,point:e.point};let r=!1;for(const o of t)o.onDecorationActivated?.(s)&&(r=!0);return r}_handleDecorationPointerEnter(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return;const i=(this._decorations.get(e.group)??[]).find(r=>r.id===e.decorationId);if(!i)return;this._decorationHoveredDecorations.set(e.group,i);const s={decoration:i,group:e.group,rect:e.rect,point:e.point};for(const r of t)r.onDecorationPointerEnter?.(s)}_handleDecorationPointerLeave(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return;const i=(this._decorations.get(e.group)??[]).find(r=>r.id===e.decorationId)??this._decorationHoveredDecorations.get(e.group);if(this._decorationHoveredDecorations.delete(e.group),!i)return;const s={decoration:i,group:e.group,rect:e.rect,point:e.point};for(const r of t)r.onDecorationPointerLeave?.(s)}async destroy(){this._suspiciousActivityListener&&window.removeEventListener(x,this._suspiciousActivityListener),this._keyboardPeripheralListener&&window.removeEventListener(C,this._keyboardPeripheralListener),this._navigatorProtector?.destroy(),this._keyboardPeripheralsManager?.destroy(),await this.framePool?.destroy(),this._decorations.clear(),this._decorationObservers.clear(),this._decorationHoveredDecorations.clear(),this._decorationActivationState.clear(),this._decorationHoverState.clear()}async changeResource(e){if(e===0)return!1;if(this._layout===d.fixed){const s=this.framePool,r=s.viewport.positions[0];if(e===1){if(!s.next(s.perPage))return!1}else if(e===-1){if(!s.prev(s.perPage))return!1}else throw Error("Invalid relative value for FXL");const o=s.viewport.positions[0];if(r>o){for(let h=this.positions.length-1;h>=0;h--)if(this.positions[h].href===this.pub.readingOrder.items[o-1].href){this.currentLocation=this.positions[h].copyWithLocations({progression:.999999999999});break}}else if(r<o){for(let h=0;h<this.positions.length;h++)if(this.positions[h].href===this.pub.readingOrder.items[o-1].href){this.currentLocation=this.positions[h];break}}return await this.apply(),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation),!0}const t=this.pub.readingOrder.findIndexWithHref(this.currentLocation.href),i=Math.max(0,Math.min(this.pub.readingOrder.items.length-1,t+e));if(i===t)return this._cframes[0]?.msg?.send("shake",void 0,async s=>{}),!1;if(t>i){for(let s=this.positions.length-1;s>=0;s--)if(this.positions[s].href===this.pub.readingOrder.items[i].href){this.currentLocation=this.positions[s].copyWithLocations({progression:.999999999999});break}}else for(let s=0;s<this.positions.length;s++)if(this.positions[s].href===this.pub.readingOrder.items[i].href){this.currentLocation=this.positions[s];break}return await this.apply(),!0}findLastPositionInProgressionRange(e,t){const i=e.findLastIndex(s=>{const r=s.locations.progression;return!!(r&&r>t.start&&r<=t.end)});return i!==-1?e[i]:void 0}findNearestPositions(e){const t=this.positions.filter(o=>o.href===this.currentLocation.href);let i=this.currentLocation,s;const r=t.findLastIndex(o=>(o.locations.progression??0)<=e.start);if(r!==-1){i=t[r];const o=t.slice(r+1);s=this.findLastPositionInProgressionRange(o,e)}return{first:i,last:s}}updateViewport(e){this.reflowViewport.readingOrder=[],this.reflowViewport.progressions.clear(),this.reflowViewport.positions=null,this.currentLocation&&(this.reflowViewport.readingOrder.push(this.currentLocation.href),this.reflowViewport.progressions.set(this.currentLocation.href,e),this.currentLocation.locations?.position!==void 0&&(this.reflowViewport.positions=[this.currentLocation.locations.position],this.lastLocationInView?.locations?.position!==void 0&&this.reflowViewport.positions.push(this.lastLocationInView.locations.position)))}async syncLocation(e){const t=e,i=this.findNearestPositions(t);this.currentLocation=i.first.copyWithLocations({progression:t.start}),this.lastLocationInView=i.last,this.updateViewport(t),this.listeners.positionChanged(this.currentLocation);const s=t.fragmentId?this.currentLocation.copyWithLocations({fragments:[`#${t.fragmentId}`]}):this.currentLocation;this._visibleFragmentIds=t.visibleFragmentIds??[],this._notifyTimelineChange(s),await this.framePool.update(this.pub,this.currentLocation,this.determineModules())}goBackward(e,t){if(this._isNavigating){t(!1);return}this._isNavigating=!0,this._layout===d.fixed?this.changeResource(-1).then(i=>{this._isNavigating=!1,t(i)}):this._cframes[0]?.msg?.send("go_prev",void 0,async i=>{if(i)this._isNavigating=!1,t(!0);else{const s=await this.changeResource(-1);this._isNavigating=!1,t(s)}})}goForward(e,t){if(this._isNavigating){t(!1);return}this._isNavigating=!0,this._layout===d.fixed?this.changeResource(1).then(i=>{this._isNavigating=!1,t(i)}):this._cframes[0]?.msg?.send("go_next",void 0,async i=>{if(i)this._isNavigating=!1,t(!0);else{const s=await this.changeResource(1);this._isNavigating=!1,t(s)}})}get currentLocator(){return this.currentLocation}get viewport(){return this._layout===d.fixed?this.framePool?this.framePool.viewport:{readingOrder:[],progressions:new Map,positions:null}:this.reflowViewport}get isScrollStart(){const e=this.viewport.readingOrder[0];return this.viewport.progressions.get(e)?.start===0}get isScrollEnd(){const e=this.viewport.readingOrder[this.viewport.readingOrder.length-1];return this.viewport.progressions.get(e)?.end===1}get canGoBackward(){const e=this.pub.readingOrder.items[0]?.href;return!(this.viewport.progressions.has(e)&&this.viewport.progressions.get(e)?.start===0)}get canGoForward(){const e=this.pub.readingOrder.items[this.pub.readingOrder.items.length-1]?.href;return!(this.viewport.progressions.has(e)&&this.viewport.progressions.get(e)?.end===1)}get readingProgression(){return this.currentProgression}async setLayout(e){this._layout!==e&&(this._layout=e,await this.framePool.update(this.pub,this.currentLocator,this.determineModules(),!0),this.attachListener())}get publication(){return this.pub}get timeline(){const e=this.pub.timeline;if(!this._timelineAugmented&&this.positions?.length){const t=this.positions,i=new Set;e.augment((s,r)=>{if(s.children?.length)return{};const o=r.href.indexOf("#"),h=o>=0?r.href.slice(0,o):r.href,f=o>=0?r.href.slice(o+1):void 0,a=t.filter(l=>l.href===h);if(!a.length)return{};const n=f?a.find(l=>l.locations.fragments[0]===f):void 0;let c;return n?c=n:i.has(h)?c=void 0:(i.add(h),c=a.reduce((l,u)=>(u.locations.position??1/0)<(l.locations.position??1/0)?u:l)),c?{position:c.locations.position,scroll:n?.locations.progression}:{}}),this._timelineAugmented=!0}return this._wrappedTimeline||(this._wrappedTimeline=new Proxy(e,{get:(t,i)=>{if(i!=="navigableFrom"){const s=Reflect.get(t,i,t);return typeof s=="function"?s.bind(t):s}return s=>{const r=this._visibleFragmentIds;let o=(r.length?t.locate(this.currentLocation.copyWithLocations({fragments:[`#${r[0]}`]})):void 0)??s;const h=(r.length?t.locate(this.currentLocation.copyWithLocations({fragments:[`#${r[r.length-1]}`]})):void 0)??s;if(this.isScrollStart){const n=t.ancestors(o).find(c=>c.references.includes(this.currentLocation.href));n&&(o=n)}const f=t.navigableFrom(o).previous,a=t.navigableFrom(h).next;return{previous:f,next:a}}}})),this._wrappedTimeline}async loadLocator(e,t){let i=!1,s=O(e.locations);if(e.text?.highlight?i=await new Promise((f,a)=>{this._cframes[0].msg.send("go_text",s?[e.text?.serialize(),s]:e.text?.serialize(),n=>f(n))}):s&&(i=await new Promise((f,a)=>{this._cframes[0].msg.send("go_text",["",s],n=>f(n))})),i){t(i);return}const r=I(e.locations);if(r&&(i=await new Promise((f,a)=>{this._cframes[0].msg.send("go_id",r,n=>f(n))})),i){t(i);return}const o=e?.locations?.progression;o&&o>0?i=await new Promise((f,a)=>{this._cframes[0].msg.send("go_progression",o,n=>f(n))}):i=!0,t(i)}go(e,t,i){const s=e.href.split("#")[0];let r=this.pub.readingOrder.findWithHref(s);if(!r)return i(this.listeners.handleLocator(e));if(this._isNavigating){i(!1);return}this._isNavigating=!0,this.currentLocation=this.positions.find(o=>o.href===r.href),this.apply().then(()=>this.loadLocator(e,o=>{this._isNavigating=!1,i(o)})).then(()=>{this.attachListener()})}goLink(e,t,i){return this.go(e.locator,t,i)}_notifyTimelineChange(e){const t=this.timeline.locate(e),i=!Z(this._visibleFragmentIds,this._notifiedVisibleFragmentIds);(t!==this._currentTimelineItem||i)&&(this._currentTimelineItem=t,this._notifiedVisibleFragmentIds=this._visibleFragmentIds,this.listeners.timelineItemChanged(t))}}export{k as EpubNavigator};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Feature as L,Link as u,Locator as p,LocatorText as P,getCssSelector as y,getHtmlId as S,LocatorLocations as k}from"@readium/shared";import{VisualNavigator as D}from"../Navigator.js";import{WebPubFramePoolManager as C}from"./WebPubFramePoolManager.js";import{WebPubModules as x}from"@readium/navigator-html-injectables";import{p as g}from"../_virtual/index.js";import{getScriptMode as E}from"../helpers/scriptMode.js";import{WebPubCSS as O}from"./css/WebPubCSS.js";import{WebUserProperties as A,WebRSProperties as I}from"./css/Properties.js";import{WebPubPreferences as W}from"./preferences/WebPubPreferences.js";import{WebPubDefaults as H}from"./preferences/WebPubDefaults.js";import{WebPubSettings as _}from"./preferences/WebPubSettings.js";import{WebPubPreferencesEditor as v}from"./preferences/WebPubPreferencesEditor.js";import{Injector as M}from"../injection/Injector.js";import{createReadiumWebPubRules as T}from"../injection/webpubInjectables.js";import{NavigatorProtector as F,NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT as b}from"../protection/NavigatorProtector.js";import{KeyboardPeripherals as N,NAVIGATOR_KEYBOARD_PERIPHERAL_EVENT as w}from"../peripherals/KeyboardPeripherals.js";import{supportsDecorationStyle as R,decorationsEqual as V,resolveDecorationForWire as m}from"@readium/decorator";const z=d=>({frameLoaded:d.frameLoaded||(()=>{}),positionChanged:d.positionChanged||(()=>{}),timelineItemChanged:d.timelineItemChanged||(()=>{}),tap:d.tap||(()=>!1),click:d.click||(()=>!1),zoom:d.zoom||(()=>{}),scroll:d.scroll||(()=>{}),customEvent:d.customEvent||(()=>{}),handleLocator:d.handleLocator||(()=>!1),textSelected:d.textSelected||(()=>{}),contentProtection:d.contentProtection||(()=>{}),contextMenu:d.contextMenu||(()=>{}),peripheral:d.peripheral||(()=>{})});class j extends D{constructor(e,t,r,i=void 0,s={preferences:{},defaults:{}}){super(),this.currentIndex=0,this._preferencesEditor=null,this._injector=null,this._isNavigating=!1,this._navigatorProtector=null,this._keyboardPeripheralsManager=null,this._suspiciousActivityListener=null,this._keyboardPeripheralListener=null,this._decorations=new Map,this._decorationObservers=new Map,this._decorationHoveredDecorations=new Map,this._decorationActivationState=new Map,this._decorationHoverState=new Map,this._decorationActivationConsumed=!1,this.webViewport={readingOrder:[],progressions:new Map,positions:null},this.pub=t,this.container=e,this.listeners=z(r),this._preferences=new W(s.preferences),this._defaults=new H(s.defaults),this._settings=new _(this._preferences,this._defaults,this.hasDisplayTransformability),this._css=new O({rsProperties:new I({experiments:this._settings.experiments||null}),userProperties:new A({zoom:this._settings.zoom})});const n=T(t.readingOrder.items),h=s.injectables||{rules:[],allowedDomains:[]};if(this._injector=new M({rules:[...n,...h.rules],allowedDomains:h.allowedDomains}),this._contentProtection=s.contentProtection||{},this._decoratorConfig=s.decoratorConfig||{},this._keyboardPeripherals=this.mergeKeyboardPeripherals(this._contentProtection,s.keyboardPeripherals||[]),(this._contentProtection.disableContextMenu||this._contentProtection.checkAutomation||this._contentProtection.checkIFrameEmbedding||this._contentProtection.monitorDevTools||this._contentProtection.protectPrinting?.disable)&&(this._navigatorProtector=new F(this._contentProtection),this._suspiciousActivityListener=a=>{const{type:o,...c}=a.detail;o==="context_menu"?this.listeners.contextMenu(c):this.listeners.contentProtection(o,c)},window.addEventListener(b,this._suspiciousActivityListener)),this._keyboardPeripherals.length>0&&(this._keyboardPeripheralsManager=new N({keyboardPeripherals:this._keyboardPeripherals}),this._keyboardPeripheralListener=a=>{const o=a.detail;this.listeners.peripheral(o)},window.addEventListener(w,this._keyboardPeripheralListener)),i&&typeof i.copyWithLocations=="function"){this.currentLocation=i;const a=this.pub.readingOrder.findIndexWithHref(i.href);a>=0&&(this.currentIndex=a)}else this.currentLocation=this.createCurrentLocator()}async load(){await this.updateCSS(!1);const e=this.compileCSSProperties(this._css);this.framePool=new C(this.container,e,this._injector,this._contentProtection,this._keyboardPeripherals,t=>this.pub.timeline.segmentsForHref(t).flatMap(r=>r.references).map(r=>{const i=r.indexOf("#");return i>=0?r.slice(i+1):""}).filter(Boolean)),await this.apply()}get settings(){return Object.freeze({...this._settings})}get preferencesEditor(){return this._preferencesEditor===null&&(this._preferencesEditor=new v(this._preferences,this.settings,this.pub.metadata)),this._preferencesEditor}async submitPreferences(e){this._preferences=this._preferences.merging(e),await this.applyPreferences()}async applyPreferences(){this._settings=new _(this._preferences,this._defaults,this.hasDisplayTransformability),this._preferencesEditor!==null&&(this._preferencesEditor=new v(this._preferences,this.settings,this.pub.metadata)),await this.updateCSS(!0)}async updateCSS(e){this._css.update(this._settings),e&&await this.commitCSS(this._css)}compileCSSProperties(e){const t={};for(const[r,i]of Object.entries(e.rsProperties.toCSSProperties()))t[r]=i;for(const[r,i]of Object.entries(e.userProperties.toCSSProperties()))t[r]=i;return t}async commitCSS(e){const t=this.compileCSSProperties(e);this.framePool.setCSSProperties(t)}get _cframes(){return this.framePool.currentFrames}get hasDisplayTransformability(){return this.pub.metadata?.accessibility?.feature?.some(e=>e.value===L.DISPLAY_TRANSFORMABILITY.value)??!1}eventListener(e,t){switch(e){case"_pong":this.listeners.frameLoaded(this.framePool.currentFrames[0].iframe.contentWindow),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation),this._reapplyDecorationsToCurrentFrame();break;case"first_visible_locator":const r=p.deserialize(t);if(!r)break;this.currentLocation=new p({href:this.currentLocation.href,type:this.currentLocation.type,title:this.currentLocation.title,locations:r?.locations,text:r?.text}),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation);break;case"text_selected":{const a=t;a.locator=new p({href:this.currentLocation.href,type:this.currentLocation.type,text:new P({highlight:a.text})}),this.listeners.textSelected(a);break}case"decoration_activated":{this._handleDecorationActivated(t)&&(this._decorationActivationConsumed=!0);break}case"decoration_pointer_enter":this._handleDecorationPointerEnter(t);break;case"decoration_pointer_leave":this._handleDecorationPointerLeave(t);break;case"click":case"tap":if(this._decorationActivationConsumed){this._decorationActivationConsumed=!1;break}const i=t;if(i.interactiveElement){const a=new DOMParser().parseFromString(i.interactiveElement,"text/html").body.children[0];if(a.nodeType===a.ELEMENT_NODE&&a.nodeName==="A"&&a.hasAttribute("href")){const o=a.attributes.getNamedItem("href")?.value;if(o.startsWith("#"))this.go(this.currentLocation.copyWithLocations({fragments:[o.substring(1)]}),!1,()=>{});else if(o.startsWith("mailto:")||o.startsWith("tel:"))this.listeners.handleLocator(new u({href:o}).locator);else try{let c;if(o.startsWith("http://")||o.startsWith("https://"))c=o;else if(this.currentLocation.href.startsWith("http://")||this.currentLocation.href.startsWith("https://")){const f=new URL(this.currentLocation.href);c=new URL(o,f).href}else c=g.join(g.dirname(this.currentLocation.href),o);const l=this.pub.readingOrder.findWithHref(c);l?this.goLink(l,!1,()=>{}):(console.warn(`Internal link not found in readingOrder: ${c}`),this.listeners.handleLocator(new u({href:o}).locator))}catch(c){console.warn(`Couldn't resolve internal link for ${o}: ${c}`),this.listeners.handleLocator(new u({href:o}).locator)}}else console.log("Clicked on",a)}else if(e==="click"?this.listeners.click(i):this.listeners.tap(i))break;break;case"scroll":this.listeners.scroll(t);break;case"zoom":this.listeners.zoom(t);break;case"progress":this.syncLocation(t);break;case"content_protection":const s=t;this.listeners.contentProtection(s.type,s);break;case"context_menu":this.listeners.contextMenu(t);break;case"keyboard_peripherals":const n=t,h={...n,interactiveElement:void 0};n.interactiveElement&&(h.interactiveElement=new DOMParser().parseFromString(n.interactiveElement,"text/html").body.children[0]),this.listeners.peripheral(h);break;case"log":console.log(this.framePool.currentFrames[0]?.source?.split("/")[3],...t);break;default:this.listeners.customEvent(e,t);break}}determineModules(){const e=x.slice(),t=E(this.pub.metadata);return t==="cjk-vertical"||t==="mongolian-vertical"?e.map(r=>r==="webpub_snapper"?"cjk_vertical_snapper":r):e}attachListener(){this.framePool.currentFrames[0]?.msg&&(this.framePool.currentFrames[0].msg.listener=(e,t)=>{this.eventListener(e,t)}),this._reapplyDecorationsToCurrentFrame()}async apply(){if(await this.framePool.update(this.pub,this.currentLocation,this.determineModules()),this.attachListener(),this.pub.readingOrder.findIndexWithHref(this.currentLocation.href)<0)throw Error("Link for "+this.currentLocation.href+" not found!")}async destroy(){this._suspiciousActivityListener&&window.removeEventListener(b,this._suspiciousActivityListener),this._keyboardPeripheralListener&&window.removeEventListener(w,this._keyboardPeripheralListener),this._navigatorProtector?.destroy(),this._keyboardPeripheralsManager?.destroy(),await this.framePool?.destroy(),this._decorations.clear(),this._decorationObservers.clear(),this._decorationHoveredDecorations.clear(),this._decorationActivationState.clear(),this._decorationHoverState.clear()}supportsDecorationStyle(e){return R(e,this._decoratorConfig.decorationTemplates)}registerDecorationObserver(e,t){this._decorationObservers.has(e)||this._decorationObservers.set(e,new Set),this._decorationObservers.get(e).add(t),t.onDecorationActivated&&(this._decorationActivationState.set(e,!0),this._sendDecorationActivatable(e,!0)),(t.onDecorationPointerEnter||t.onDecorationPointerLeave)&&(this._decorationHoverState.set(e,!0),this._sendDecorationHoverable(e,!0))}unregisterDecorationObserver(e){this._decorationObservers.forEach((t,r)=>{if(!t.has(e))return;t.delete(e);const i=[...t].some(n=>n.onDecorationActivated);this._decorationActivationState.has(r)&&!i&&(this._decorationActivationState.delete(r),this._sendDecorationActivatable(r,!1));const s=[...t].some(n=>n.onDecorationPointerEnter||n.onDecorationPointerLeave);this._decorationHoverState.has(r)&&!s&&(this._decorationHoverState.delete(r),this._sendDecorationHoverable(r,!1))})}_sendDecorationActivatable(e,t){const r=this.framePool?.currentFrames[0];r?.msg&&r.msg.send("decoration_activatable",{group:e,activatable:t})}_sendDecorationHoverable(e,t){const r=this.framePool?.currentFrames[0];r?.msg&&r.msg.send("decoration_hoverable",{group:e,hoverable:t})}applyDecorations(e,t){const r=this._decorations.get(t)??[],i=new Map(r.map(l=>[l.id,l])),s=new Map(e.map(l=>[l.id,l])),n=[],h=[],a=[];for(const[l,f]of i)s.has(l)?V(f,s.get(l))||a.push(s.get(l)):n.push(l);for(const[l,f]of s)i.has(l)||h.push(f);this._decorations.set(t,e),this._sendDecorationOps(t,n,h,a,r);const o=this._decorationActivationState.get(t);o!==void 0&&this._sendDecorationActivatable(t,o);const c=this._decorationHoverState.get(t);c!==void 0&&this._sendDecorationHoverable(t,c)}_sendDecorationOps(e,t,r,i,s){const n=this.framePool?.currentFrames[0];if(!n?.msg)return;const h=this.currentLocation.href,a=new Map(s.map(o=>[o.id,o]));for(const o of t){const c=a.get(o);!c||c.locator.href!==h||n.msg.send("decorate",{group:e,action:"remove",decoration:{id:o}})}for(const o of r)o.locator.href===h&&n.msg.send("decorate",{group:e,action:"add",decoration:m(o,this._decoratorConfig.decorationTemplates)});for(const o of i)o.locator.href===h&&n.msg.send("decorate",{group:e,action:"update",decoration:m(o,this._decoratorConfig.decorationTemplates)})}_reapplyDecorationsToCurrentFrame(){const e=this.framePool?.currentFrames[0];if(!e?.msg)return;const t=this.currentLocation.href;for(const[r,i]of this._decorations){const s=i.filter(n=>n.locator.href===t);if(s.length!==0){e.msg.send("decorate",{group:r,action:"clear"});for(const n of s)e.msg.send("decorate",{group:r,action:"add",decoration:m(n,this._decoratorConfig.decorationTemplates)})}}for(const[r,i]of this._decorationActivationState)e.msg.send("decoration_activatable",{group:r,activatable:i});for(const[r,i]of this._decorationHoverState)e.msg.send("decoration_hoverable",{group:r,hoverable:i})}_handleDecorationActivated(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return!1;const r=(this._decorations.get(e.group)??[]).find(n=>n.id===e.decorationId);if(!r)return!1;const i={decoration:r,group:e.group,rect:e.rect,point:e.point};let s=!1;for(const n of t)n.onDecorationActivated?.(i)&&(s=!0);return s}_handleDecorationPointerEnter(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return;const r=(this._decorations.get(e.group)??[]).find(s=>s.id===e.decorationId);if(!r)return;this._decorationHoveredDecorations.set(e.group,r);const i={decoration:r,group:e.group,rect:e.rect,point:e.point};for(const s of t)s.onDecorationPointerEnter?.(i)}_handleDecorationPointerLeave(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return;const r=(this._decorations.get(e.group)??[]).find(s=>s.id===e.decorationId)??this._decorationHoveredDecorations.get(e.group);if(this._decorationHoveredDecorations.delete(e.group),!r)return;const i={decoration:r,group:e.group,rect:e.rect,point:e.point};for(const s of t)s.onDecorationPointerLeave?.(i)}async changeResource(e){if(e===0)return!1;const t=this.pub.readingOrder.findIndexWithHref(this.currentLocation.href),r=Math.max(0,Math.min(this.pub.readingOrder.items.length-1,t+e));return r===t?!1:(this.currentIndex=r,this.currentLocation=this.createCurrentLocator(),await this.apply(),!0)}updateViewport(e){this.webViewport.readingOrder=[],this.webViewport.progressions.clear(),this.webViewport.positions=null,this.currentLocation&&(this.webViewport.readingOrder.push(this.currentLocation.href),this.webViewport.progressions.set(this.currentLocation.href,e),this.currentLocation.locations?.position!==void 0&&(this.webViewport.positions=[this.currentLocation.locations.position]))}async syncLocation(e){const t=e;this.currentLocation&&(this.currentLocation=this.currentLocation.copyWithLocations({progression:t.start})),this.updateViewport(t),this.listeners.positionChanged(this.currentLocation);const r=t.fragmentId?this.currentLocation.copyWithLocations({fragments:[`#${t.fragmentId}`]}):this.currentLocation;this._notifyTimelineChange(r),await this.framePool.update(this.pub,this.currentLocation,this.determineModules())}goBackward(e,t){if(this._isNavigating){t(!1);return}this._isNavigating=!0,this.changeResource(-1).then(r=>{this._isNavigating=!1,t(r)})}goForward(e,t){if(this._isNavigating){t(!1);return}this._isNavigating=!0,this.changeResource(1).then(r=>{this._isNavigating=!1,t(r)})}get currentLocator(){return this.currentLocation}get viewport(){return this.webViewport}get isScrollStart(){const e=this.viewport.readingOrder[0];return this.viewport.progressions.get(e)?.start===0}get isScrollEnd(){const e=this.viewport.readingOrder[this.viewport.readingOrder.length-1];return this.viewport.progressions.get(e)?.end===1}get canGoBackward(){const e=this.pub.readingOrder.items[0]?.href;return!(this.viewport.progressions.has(e)&&this.viewport.progressions.get(e)?.start===0)}get canGoForward(){const e=this.pub.readingOrder.items[this.pub.readingOrder.items.length-1]?.href;return!(this.viewport.progressions.has(e)&&this.viewport.progressions.get(e)?.end===1)}get readingProgression(){return this.pub.metadata.effectiveReadingProgression}get publication(){return this.pub}get timeline(){return this.pub.timeline}async loadLocator(e,t){let r=!1,i=y(e.locations);if(e.text?.highlight?r=await new Promise((a,o)=>{this.framePool.currentFrames[0].msg.send("go_text",i?[e.text?.serialize(),i]:e.text?.serialize(),c=>a(c))}):i&&(r=await new Promise((a,o)=>{this.framePool.currentFrames[0].msg.send("go_text",["",i],c=>a(c))})),r){t(r);return}const s=S(e.locations);if(s&&(r=await new Promise((a,o)=>{this.framePool.currentFrames[0].msg.send("go_id",s,c=>a(c))})),r){t(r);return}const n=e?.locations?.progression;n&&n>0?r=await new Promise((a,o)=>{this.framePool.currentFrames[0].msg.send("go_progression",n,c=>a(c))}):r=!0,t(r)}go(e,t,r){const i=e.href.split("#")[0];if(!this.pub.readingOrder.findWithHref(i))return r(this.listeners.handleLocator(e));const n=this.pub.readingOrder.findIndexWithHref(i);if(n>=0&&(this.currentIndex=n),this._isNavigating){r(!1);return}this._isNavigating=!0,this.currentLocation=this.createCurrentLocator(),this.apply().then(()=>this.loadLocator(e,h=>{this._isNavigating=!1,r(h)})).then(()=>{this.attachListener()})}goLink(e,t,r){return this.go(e.locator,t,r)}createCurrentLocator(){const t=this.pub.readingOrder.items[this.currentIndex];if(!t)throw new Error("No current resource available");const i=this.currentLocation&&this.currentLocation.href===t.href&&this.currentLocation.locations.progression?this.currentLocation.locations.progression:0;return this.pub.manifest.locatorFromLink(t)||new p({href:t.href,type:t.type||"text/html",locations:new k({fragments:[],progression:i,position:this.currentIndex+1})})}_notifyTimelineChange(e){const t=this.timeline.locate(e);t!==this._currentTimelineItem&&(this._currentTimelineItem=t,this.listeners.timelineItemChanged(t))}}const ae=j;export{ae as ExperimentalWebPubNavigator,j as WebPubNavigator};
|
|
1
|
+
import{Feature as w,Link as u,Locator as p,LocatorText as P,getCssSelector as y,getHtmlId as S,LocatorLocations as k}from"@readium/shared";import{VisualNavigator as D}from"../Navigator.js";import{WebPubFramePoolManager as x}from"./WebPubFramePoolManager.js";import{WebPubModules as C}from"@readium/navigator-html-injectables";import{p as g}from"../_virtual/index.js";import{getScriptMode as I}from"../helpers/scriptMode.js";import{WebPubCSS as E}from"./css/WebPubCSS.js";import{WebUserProperties as O,WebRSProperties as A}from"./css/Properties.js";import{WebPubPreferences as F}from"./preferences/WebPubPreferences.js";import{WebPubDefaults as T}from"./preferences/WebPubDefaults.js";import{WebPubSettings as _}from"./preferences/WebPubSettings.js";import{WebPubPreferencesEditor as v}from"./preferences/WebPubPreferencesEditor.js";import{Injector as W}from"../injection/Injector.js";import{createReadiumWebPubRules as H}from"../injection/webpubInjectables.js";import{NavigatorProtector as M,NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT as b}from"../protection/NavigatorProtector.js";import{KeyboardPeripherals as N,NAVIGATOR_KEYBOARD_PERIPHERAL_EVENT as L}from"../peripherals/KeyboardPeripherals.js";import{supportsDecorationStyle as R,decorationsEqual as V,resolveDecorationForWire as m}from"@readium/decorator";const z=h=>({frameLoaded:h.frameLoaded||(()=>{}),positionChanged:h.positionChanged||(()=>{}),timelineItemChanged:h.timelineItemChanged||(()=>{}),tap:h.tap||(()=>!1),click:h.click||(()=>!1),zoom:h.zoom||(()=>{}),scroll:h.scroll||(()=>{}),customEvent:h.customEvent||(()=>{}),handleLocator:h.handleLocator||(()=>!1),textSelected:h.textSelected||(()=>{}),contentProtection:h.contentProtection||(()=>{}),contextMenu:h.contextMenu||(()=>{}),peripheral:h.peripheral||(()=>{})});function j(h,e){return h.length===e.length&&h.every((t,i)=>t===e[i])}class B extends D{constructor(e,t,i,r=void 0,o={preferences:{},defaults:{}}){super(),this.currentIndex=0,this._visibleFragmentIds=[],this._notifiedVisibleFragmentIds=[],this._preferencesEditor=null,this._injector=null,this._isNavigating=!1,this._navigatorProtector=null,this._keyboardPeripheralsManager=null,this._suspiciousActivityListener=null,this._keyboardPeripheralListener=null,this._decorations=new Map,this._decorationObservers=new Map,this._decorationHoveredDecorations=new Map,this._decorationActivationState=new Map,this._decorationHoverState=new Map,this._decorationActivationConsumed=!1,this.webViewport={readingOrder:[],progressions:new Map,positions:null},this.pub=t,this.container=e,this.listeners=z(i),this._preferences=new F(o.preferences),this._defaults=new T(o.defaults),this._settings=new _(this._preferences,this._defaults,this.hasDisplayTransformability),this._css=new E({rsProperties:new A({experiments:this._settings.experiments||null}),userProperties:new O({zoom:this._settings.zoom})});const n=H(t.readingOrder.items),l=o.injectables||{rules:[],allowedDomains:[]};if(this._injector=new W({rules:[...n,...l.rules],allowedDomains:l.allowedDomains}),this._contentProtection=o.contentProtection||{},this._decoratorConfig=o.decoratorConfig||{},this._keyboardPeripherals=this.mergeKeyboardPeripherals(this._contentProtection,o.keyboardPeripherals||[]),(this._contentProtection.disableContextMenu||this._contentProtection.checkAutomation||this._contentProtection.checkIFrameEmbedding||this._contentProtection.monitorDevTools||this._contentProtection.protectPrinting?.disable)&&(this._navigatorProtector=new M(this._contentProtection),this._suspiciousActivityListener=a=>{const{type:s,...c}=a.detail;s==="context_menu"?this.listeners.contextMenu(c):this.listeners.contentProtection(s,c)},window.addEventListener(b,this._suspiciousActivityListener)),this._keyboardPeripherals.length>0&&(this._keyboardPeripheralsManager=new N({keyboardPeripherals:this._keyboardPeripherals}),this._keyboardPeripheralListener=a=>{const s=a.detail;this.listeners.peripheral(s)},window.addEventListener(L,this._keyboardPeripheralListener)),r&&typeof r.copyWithLocations=="function"){this.currentLocation=r;const a=this.pub.readingOrder.findIndexWithHref(r.href);a>=0&&(this.currentIndex=a)}else this.currentLocation=this.createCurrentLocator()}async load(){await this.updateCSS(!1);const e=this.compileCSSProperties(this._css);this.framePool=new x(this.container,e,this._injector,this._contentProtection,this._keyboardPeripherals,t=>this.pub.timeline.segmentsForHref(t).flatMap(i=>i.references).map(i=>{const r=i.indexOf("#");return r>=0?i.slice(r+1):""}).filter(Boolean)),await this.apply()}get settings(){return Object.freeze({...this._settings})}get preferencesEditor(){return this._preferencesEditor===null&&(this._preferencesEditor=new v(this._preferences,this.settings,this.pub.metadata)),this._preferencesEditor}async submitPreferences(e){this._preferences=this._preferences.merging(e),await this.applyPreferences()}async applyPreferences(){this._settings=new _(this._preferences,this._defaults,this.hasDisplayTransformability),this._preferencesEditor!==null&&(this._preferencesEditor=new v(this._preferences,this.settings,this.pub.metadata)),await this.updateCSS(!0)}async updateCSS(e){this._css.update(this._settings),e&&await this.commitCSS(this._css)}compileCSSProperties(e){const t={};for(const[i,r]of Object.entries(e.rsProperties.toCSSProperties()))t[i]=r;for(const[i,r]of Object.entries(e.userProperties.toCSSProperties()))t[i]=r;return t}async commitCSS(e){const t=this.compileCSSProperties(e);this.framePool.setCSSProperties(t)}get _cframes(){return this.framePool.currentFrames}get hasDisplayTransformability(){return this.pub.metadata?.accessibility?.feature?.some(e=>e.value===w.DISPLAY_TRANSFORMABILITY.value)??!1}eventListener(e,t){switch(e){case"_pong":this.listeners.frameLoaded(this.framePool.currentFrames[0].iframe.contentWindow),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation),this._reapplyDecorationsToCurrentFrame();break;case"first_visible_locator":const i=p.deserialize(t);if(!i)break;this.currentLocation=new p({href:this.currentLocation.href,type:this.currentLocation.type,title:this.currentLocation.title,locations:i?.locations,text:i?.text}),this.listeners.positionChanged(this.currentLocation),this._notifyTimelineChange(this.currentLocation);break;case"text_selected":{const a=t;a.locator=new p({href:this.currentLocation.href,type:this.currentLocation.type,text:new P({highlight:a.text})}),this.listeners.textSelected(a);break}case"decoration_activated":{this._handleDecorationActivated(t)&&(this._decorationActivationConsumed=!0);break}case"decoration_pointer_enter":this._handleDecorationPointerEnter(t);break;case"decoration_pointer_leave":this._handleDecorationPointerLeave(t);break;case"click":case"tap":if(this._decorationActivationConsumed){this._decorationActivationConsumed=!1;break}const r=t;if(r.interactiveElement){const a=new DOMParser().parseFromString(r.interactiveElement,"text/html").body.children[0];if(a.nodeType===a.ELEMENT_NODE&&a.nodeName==="A"&&a.hasAttribute("href")){const s=a.attributes.getNamedItem("href")?.value;if(s.startsWith("#"))this.go(this.currentLocation.copyWithLocations({fragments:[s.substring(1)]}),!1,()=>{});else if(s.startsWith("mailto:")||s.startsWith("tel:"))this.listeners.handleLocator(new u({href:s}).locator);else try{let c;if(s.startsWith("http://")||s.startsWith("https://"))c=s;else if(this.currentLocation.href.startsWith("http://")||this.currentLocation.href.startsWith("https://")){const f=new URL(this.currentLocation.href);c=new URL(s,f).href}else c=g.join(g.dirname(this.currentLocation.href),s);const d=this.pub.readingOrder.findWithHref(c);d?this.goLink(d,!1,()=>{}):(console.warn(`Internal link not found in readingOrder: ${c}`),this.listeners.handleLocator(new u({href:s}).locator))}catch(c){console.warn(`Couldn't resolve internal link for ${s}: ${c}`),this.listeners.handleLocator(new u({href:s}).locator)}}else console.log("Clicked on",a)}else if(e==="click"?this.listeners.click(r):this.listeners.tap(r))break;break;case"scroll":this.listeners.scroll(t);break;case"zoom":this.listeners.zoom(t);break;case"progress":this.syncLocation(t);break;case"content_protection":const o=t;this.listeners.contentProtection(o.type,o);break;case"context_menu":this.listeners.contextMenu(t);break;case"keyboard_peripherals":const n=t,l={...n,interactiveElement:void 0};n.interactiveElement&&(l.interactiveElement=new DOMParser().parseFromString(n.interactiveElement,"text/html").body.children[0]),this.listeners.peripheral(l);break;case"log":console.log(this.framePool.currentFrames[0]?.source?.split("/")[3],...t);break;default:this.listeners.customEvent(e,t);break}}determineModules(){const e=C.slice(),t=I(this.pub.metadata);return t==="cjk-vertical"||t==="mongolian-vertical"?e.map(i=>i==="webpub_snapper"?"cjk_vertical_snapper":i):e}attachListener(){this.framePool.currentFrames[0]?.msg&&(this.framePool.currentFrames[0].msg.listener=(e,t)=>{this.eventListener(e,t)}),this._reapplyDecorationsToCurrentFrame()}async apply(){if(await this.framePool.update(this.pub,this.currentLocation,this.determineModules()),this.attachListener(),this.pub.readingOrder.findIndexWithHref(this.currentLocation.href)<0)throw Error("Link for "+this.currentLocation.href+" not found!")}async destroy(){this._suspiciousActivityListener&&window.removeEventListener(b,this._suspiciousActivityListener),this._keyboardPeripheralListener&&window.removeEventListener(L,this._keyboardPeripheralListener),this._navigatorProtector?.destroy(),this._keyboardPeripheralsManager?.destroy(),await this.framePool?.destroy(),this._decorations.clear(),this._decorationObservers.clear(),this._decorationHoveredDecorations.clear(),this._decorationActivationState.clear(),this._decorationHoverState.clear()}supportsDecorationStyle(e){return R(e,this._decoratorConfig.decorationTemplates)}registerDecorationObserver(e,t){this._decorationObservers.has(e)||this._decorationObservers.set(e,new Set),this._decorationObservers.get(e).add(t),t.onDecorationActivated&&(this._decorationActivationState.set(e,!0),this._sendDecorationActivatable(e,!0)),(t.onDecorationPointerEnter||t.onDecorationPointerLeave)&&(this._decorationHoverState.set(e,!0),this._sendDecorationHoverable(e,!0))}unregisterDecorationObserver(e){this._decorationObservers.forEach((t,i)=>{if(!t.has(e))return;t.delete(e);const r=[...t].some(n=>n.onDecorationActivated);this._decorationActivationState.has(i)&&!r&&(this._decorationActivationState.delete(i),this._sendDecorationActivatable(i,!1));const o=[...t].some(n=>n.onDecorationPointerEnter||n.onDecorationPointerLeave);this._decorationHoverState.has(i)&&!o&&(this._decorationHoverState.delete(i),this._sendDecorationHoverable(i,!1))})}_sendDecorationActivatable(e,t){const i=this.framePool?.currentFrames[0];i?.msg&&i.msg.send("decoration_activatable",{group:e,activatable:t})}_sendDecorationHoverable(e,t){const i=this.framePool?.currentFrames[0];i?.msg&&i.msg.send("decoration_hoverable",{group:e,hoverable:t})}applyDecorations(e,t){const i=this._decorations.get(t)??[],r=new Map(i.map(d=>[d.id,d])),o=new Map(e.map(d=>[d.id,d])),n=[],l=[],a=[];for(const[d,f]of r)o.has(d)?V(f,o.get(d))||a.push(o.get(d)):n.push(d);for(const[d,f]of o)r.has(d)||l.push(f);this._decorations.set(t,e),this._sendDecorationOps(t,n,l,a,i);const s=this._decorationActivationState.get(t);s!==void 0&&this._sendDecorationActivatable(t,s);const c=this._decorationHoverState.get(t);c!==void 0&&this._sendDecorationHoverable(t,c)}_sendDecorationOps(e,t,i,r,o){const n=this.framePool?.currentFrames[0];if(!n?.msg)return;const l=this.currentLocation.href,a=new Map(o.map(s=>[s.id,s]));for(const s of t){const c=a.get(s);!c||c.locator.href!==l||n.msg.send("decorate",{group:e,action:"remove",decoration:{id:s}})}for(const s of i)s.locator.href===l&&n.msg.send("decorate",{group:e,action:"add",decoration:m(s,this._decoratorConfig.decorationTemplates)});for(const s of r)s.locator.href===l&&n.msg.send("decorate",{group:e,action:"update",decoration:m(s,this._decoratorConfig.decorationTemplates)})}_reapplyDecorationsToCurrentFrame(){const e=this.framePool?.currentFrames[0];if(!e?.msg)return;const t=this.currentLocation.href;for(const[i,r]of this._decorations){const o=r.filter(n=>n.locator.href===t);if(o.length!==0){e.msg.send("decorate",{group:i,action:"clear"});for(const n of o)e.msg.send("decorate",{group:i,action:"add",decoration:m(n,this._decoratorConfig.decorationTemplates)})}}for(const[i,r]of this._decorationActivationState)e.msg.send("decoration_activatable",{group:i,activatable:r});for(const[i,r]of this._decorationHoverState)e.msg.send("decoration_hoverable",{group:i,hoverable:r})}_handleDecorationActivated(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return!1;const i=(this._decorations.get(e.group)??[]).find(n=>n.id===e.decorationId);if(!i)return!1;const r={decoration:i,group:e.group,rect:e.rect,point:e.point};let o=!1;for(const n of t)n.onDecorationActivated?.(r)&&(o=!0);return o}_handleDecorationPointerEnter(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return;const i=(this._decorations.get(e.group)??[]).find(o=>o.id===e.decorationId);if(!i)return;this._decorationHoveredDecorations.set(e.group,i);const r={decoration:i,group:e.group,rect:e.rect,point:e.point};for(const o of t)o.onDecorationPointerEnter?.(r)}_handleDecorationPointerLeave(e){const t=this._decorationObservers.get(e.group);if(!t||t.size===0)return;const i=(this._decorations.get(e.group)??[]).find(o=>o.id===e.decorationId)??this._decorationHoveredDecorations.get(e.group);if(this._decorationHoveredDecorations.delete(e.group),!i)return;const r={decoration:i,group:e.group,rect:e.rect,point:e.point};for(const o of t)o.onDecorationPointerLeave?.(r)}async changeResource(e){if(e===0)return!1;const t=this.pub.readingOrder.findIndexWithHref(this.currentLocation.href),i=Math.max(0,Math.min(this.pub.readingOrder.items.length-1,t+e));return i===t?!1:(this.currentIndex=i,this.currentLocation=this.createCurrentLocator(),await this.apply(),!0)}updateViewport(e){this.webViewport.readingOrder=[],this.webViewport.progressions.clear(),this.webViewport.positions=null,this.currentLocation&&(this.webViewport.readingOrder.push(this.currentLocation.href),this.webViewport.progressions.set(this.currentLocation.href,e),this.currentLocation.locations?.position!==void 0&&(this.webViewport.positions=[this.currentLocation.locations.position]))}async syncLocation(e){const t=e;this.currentLocation&&(this.currentLocation=this.currentLocation.copyWithLocations({progression:t.start})),this.updateViewport(t),this.listeners.positionChanged(this.currentLocation);const i=t.fragmentId?this.currentLocation.copyWithLocations({fragments:[`#${t.fragmentId}`]}):this.currentLocation;this._visibleFragmentIds=t.visibleFragmentIds??[],this._notifyTimelineChange(i),await this.framePool.update(this.pub,this.currentLocation,this.determineModules())}goBackward(e,t){if(this._isNavigating){t(!1);return}this._isNavigating=!0,this.changeResource(-1).then(i=>{this._isNavigating=!1,t(i)})}goForward(e,t){if(this._isNavigating){t(!1);return}this._isNavigating=!0,this.changeResource(1).then(i=>{this._isNavigating=!1,t(i)})}get currentLocator(){return this.currentLocation}get viewport(){return this.webViewport}get isScrollStart(){const e=this.viewport.readingOrder[0];return this.viewport.progressions.get(e)?.start===0}get isScrollEnd(){const e=this.viewport.readingOrder[this.viewport.readingOrder.length-1];return this.viewport.progressions.get(e)?.end===1}get canGoBackward(){const e=this.pub.readingOrder.items[0]?.href;return!(this.viewport.progressions.has(e)&&this.viewport.progressions.get(e)?.start===0)}get canGoForward(){const e=this.pub.readingOrder.items[this.pub.readingOrder.items.length-1]?.href;return!(this.viewport.progressions.has(e)&&this.viewport.progressions.get(e)?.end===1)}get readingProgression(){return this.pub.metadata.effectiveReadingProgression}get publication(){return this.pub}get timeline(){const e=this.pub.timeline;return this._wrappedTimeline||(this._wrappedTimeline=new Proxy(e,{get:(t,i)=>{if(i!=="navigableFrom"){const r=Reflect.get(t,i,t);return typeof r=="function"?r.bind(t):r}return r=>{const o=this._visibleFragmentIds;let n=(o.length?t.locate(this.currentLocation.copyWithLocations({fragments:[`#${o[0]}`]})):void 0)??r;const l=(o.length?t.locate(this.currentLocation.copyWithLocations({fragments:[`#${o[o.length-1]}`]})):void 0)??r;if(this.isScrollStart){const c=t.ancestors(n).find(d=>d.references.includes(this.currentLocation.href));c&&(n=c)}const a=t.navigableFrom(n).previous,s=t.navigableFrom(l).next;return{previous:a,next:s}}}})),this._wrappedTimeline}async loadLocator(e,t){let i=!1,r=y(e.locations);if(e.text?.highlight?i=await new Promise((a,s)=>{this.framePool.currentFrames[0].msg.send("go_text",r?[e.text?.serialize(),r]:e.text?.serialize(),c=>a(c))}):r&&(i=await new Promise((a,s)=>{this.framePool.currentFrames[0].msg.send("go_text",["",r],c=>a(c))})),i){t(i);return}const o=S(e.locations);if(o&&(i=await new Promise((a,s)=>{this.framePool.currentFrames[0].msg.send("go_id",o,c=>a(c))})),i){t(i);return}const n=e?.locations?.progression;n&&n>0?i=await new Promise((a,s)=>{this.framePool.currentFrames[0].msg.send("go_progression",n,c=>a(c))}):i=!0,t(i)}go(e,t,i){const r=e.href.split("#")[0];if(!this.pub.readingOrder.findWithHref(r))return i(this.listeners.handleLocator(e));const n=this.pub.readingOrder.findIndexWithHref(r);if(n>=0&&(this.currentIndex=n),this._isNavigating){i(!1);return}this._isNavigating=!0,this.currentLocation=this.createCurrentLocator(),this.apply().then(()=>this.loadLocator(e,l=>{this._isNavigating=!1,i(l)})).then(()=>{this.attachListener()})}goLink(e,t,i){return this.go(e.locator,t,i)}createCurrentLocator(){const t=this.pub.readingOrder.items[this.currentIndex];if(!t)throw new Error("No current resource available");const r=this.currentLocation&&this.currentLocation.href===t.href&&this.currentLocation.locations.progression?this.currentLocation.locations.progression:0;return this.pub.manifest.locatorFromLink(t)||new p({href:t.href,type:t.type||"text/html",locations:new k({fragments:[],progression:r,position:this.currentIndex+1})})}_notifyTimelineChange(e){const t=this.timeline.locate(e),i=!j(this._visibleFragmentIds,this._notifiedVisibleFragmentIds);(t!==this._currentTimelineItem||i)&&(this._currentTimelineItem=t,this._notifiedVisibleFragmentIds=this._visibleFragmentIds,this.listeners.timelineItemChanged(t))}}const ce=B;export{ce as ExperimentalWebPubNavigator,B as WebPubNavigator};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@readium/navigator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Next generation SDK for publications in Web Apps",
|
|
6
6
|
"author": "readium",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"node": ">=18"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@readium/shared": "2.3.1",
|
|
48
|
-
"@readium/helpers": "1.0.0",
|
|
49
47
|
"@readium/decorator": "1.0.1",
|
|
50
|
-
"@readium/
|
|
48
|
+
"@readium/helpers": "1.0.0",
|
|
49
|
+
"@readium/navigator-html-injectables": "2.6.3",
|
|
50
|
+
"@readium/shared": "2.4.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@readium/css": "^2.0.5",
|
package/src/Navigator.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface ProgressionRange {
|
|
|
25
25
|
start: number;
|
|
26
26
|
end: number;
|
|
27
27
|
fragmentId?: string; // DOM element ID of the currently active timeline anchor
|
|
28
|
+
visibleFragmentIds?: string[]; // DOM-order IDs of every timeline anchor currently on screen
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export interface VisualNavigatorViewport {
|
|
@@ -68,6 +68,10 @@ const defaultListeners = (listeners: EpubNavigatorListeners): EpubNavigatorListe
|
|
|
68
68
|
peripheral: listeners.peripheral || (() => {}),
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
function sameIds(a: string[], b: string[]): boolean {
|
|
72
|
+
return a.length === b.length && a.every((id, i) => id === b[i]);
|
|
73
|
+
}
|
|
74
|
+
|
|
71
75
|
export class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, EpubPreferences>, DecorableNavigator {
|
|
72
76
|
private readonly pub: Publication;
|
|
73
77
|
private readonly container: HTMLElement;
|
|
@@ -76,7 +80,10 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
76
80
|
private positions!: Locator[];
|
|
77
81
|
private currentLocation!: Locator;
|
|
78
82
|
private _currentTimelineItem: TimelineItem | undefined;
|
|
83
|
+
private _visibleFragmentIds: string[] = [];
|
|
84
|
+
private _notifiedVisibleFragmentIds: string[] = [];
|
|
79
85
|
private _timelineAugmented = false;
|
|
86
|
+
private _wrappedTimeline: Timeline | undefined;
|
|
80
87
|
private lastLocationInView: Locator | undefined;
|
|
81
88
|
private currentProgression: ReadingProgression;
|
|
82
89
|
private _layout: Layout;
|
|
@@ -1006,6 +1013,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
1006
1013
|
const locatorForTimeline = progression.fragmentId
|
|
1007
1014
|
? this.currentLocation.copyWithLocations({ fragments: [`#${progression.fragmentId}`] })
|
|
1008
1015
|
: this.currentLocation;
|
|
1016
|
+
this._visibleFragmentIds = progression.visibleFragmentIds ?? [];
|
|
1009
1017
|
this._notifyTimelineChange(locatorForTimeline);
|
|
1010
1018
|
await this.framePool.update(this.pub, this.currentLocation, this.determineModules());
|
|
1011
1019
|
}
|
|
@@ -1149,9 +1157,50 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
1149
1157
|
scroll: atFragment?.locations.progression,
|
|
1150
1158
|
};
|
|
1151
1159
|
});
|
|
1160
|
+
|
|
1152
1161
|
this._timelineAugmented = true;
|
|
1153
1162
|
}
|
|
1154
|
-
|
|
1163
|
+
|
|
1164
|
+
if (!this._wrappedTimeline) {
|
|
1165
|
+
// Wraps the real, shared Timeline rather than mutating it — publication.timeline
|
|
1166
|
+
// stays plain for every consumer; only navigator.timeline answers navigableFrom()
|
|
1167
|
+
// with visible-range-aware previous/next. Every other member (locate, ancestors,
|
|
1168
|
+
// augment, etc.) passes straight through to the real instance untouched.
|
|
1169
|
+
this._wrappedTimeline = new Proxy(t, {
|
|
1170
|
+
get: (target, prop) => {
|
|
1171
|
+
if (prop !== 'navigableFrom') {
|
|
1172
|
+
// Resolve strictly against `target`, never the proxy: Timeline's own
|
|
1173
|
+
// getters (flat, items) lazily cache onto `this` on first access, and
|
|
1174
|
+
// forwarding the proxy as receiver would make that caching target the
|
|
1175
|
+
// wrong object instead of the real Timeline.
|
|
1176
|
+
const value = Reflect.get(target, prop, target);
|
|
1177
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
1178
|
+
}
|
|
1179
|
+
return (item: TimelineItem) => {
|
|
1180
|
+
// Fragments on screen are always contiguous in the flattened timeline,
|
|
1181
|
+
// so anchor previous/next on the two ends of the visible run instead of
|
|
1182
|
+
// on `item` itself.
|
|
1183
|
+
const ids = this._visibleFragmentIds;
|
|
1184
|
+
let first = (ids.length ? target.locate(this.currentLocation.copyWithLocations({ fragments: [`#${ids[0]}`] })) : undefined) ?? item;
|
|
1185
|
+
const last = (ids.length ? target.locate(this.currentLocation.copyWithLocations({ fragments: [`#${ids[ids.length - 1]}`] })) : undefined) ?? item;
|
|
1186
|
+
// The current resource's own bare-href container(s) have no DOM anchor,
|
|
1187
|
+
// so they never appear among visible fragment ids. Only when scrollTop is
|
|
1188
|
+
// actually 0 (untracked content can precede the first fragment, and
|
|
1189
|
+
// scrolling past it must leave "back to resource start" reachable) walk
|
|
1190
|
+
// out to the outermost ancestor still within this resource, so previous
|
|
1191
|
+
// is anchored past all of them at once rather than one guessed step back.
|
|
1192
|
+
if (this.isScrollStart) {
|
|
1193
|
+
const outermost = target.ancestors(first).find(a => a.references.includes(this.currentLocation.href));
|
|
1194
|
+
if (outermost) first = outermost;
|
|
1195
|
+
}
|
|
1196
|
+
const previous = target.navigableFrom(first).previous;
|
|
1197
|
+
const next = target.navigableFrom(last).next;
|
|
1198
|
+
return { previous, next };
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return this._wrappedTimeline;
|
|
1155
1204
|
}
|
|
1156
1205
|
|
|
1157
1206
|
private async loadLocator(locator: Locator, cb: (ok: boolean) => void) {
|
|
@@ -1237,8 +1286,10 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
|
|
|
1237
1286
|
|
|
1238
1287
|
private _notifyTimelineChange(locator: Locator): void {
|
|
1239
1288
|
const item = this.timeline.locate(locator);
|
|
1240
|
-
|
|
1289
|
+
const visibleChanged = !sameIds(this._visibleFragmentIds, this._notifiedVisibleFragmentIds);
|
|
1290
|
+
if (item !== this._currentTimelineItem || visibleChanged) {
|
|
1241
1291
|
this._currentTimelineItem = item;
|
|
1292
|
+
this._notifiedVisibleFragmentIds = this._visibleFragmentIds;
|
|
1242
1293
|
this.listeners.timelineItemChanged(item);
|
|
1243
1294
|
}
|
|
1244
1295
|
}
|
|
@@ -62,6 +62,10 @@ const defaultListeners = (listeners: WebPubNavigatorListeners): WebPubNavigatorL
|
|
|
62
62
|
peripheral: listeners.peripheral || (() => {})
|
|
63
63
|
})
|
|
64
64
|
|
|
65
|
+
function sameIds(a: string[], b: string[]): boolean {
|
|
66
|
+
return a.length === b.length && a.every((id, i) => id === b[i]);
|
|
67
|
+
}
|
|
68
|
+
|
|
65
69
|
export class WebPubNavigator extends VisualNavigator implements Configurable<WebPubSettings, WebPubPreferences>, DecorableNavigator {
|
|
66
70
|
private readonly pub: Publication;
|
|
67
71
|
private readonly container: HTMLElement;
|
|
@@ -70,6 +74,9 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
|
|
|
70
74
|
private currentIndex: number = 0;
|
|
71
75
|
private currentLocation: Locator;
|
|
72
76
|
private _currentTimelineItem: TimelineItem | undefined;
|
|
77
|
+
private _visibleFragmentIds: string[] = [];
|
|
78
|
+
private _notifiedVisibleFragmentIds: string[] = [];
|
|
79
|
+
private _wrappedTimeline: Timeline | undefined;
|
|
73
80
|
|
|
74
81
|
private _preferences: WebPubPreferences;
|
|
75
82
|
private _defaults: WebPubDefaults;
|
|
@@ -672,6 +679,7 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
|
|
|
672
679
|
const locatorForTimeline = progression.fragmentId
|
|
673
680
|
? this.currentLocation.copyWithLocations({ fragments: [`#${progression.fragmentId}`] })
|
|
674
681
|
: this.currentLocation;
|
|
682
|
+
this._visibleFragmentIds = progression.visibleFragmentIds ?? [];
|
|
675
683
|
this._notifyTimelineChange(locatorForTimeline);
|
|
676
684
|
await this.framePool.update(this.pub, this.currentLocation, this.determineModules());
|
|
677
685
|
}
|
|
@@ -733,7 +741,47 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
|
|
|
733
741
|
}
|
|
734
742
|
|
|
735
743
|
get timeline(): Timeline {
|
|
736
|
-
|
|
744
|
+
const t = this.pub.timeline;
|
|
745
|
+
if (!this._wrappedTimeline) {
|
|
746
|
+
// Wraps the real, shared Timeline rather than mutating it — publication.timeline
|
|
747
|
+
// stays plain for every consumer; only navigator.timeline answers navigableFrom()
|
|
748
|
+
// with visible-range-aware previous/next. Every other member (locate, ancestors,
|
|
749
|
+
// augment, etc.) passes straight through to the real instance untouched.
|
|
750
|
+
this._wrappedTimeline = new Proxy(t, {
|
|
751
|
+
get: (target, prop) => {
|
|
752
|
+
if (prop !== 'navigableFrom') {
|
|
753
|
+
// Resolve strictly against `target`, never the proxy: Timeline's own
|
|
754
|
+
// getters (flat, items) lazily cache onto `this` on first access, and
|
|
755
|
+
// forwarding the proxy as receiver would make that caching target the
|
|
756
|
+
// wrong object instead of the real Timeline.
|
|
757
|
+
const value = Reflect.get(target, prop, target);
|
|
758
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
759
|
+
}
|
|
760
|
+
return (item: TimelineItem) => {
|
|
761
|
+
// Fragments on screen are always contiguous in the flattened timeline,
|
|
762
|
+
// so anchor previous/next on the two ends of the visible run instead of
|
|
763
|
+
// on `item` itself.
|
|
764
|
+
const ids = this._visibleFragmentIds;
|
|
765
|
+
let first = (ids.length ? target.locate(this.currentLocation.copyWithLocations({ fragments: [`#${ids[0]}`] })) : undefined) ?? item;
|
|
766
|
+
const last = (ids.length ? target.locate(this.currentLocation.copyWithLocations({ fragments: [`#${ids[ids.length - 1]}`] })) : undefined) ?? item;
|
|
767
|
+
// The current resource's own bare-href container(s) have no DOM anchor,
|
|
768
|
+
// so they never appear among visible fragment ids. Only when scrollTop is
|
|
769
|
+
// actually 0 (untracked content can precede the first fragment, and
|
|
770
|
+
// scrolling past it must leave "back to resource start" reachable) walk
|
|
771
|
+
// out to the outermost ancestor still within this resource, so previous
|
|
772
|
+
// is anchored past all of them at once rather than one guessed step back.
|
|
773
|
+
if (this.isScrollStart) {
|
|
774
|
+
const outermost = target.ancestors(first).find(a => a.references.includes(this.currentLocation.href));
|
|
775
|
+
if (outermost) first = outermost;
|
|
776
|
+
}
|
|
777
|
+
const previous = target.navigableFrom(first).previous;
|
|
778
|
+
const next = target.navigableFrom(last).next;
|
|
779
|
+
return { previous, next };
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
return this._wrappedTimeline;
|
|
737
785
|
}
|
|
738
786
|
|
|
739
787
|
private async loadLocator(locator: Locator, cb: (ok: boolean) => void) {
|
|
@@ -854,8 +902,10 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
|
|
|
854
902
|
|
|
855
903
|
private _notifyTimelineChange(locator: Locator): void {
|
|
856
904
|
const item = this.timeline.locate(locator);
|
|
857
|
-
|
|
905
|
+
const visibleChanged = !sameIds(this._visibleFragmentIds, this._notifiedVisibleFragmentIds);
|
|
906
|
+
if (item !== this._currentTimelineItem || visibleChanged) {
|
|
858
907
|
this._currentTimelineItem = item;
|
|
908
|
+
this._notifiedVisibleFragmentIds = this._visibleFragmentIds;
|
|
859
909
|
this.listeners.timelineItemChanged(item);
|
|
860
910
|
}
|
|
861
911
|
}
|
package/types/src/Navigator.d.ts
CHANGED
|
@@ -45,7 +45,10 @@ export declare class EpubNavigator extends VisualNavigator implements Configurab
|
|
|
45
45
|
private positions;
|
|
46
46
|
private currentLocation;
|
|
47
47
|
private _currentTimelineItem;
|
|
48
|
+
private _visibleFragmentIds;
|
|
49
|
+
private _notifiedVisibleFragmentIds;
|
|
48
50
|
private _timelineAugmented;
|
|
51
|
+
private _wrappedTimeline;
|
|
49
52
|
private lastLocationInView;
|
|
50
53
|
private currentProgression;
|
|
51
54
|
private _layout;
|
|
@@ -42,6 +42,9 @@ export declare class WebPubNavigator extends VisualNavigator implements Configur
|
|
|
42
42
|
private currentIndex;
|
|
43
43
|
private currentLocation;
|
|
44
44
|
private _currentTimelineItem;
|
|
45
|
+
private _visibleFragmentIds;
|
|
46
|
+
private _notifiedVisibleFragmentIds;
|
|
47
|
+
private _wrappedTimeline;
|
|
45
48
|
private _preferences;
|
|
46
49
|
private _defaults;
|
|
47
50
|
private _settings;
|