@internetarchive/bookreader 5.0.0-12-fixsorting-7 → 5.0.0-120

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.
Files changed (420) hide show
  1. package/BookReader/474.js +2 -0
  2. package/BookReader/474.js.map +1 -0
  3. package/BookReader/BookReader.css +670 -1190
  4. package/BookReader/bergamot-translator-worker.js +2966 -0
  5. package/BookReader/bergamot-translator-worker.wasm +0 -0
  6. package/BookReader/hypothesis/LICENSE +50 -0
  7. package/BookReader/hypothesis/README.md +55 -0
  8. package/BookReader/hypothesis/build/boot-template.js +1 -0
  9. package/BookReader/hypothesis/build/boot.js +1 -0
  10. package/BookReader/hypothesis/build/manifest.json +20 -0
  11. package/BookReader/hypothesis/build/scripts/annotator.bundle.js +199 -0
  12. package/BookReader/hypothesis/build/scripts/annotator.bundle.js.map +1 -0
  13. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js +811 -0
  14. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js.map +1 -0
  15. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js +437 -0
  16. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js.map +1 -0
  17. package/BookReader/hypothesis/build/styles/annotator.css +4 -0
  18. package/BookReader/hypothesis/build/styles/annotator.css.map +1 -0
  19. package/BookReader/hypothesis/build/styles/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  20. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  21. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  22. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  23. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  24. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Bold.woff2 +0 -0
  25. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  26. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Italic.woff2 +0 -0
  27. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Regular.woff2 +0 -0
  28. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  29. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-Italic.woff2 +0 -0
  30. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  31. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  32. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  33. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Script-Regular.woff2 +0 -0
  34. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  35. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  36. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  37. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  38. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  39. package/BookReader/hypothesis/build/styles/highlights.css +2 -0
  40. package/BookReader/hypothesis/build/styles/highlights.css.map +1 -0
  41. package/BookReader/hypothesis/build/styles/katex.min.css +3 -0
  42. package/BookReader/hypothesis/build/styles/katex.min.css.map +1 -0
  43. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css +14 -0
  44. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css.map +1 -0
  45. package/BookReader/hypothesis/build/styles/sidebar.css +4 -0
  46. package/BookReader/hypothesis/build/styles/sidebar.css.map +1 -0
  47. package/BookReader/hypothesis/build/styles/ui-playground.css +4 -0
  48. package/BookReader/hypothesis/build/styles/ui-playground.css.map +1 -0
  49. package/BookReader/hypothesis/package.json +126 -0
  50. package/BookReader/ia-bookreader.js +1886 -0
  51. package/BookReader/{BookReader.js.LICENSE.txt → ia-bookreader.js.LICENSE.txt} +26 -20
  52. package/BookReader/ia-bookreader.js.map +1 -0
  53. package/BookReader/icons/1up.svg +1 -12
  54. package/BookReader/icons/2up.svg +1 -15
  55. package/BookReader/icons/advance.svg +3 -26
  56. package/BookReader/icons/chevron-right.svg +1 -1
  57. package/BookReader/icons/close-circle-dark.svg +1 -1
  58. package/BookReader/icons/close-circle.svg +1 -1
  59. package/BookReader/icons/fullscreen.svg +1 -17
  60. package/BookReader/icons/fullscreen_exit.svg +1 -17
  61. package/BookReader/icons/hamburger.svg +1 -15
  62. package/BookReader/icons/left-arrow.svg +1 -12
  63. package/BookReader/icons/magnify-minus.svg +1 -16
  64. package/BookReader/icons/magnify-plus.svg +1 -17
  65. package/BookReader/icons/magnify.svg +1 -15
  66. package/BookReader/icons/pause.svg +1 -23
  67. package/BookReader/icons/play.svg +1 -22
  68. package/BookReader/icons/playback-speed.svg +1 -34
  69. package/BookReader/icons/read-aloud.svg +1 -22
  70. package/BookReader/icons/review.svg +3 -22
  71. package/BookReader/icons/slider-toggle.svg +1 -0
  72. package/BookReader/icons/thumbnails.svg +1 -17
  73. package/BookReader/icons/voice.svg +1 -0
  74. package/BookReader/icons/volume-full.svg +1 -22
  75. package/BookReader/images/BRicons.svg +5 -94
  76. package/BookReader/images/books_graphic.svg +1 -177
  77. package/BookReader/images/hypothesis.ico +0 -0
  78. package/BookReader/images/icon_book.svg +1 -12
  79. package/BookReader/images/icon_bookmark.svg +1 -12
  80. package/BookReader/images/icon_experiment.svg +1 -0
  81. package/BookReader/images/icon_gear.svg +1 -14
  82. package/BookReader/images/icon_hamburger.svg +1 -20
  83. package/BookReader/images/icon_home.svg +1 -21
  84. package/BookReader/images/icon_info.svg +1 -11
  85. package/BookReader/images/icon_one_page.svg +1 -8
  86. package/BookReader/images/icon_pause.svg +1 -1
  87. package/BookReader/images/icon_play.svg +1 -1
  88. package/BookReader/images/icon_playback-rate.svg +1 -15
  89. package/BookReader/images/icon_search_button.svg +1 -8
  90. package/BookReader/images/icon_share.svg +1 -9
  91. package/BookReader/images/icon_skip-ahead.svg +1 -6
  92. package/BookReader/images/icon_skip-back.svg +2 -13
  93. package/BookReader/images/icon_speaker.svg +1 -18
  94. package/BookReader/images/icon_speaker_open.svg +1 -10
  95. package/BookReader/images/icon_thumbnails.svg +1 -12
  96. package/BookReader/images/icon_toc.svg +1 -5
  97. package/BookReader/images/icon_two_pages.svg +1 -9
  98. package/BookReader/images/marker_chap-off.svg +1 -11
  99. package/BookReader/images/marker_chap-on.svg +1 -11
  100. package/BookReader/images/marker_srch-on.svg +1 -11
  101. package/BookReader/images/translate.svg +1 -0
  102. package/BookReader/images/unviewable_page.png +0 -0
  103. package/BookReader/jquery-3.js +2 -0
  104. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  105. package/BookReader/plugins/plugin.archive_analytics.js +1 -172
  106. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  107. package/BookReader/plugins/plugin.autoplay.js +1 -165
  108. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  109. package/BookReader/plugins/plugin.chapters.js +19 -301
  110. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  111. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  112. package/BookReader/plugins/plugin.experiments.js +3 -0
  113. package/BookReader/plugins/plugin.experiments.js.LICENSE.txt +1 -0
  114. package/BookReader/plugins/plugin.experiments.js.map +1 -0
  115. package/BookReader/plugins/plugin.iframe.js +1 -74
  116. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  117. package/BookReader/plugins/plugin.iiif.js +2 -0
  118. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  119. package/BookReader/plugins/plugin.resume.js +1 -368
  120. package/BookReader/plugins/plugin.resume.js.map +1 -1
  121. package/BookReader/plugins/plugin.search.js +2 -1385
  122. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  123. package/BookReader/plugins/plugin.search.js.map +1 -1
  124. package/BookReader/plugins/plugin.text_selection.js +2 -1080
  125. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  126. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  127. package/BookReader/plugins/plugin.translate.js +3 -0
  128. package/BookReader/plugins/plugin.translate.js.LICENSE.txt +1 -0
  129. package/BookReader/plugins/plugin.translate.js.map +1 -0
  130. package/BookReader/plugins/plugin.tts.js +2 -9193
  131. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  132. package/BookReader/plugins/plugin.tts.js.map +1 -1
  133. package/BookReader/plugins/plugin.url.js +1 -269
  134. package/BookReader/plugins/plugin.url.js.map +1 -1
  135. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -379
  136. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  137. package/BookReader/plugins/translator-worker.js +2 -0
  138. package/BookReader/plugins/translator-worker.js.map +1 -0
  139. package/BookReader/silence.mp3 +0 -0
  140. package/BookReader/translator-worker.js +475 -0
  141. package/BookReader/webcomponents-bundle.js +3 -0
  142. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  143. package/BookReader/webcomponents-bundle.js.map +1 -0
  144. package/README.md +17 -4
  145. package/dist/esm/ia-bookreader.js +3 -0
  146. package/dist/esm/ia-bookreader.js.LICENSE.txt +17 -0
  147. package/dist/esm/ia-bookreader.js.map +1 -0
  148. package/dist/esm/plugins/plugin.archive_analytics.js +2 -0
  149. package/dist/esm/plugins/plugin.archive_analytics.js.map +1 -0
  150. package/dist/esm/plugins/plugin.autoplay.js +2 -0
  151. package/dist/esm/plugins/plugin.autoplay.js.map +1 -0
  152. package/dist/esm/plugins/plugin.chapters.js +2 -0
  153. package/dist/esm/plugins/plugin.chapters.js.map +1 -0
  154. package/dist/esm/plugins/plugin.experiments.js +2 -0
  155. package/dist/esm/plugins/plugin.experiments.js.map +1 -0
  156. package/dist/esm/plugins/plugin.iframe.js +2 -0
  157. package/dist/esm/plugins/plugin.iframe.js.map +1 -0
  158. package/dist/esm/plugins/plugin.iiif.js +2 -0
  159. package/dist/esm/plugins/plugin.iiif.js.map +1 -0
  160. package/dist/esm/plugins/plugin.resume.js +2 -0
  161. package/dist/esm/plugins/plugin.resume.js.map +1 -0
  162. package/dist/esm/plugins/plugin.search.js +2 -0
  163. package/dist/esm/plugins/plugin.search.js.map +1 -0
  164. package/dist/esm/plugins/plugin.text_selection.js +2 -0
  165. package/dist/esm/plugins/plugin.text_selection.js.map +1 -0
  166. package/dist/esm/plugins/plugin.translate.js +2 -0
  167. package/dist/esm/plugins/plugin.translate.js.map +1 -0
  168. package/dist/esm/plugins/plugin.tts.js +2 -0
  169. package/dist/esm/plugins/plugin.tts.js.map +1 -0
  170. package/dist/esm/plugins/plugin.url.js +2 -0
  171. package/dist/esm/plugins/plugin.url.js.map +1 -0
  172. package/dist/esm/plugins/plugin.vendor-fullscreen.js +2 -0
  173. package/dist/esm/plugins/plugin.vendor-fullscreen.js.map +1 -0
  174. package/jsconfig.json +18 -0
  175. package/package.json +113 -71
  176. package/src/BookReader/BookModel.js +92 -46
  177. package/src/BookReader/DragScrollable.js +233 -0
  178. package/src/BookReader/ImageCache.js +49 -16
  179. package/src/BookReader/Mode1Up.js +66 -365
  180. package/src/BookReader/Mode1UpLit.js +392 -0
  181. package/src/BookReader/Mode2Up.js +87 -1315
  182. package/src/BookReader/Mode2UpLit.js +786 -0
  183. package/src/BookReader/ModeAbstract.js +43 -0
  184. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  185. package/src/BookReader/ModeSmoothZoom.js +312 -0
  186. package/src/BookReader/ModeThumb.js +29 -15
  187. package/src/BookReader/Navbar/Navbar.js +204 -84
  188. package/src/BookReader/PageContainer.js +120 -23
  189. package/src/BookReader/ReduceSet.js +2 -2
  190. package/src/BookReader/Toolbar/Toolbar.js +18 -40
  191. package/src/BookReader/events.js +3 -3
  192. package/src/BookReader/options.js +105 -18
  193. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  194. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  195. package/src/BookReader/utils/SelectionObserver.js +72 -0
  196. package/src/BookReader/utils/classes.js +1 -1
  197. package/src/BookReader/utils.js +136 -12
  198. package/src/BookReader.js +732 -1231
  199. package/src/BookReaderPlugin.js +52 -0
  200. package/src/assets/icons/magnify-minus.svg +3 -7
  201. package/src/assets/icons/magnify-plus.svg +3 -7
  202. package/src/assets/icons/slider-toggle.svg +1 -0
  203. package/src/assets/icons/voice.svg +1 -0
  204. package/src/assets/images/hypothesis.ico +0 -0
  205. package/src/assets/images/icon_experiment.svg +1 -0
  206. package/src/assets/images/translate.svg +1 -0
  207. package/src/assets/images/unviewable_page.png +0 -0
  208. package/src/assets/silence.mp3 +0 -0
  209. package/src/css/BookReader.scss +1 -5
  210. package/src/css/_BRBookmarks.scss +1 -1
  211. package/src/css/_BRComponent.scss +1 -1
  212. package/src/css/_BRicon.scss +8 -2
  213. package/src/css/_BRmain.scss +16 -3
  214. package/src/css/_BRnav.scss +74 -70
  215. package/src/css/_BRpages.scss +174 -42
  216. package/src/css/_BRsearch.scss +69 -30
  217. package/src/css/_BRtoolbar.scss +5 -5
  218. package/src/css/_TextSelection.scss +249 -25
  219. package/src/css/_colorbox.scss +2 -2
  220. package/src/css/_controls.scss +24 -7
  221. package/src/css/_icons.scss +8 -1
  222. package/src/{BookNavigator/assets → css}/button-base.js +2 -2
  223. package/src/css/icon_checkmark.js +9 -0
  224. package/src/css/sharedStyles.js +15 -0
  225. package/src/ia-bookreader/downloads/downloads-provider.js +81 -0
  226. package/src/{BookNavigator → ia-bookreader}/downloads/downloads.js +25 -5
  227. package/src/ia-bookreader/ia-bookreader.js +686 -0
  228. package/src/ia-bookreader/sharing.js +27 -0
  229. package/src/ia-bookreader/viewable-files.js +98 -0
  230. package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments-provider.js +17 -17
  231. package/src/{BookNavigator → ia-bookreader}/visual-adjustments/visual-adjustments.js +75 -67
  232. package/src/{BookNavigator → plugins}/bookmarks/bookmark-button.js +4 -3
  233. package/src/{BookNavigator/assets → plugins/bookmarks}/bookmark-colors.js +1 -1
  234. package/src/{BookNavigator → plugins}/bookmarks/bookmark-edit.js +43 -31
  235. package/src/{BookNavigator → plugins}/bookmarks/bookmarks-list.js +48 -49
  236. package/src/{BookNavigator → plugins}/bookmarks/bookmarks-loginCTA.js +3 -3
  237. package/src/plugins/bookmarks/bookmarks-provider.js +63 -0
  238. package/src/{BookNavigator → plugins/bookmarks}/delete-modal-actions.js +1 -1
  239. package/src/{BookNavigator → plugins}/bookmarks/ia-bookmarks.js +117 -68
  240. package/src/plugins/plugin.archive_analytics.js +84 -78
  241. package/src/plugins/plugin.autoplay.js +99 -104
  242. package/src/plugins/plugin.chapters.js +323 -205
  243. package/src/plugins/plugin.experiments.js +375 -0
  244. package/src/plugins/plugin.iframe.js +36 -37
  245. package/src/plugins/plugin.iiif.js +147 -0
  246. package/src/plugins/plugin.resume.js +54 -51
  247. package/src/plugins/plugin.text_selection.js +464 -219
  248. package/src/plugins/plugin.vendor-fullscreen.js +5 -5
  249. package/src/plugins/search/plugin.search.js +347 -397
  250. package/src/{BookNavigator → plugins}/search/search-provider.js +59 -27
  251. package/src/{BookNavigator → plugins}/search/search-results.js +105 -76
  252. package/src/plugins/search/utils.js +50 -0
  253. package/src/plugins/search/view.js +51 -69
  254. package/src/plugins/translate/TranslationManager.js +164 -0
  255. package/src/plugins/translate/plugin.translate.js +559 -0
  256. package/src/plugins/tts/AbstractTTSEngine.js +78 -49
  257. package/src/plugins/tts/FestivalTTSEngine.js +20 -30
  258. package/src/plugins/tts/PageChunk.js +117 -23
  259. package/src/plugins/tts/PageChunkIterator.js +11 -17
  260. package/src/plugins/tts/WebTTSEngine.js +132 -92
  261. package/src/plugins/tts/plugin.tts.js +341 -351
  262. package/src/plugins/tts/utils.js +76 -67
  263. package/src/plugins/url/UrlPlugin.js +213 -0
  264. package/src/plugins/{plugin.url.js → url/plugin.url.js} +54 -18
  265. package/src/util/TextSelectionManager.js +1464 -0
  266. package/src/util/browserSniffing.js +42 -1
  267. package/src/util/cache.js +20 -0
  268. package/src/util/docCookies.js +21 -2
  269. package/src/util/dom.js +88 -0
  270. package/src/util/generators.js +89 -0
  271. package/src/util/lit.js +15 -0
  272. package/src/util/strings.js +11 -0
  273. package/.babelrc +0 -12
  274. package/.dependabot/config.yml +0 -6
  275. package/.eslintrc.js +0 -50
  276. package/.gitattributes +0 -2
  277. package/.github/ISSUE_TEMPLATE/bug.md +0 -32
  278. package/.github/ISSUE_TEMPLATE/feature-request.md +0 -30
  279. package/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -15
  280. package/.github/workflows/node.js.yml +0 -37
  281. package/.github/workflows/npm-publish.yml +0 -47
  282. package/.testcaferc.json +0 -5
  283. package/BookReader/BookReader.js +0 -21613
  284. package/BookReader/BookReader.js.map +0 -1
  285. package/BookReader/bookreader-component-bundle.js +0 -14497
  286. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  287. package/BookReader/bookreader-component-bundle.js.map +0 -1
  288. package/BookReader/jquery-1.10.1.js +0 -108
  289. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  290. package/BookReader/plugins/plugin.menu_toggle.js +0 -369
  291. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  292. package/BookReader/plugins/plugin.mobile_nav.js +0 -335
  293. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  294. package/BookReaderDemo/BookReaderDemo.css +0 -41
  295. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -115
  296. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  297. package/BookReaderDemo/BookReaderJSSimple.js +0 -55
  298. package/BookReaderDemo/IIIFBookReader.js +0 -207
  299. package/BookReaderDemo/assets/v5/Bookreader-logo-cool-grad.svg +0 -1
  300. package/BookReaderDemo/assets/v5/Bookreader-logo-flat.svg +0 -1
  301. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-cool-grad.png +0 -0
  302. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-flat.png +0 -0
  303. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.png +0 -0
  304. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.svg +0 -1
  305. package/BookReaderDemo/assets/v5/Bookreader-logo-warm.svg +0 -1
  306. package/BookReaderDemo/assets/v5/bookreader-logo-renders@1x.png +0 -0
  307. package/BookReaderDemo/assets/v5/bookreader-logo-renders@2x.png +0 -0
  308. package/BookReaderDemo/assets/v5/bookreader-v5-screenshot.png +0 -0
  309. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  310. package/BookReaderDemo/demo-advanced.html +0 -33
  311. package/BookReaderDemo/demo-autoplay.html +0 -38
  312. package/BookReaderDemo/demo-embed-iframe-src.html +0 -84
  313. package/BookReaderDemo/demo-embed.html +0 -26
  314. package/BookReaderDemo/demo-fullscreen-mobile.html +0 -36
  315. package/BookReaderDemo/demo-fullscreen.html +0 -33
  316. package/BookReaderDemo/demo-iiif.html +0 -34
  317. package/BookReaderDemo/demo-iiif.js +0 -26
  318. package/BookReaderDemo/demo-internetarchive.html +0 -74
  319. package/BookReaderDemo/demo-multiple.html +0 -43
  320. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  321. package/BookReaderDemo/demo-preview-pages.html +0 -1092
  322. package/BookReaderDemo/demo-simple.html +0 -34
  323. package/BookReaderDemo/demo-vendor-fullscreen.html +0 -36
  324. package/BookReaderDemo/immersion-1up.html +0 -64
  325. package/BookReaderDemo/immersion-mode.html +0 -35
  326. package/BookReaderDemo/toggle_controls.html +0 -53
  327. package/BookReaderDemo/view_mode.html +0 -39
  328. package/BookReaderDemo/viewmode-cycle.html +0 -41
  329. package/CHANGELOG.md +0 -499
  330. package/CONTRIBUTING.md +0 -7
  331. package/codecov.yml +0 -17
  332. package/index.html +0 -31
  333. package/karma.conf.js +0 -23
  334. package/screenshot.png +0 -0
  335. package/scripts/postversion.js +0 -10
  336. package/scripts/preversion.js +0 -14
  337. package/scripts/version.js +0 -26
  338. package/src/BookNavigator/BookModel.js +0 -14
  339. package/src/BookNavigator/BookNavigator.js +0 -459
  340. package/src/BookNavigator/assets/book-loader.js +0 -27
  341. package/src/BookNavigator/assets/icon_checkmark.js +0 -6
  342. package/src/BookNavigator/assets/icon_close.js +0 -3
  343. package/src/BookNavigator/assets/icon_sort_ascending.js +0 -5
  344. package/src/BookNavigator/assets/icon_sort_descending.js +0 -5
  345. package/src/BookNavigator/assets/icon_sort_neutral.js +0 -5
  346. package/src/BookNavigator/assets/icon_volumes.js +0 -11
  347. package/src/BookNavigator/bookmarks/bookmarks-provider.js +0 -53
  348. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  349. package/src/BookNavigator/downloads/downloads-provider.js +0 -66
  350. package/src/BookNavigator/search/a-search-result.js +0 -55
  351. package/src/BookNavigator/volumes/volumes-provider.js +0 -83
  352. package/src/BookNavigator/volumes/volumes.js +0 -178
  353. package/src/BookReader/DebugConsole.js +0 -54
  354. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  355. package/src/ItemNavigator/ItemNavigator.js +0 -376
  356. package/src/ItemNavigator/providers/sharing.js +0 -33
  357. package/src/css/_MobileNav.scss +0 -194
  358. package/src/dragscrollable-br.js +0 -261
  359. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  360. package/src/plugins/plugin.mobile_nav.js +0 -287
  361. package/tests/BookReader/BookModel.test.js +0 -312
  362. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  363. package/tests/BookReader/DebugConsole.test.js +0 -25
  364. package/tests/BookReader/ImageCache.test.js +0 -150
  365. package/tests/BookReader/Mode1Up.test.js +0 -164
  366. package/tests/BookReader/Mode2Up.test.js +0 -247
  367. package/tests/BookReader/Navbar/Navbar.test.js +0 -169
  368. package/tests/BookReader/PageContainer.test.js +0 -115
  369. package/tests/BookReader/ReduceSet.test.js +0 -38
  370. package/tests/BookReader/Toolbar/Toolbar.test.js +0 -26
  371. package/tests/BookReader/utils/classes.test.js +0 -88
  372. package/tests/BookReader/utils.test.js +0 -109
  373. package/tests/BookReader.options.test.js +0 -39
  374. package/tests/BookReader.test.js +0 -301
  375. package/tests/e2e/README.md +0 -75
  376. package/tests/e2e/autoplay.test.js +0 -13
  377. package/tests/e2e/base.test.js +0 -35
  378. package/tests/e2e/helpers/base.js +0 -263
  379. package/tests/e2e/helpers/debug.js +0 -13
  380. package/tests/e2e/helpers/desktopSearch.js +0 -72
  381. package/tests/e2e/helpers/mobileSearch.js +0 -85
  382. package/tests/e2e/helpers/mockSearch.js +0 -93
  383. package/tests/e2e/helpers/rightToLeft.js +0 -29
  384. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  385. package/tests/e2e/models/BookReader.js +0 -11
  386. package/tests/e2e/models/Navigation.js +0 -56
  387. package/tests/e2e/rightToLeft.test.js +0 -20
  388. package/tests/e2e/viewmode.test.js +0 -37
  389. package/tests/karma/BookNavigator/book-navigator.test.js +0 -139
  390. package/tests/karma/BookNavigator/bookmarks/bookmark-edit.test.js +0 -133
  391. package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +0 -222
  392. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  393. package/tests/karma/BookNavigator/search/search-results.test.js +0 -240
  394. package/tests/karma/BookNavigator/sharing/sharing-provider.test.js +0 -40
  395. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  396. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +0 -141
  397. package/tests/karma/BookNavigator/volumes/volumes.test.js +0 -98
  398. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  399. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  400. package/tests/plugins/plugin.autoplay.test.js +0 -52
  401. package/tests/plugins/plugin.chapters.test.js +0 -130
  402. package/tests/plugins/plugin.iframe.test.js +0 -42
  403. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  404. package/tests/plugins/plugin.resume.test.js +0 -98
  405. package/tests/plugins/plugin.text_selection.test.js +0 -203
  406. package/tests/plugins/plugin.url.test.js +0 -129
  407. package/tests/plugins/plugin.vendor-fullscreen.test.js +0 -65
  408. package/tests/plugins/search/plugin.search.test.js +0 -173
  409. package/tests/plugins/search/plugin.search.view.test.js +0 -106
  410. package/tests/plugins/tts/AbstractTTSEngine.test.js +0 -153
  411. package/tests/plugins/tts/FestivalTTSEngine.test.js +0 -59
  412. package/tests/plugins/tts/PageChunk.test.js +0 -57
  413. package/tests/plugins/tts/PageChunkIterator.test.js +0 -179
  414. package/tests/plugins/tts/WebTTSEngine.test.js +0 -126
  415. package/tests/plugins/tts/utils.test.js +0 -133
  416. package/tests/util/browserSniffing.test.js +0 -56
  417. package/tests/util/docCookies.test.js +0 -15
  418. package/tests/util/strings.test.js +0 -63
  419. package/tests/utils.js +0 -42
  420. package/webpack.config.js +0 -86
package/src/BookReader.js CHANGED
@@ -19,54 +19,79 @@ This file is part of BookReader.
19
19
  The BookReader source is hosted at http://github.com/internetarchive/bookreader/
20
20
 
21
21
  */
22
- // effect.js gives acces to extra easing function (e.g. easeInOutExpo)
23
- import 'jquery-ui/ui/effect.js';
24
-
25
22
  // Needed by touch-punch
26
23
  import 'jquery-ui/ui/widget.js';
27
24
  import 'jquery-ui/ui/widgets/mouse.js';
28
25
  import 'jquery-ui-touch-punch';
29
26
 
30
- import './dragscrollable-br.js';
31
27
  import PACKAGE_JSON from '../package.json';
32
28
  import * as utils from './BookReader/utils.js';
29
+ import { singleScrollIntoView } from './util/dom.js';
33
30
  import { exposeOverrideable } from './BookReader/utils/classes.js';
34
- import { Navbar, getNavPageNumHtml } from './BookReader/Navbar/Navbar.js';
35
- import { DEFAULT_OPTIONS } from './BookReader/options.js';
31
+ import { Navbar } from './BookReader/Navbar/Navbar.js';
32
+ import { DEFAULT_OPTIONS, OptionsParseError } from './BookReader/options.js';
36
33
  /** @typedef {import('./BookReader/options.js').BookReaderOptions} BookReaderOptions */
37
34
  /** @typedef {import('./BookReader/options.js').ReductionFactor} ReductionFactor */
38
35
  /** @typedef {import('./BookReader/BookModel.js').PageIndex} PageIndex */
36
+ /** @typedef {import('./BookReader/BookModel.js').PageModel} PageModel */
39
37
  import { EVENTS } from './BookReader/events.js';
40
- import { DebugConsole } from './BookReader/DebugConsole.js';
41
- import {
42
- Toolbar,
43
- blankInfoDiv,
44
- blankShareDiv,
45
- createPopup,
46
- } from './BookReader/Toolbar/Toolbar.js';
38
+ import { Toolbar } from './BookReader/Toolbar/Toolbar.js';
47
39
  import { BookModel } from './BookReader/BookModel.js';
48
40
  import { Mode1Up } from './BookReader/Mode1Up.js';
49
41
  import { Mode2Up } from './BookReader/Mode2Up.js';
50
- import { ModeThumb } from './BookReader/ModeThumb';
42
+ import { ModeThumb } from './BookReader/ModeThumb.js';
51
43
  import { ImageCache } from './BookReader/ImageCache.js';
52
44
  import { PageContainer } from './BookReader/PageContainer.js';
53
- import { NAMED_REDUCE_SETS } from './BookReader/ReduceSet';
54
-
55
- if (location.toString().indexOf('_debugShowConsole=true') != -1) {
56
- $(() => new DebugConsole().init());
57
- }
45
+ import { NAMED_REDUCE_SETS } from './BookReader/ReduceSet.js';
58
46
 
59
47
  /**
60
48
  * BookReader
61
- * @param {BookReaderOptions} options
49
+ * @param {Partial<BookReaderOptions>} overrides
62
50
  * TODO document all options properties
63
51
  * @constructor
64
52
  */
65
53
  export default function BookReader(overrides = {}) {
66
- const options = jQuery.extend(true, {}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
54
+ const options = BookReader.extendOptions({}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
67
55
  this.setup(options);
68
56
  }
69
57
 
58
+ /**
59
+ * Extend the default options for BookReader
60
+ * Accepts any number of option objects and merges them in order.
61
+ *
62
+ * Does a shallow merge of everything except plugin options. These are individually merged.
63
+ * @param {...Partial<BookReaderOptions>} newOptions
64
+ */
65
+ BookReader.extendOptions = function(...newOptions) {
66
+ if (newOptions.length <= 1) {
67
+ return newOptions[0];
68
+ }
69
+ /** @type {Array<keyof BookReaderOptions>} */
70
+ const shallowOverrides = ['onePage', 'twoPage', 'vars'];
71
+ /** @type {Array<keyof BookReaderOptions>} */
72
+ const mapOverrides = ['plugins', 'controls'];
73
+
74
+ const result = newOptions.shift();
75
+ for (const opts of newOptions) {
76
+ for (const [key, value] of Object.entries(opts)) {
77
+ if (shallowOverrides.includes(key)) {
78
+ result[key] ||= {};
79
+ result[key] = Object.assign(result[key], value);
80
+ } else if (mapOverrides.includes(key)) {
81
+ result[key] ||= {};
82
+ for (const [name, mapValue] of Object.entries(value)) {
83
+ result[key][name] ||= {};
84
+ Object.assign(result[key][name], mapValue);
85
+ }
86
+ } else {
87
+ result[key] = value;
88
+ }
89
+ }
90
+ }
91
+
92
+ return result;
93
+ };
94
+
70
95
  BookReader.version = PACKAGE_JSON.version;
71
96
 
72
97
  // Mode constants
@@ -76,6 +101,43 @@ BookReader.constMode1up = 1;
76
101
  BookReader.constMode2up = 2;
77
102
  /** thumbnails view */
78
103
  BookReader.constModeThumb = 3;
104
+
105
+ // Although this can actualy have any BookReaderPlugin subclass as value, we
106
+ // hardcode the known plugins here for type checking
107
+ BookReader.PLUGINS = {
108
+ /** @type {typeof import('./plugins/plugin.archive_analytics.js').ArchiveAnalyticsPlugin | null}*/
109
+ archiveAnalytics: null,
110
+ /** @type {typeof import('./plugins/plugin.autoplay.js').AutoplayPlugin | null}*/
111
+ autoplay: null,
112
+ /** @type {typeof import('./plugins/plugin.chapters.js').ChaptersPlugin | null}*/
113
+ chapters: null,
114
+ /** @type {typeof import('./plugins/plugin.experiments.js').ExperimentsPlugin | null}*/
115
+ experiments: null,
116
+ /** @type {typeof import('./plugins/plugin.iframe.js').IframePlugin | null}*/
117
+ iframe: null,
118
+ /** @type {typeof import('./plugins/plugin.resume.js').ResumePlugin | null}*/
119
+ resume: null,
120
+ /** @type {typeof import('./plugins/search/plugin.search.js').SearchPlugin | null}*/
121
+ search: null,
122
+ /** @type {typeof import('./plugins/plugin.text_selection.js').TextSelectionPlugin | null}*/
123
+ textSelection: null,
124
+ /** @type {typeof import('./plugins/translate/plugin.translate.js').TranslatePlugin | null}*/
125
+ translate: null,
126
+ /** @type {typeof import('./plugins/tts/plugin.tts.js').TtsPlugin | null}*/
127
+ tts: null,
128
+ };
129
+
130
+ /**
131
+ * @param {string} pluginName
132
+ * @param {new (...args: any[]) => import('./BookReaderPlugin.js').BookReaderPlugin} plugin
133
+ */
134
+ BookReader.registerPlugin = function(pluginName, plugin) {
135
+ if (BookReader.PLUGINS[pluginName]) {
136
+ console.warn(`Plugin ${pluginName} already registered. Overwriting.`);
137
+ }
138
+ BookReader.PLUGINS[pluginName] = plugin;
139
+ };
140
+
79
141
  /** image cache */
80
142
  BookReader.imageCache = null;
81
143
 
@@ -92,23 +154,79 @@ BookReader.defaultOptions = DEFAULT_OPTIONS;
92
154
  * @type {BookReaderOptions}
93
155
  * This is here, just in case you need to absolutely override an option.
94
156
  */
95
- BookReader.optionOverrides = {};
157
+ BookReader.optionOverrides = BookReader.optionOverrides || {};
96
158
 
97
159
  /**
98
160
  * Setup
99
161
  * It is separate from the constructor, so plugins can extend.
162
+ * @constructor
100
163
  * @param {BookReaderOptions} options
101
164
  */
102
165
  BookReader.prototype.setup = function(options) {
103
166
  // Store the options used to setup bookreader
104
167
  this.options = options;
105
168
 
169
+ /** @type {import('@/src/ia-bookreader/ia-bookreader.js').IaBookReader} */
170
+ this.shell;
171
+
172
+ // Base server used by some api calls
173
+ /** @deprecated */
174
+ this.bookId = options.bookId;
175
+ /** @deprecated */
176
+ this.server = options.server;
177
+ /** @deprecated */
178
+ this.subPrefix = options.subPrefix;
179
+ /** @deprecated */
180
+ this.bookPath = options.bookPath;
181
+
182
+ /** @type {import('./util/TextSelectionManager.js').BookReaderTextFragment | null} */
183
+ this.targetTextFragment = null;
184
+
185
+ // Construct the usual plugins first to get type hints
186
+ this.plugins = {
187
+ archiveAnalytics: BookReader.PLUGINS.archiveAnalytics ? new BookReader.PLUGINS.archiveAnalytics(this) : null,
188
+ autoplay: BookReader.PLUGINS.autoplay ? new BookReader.PLUGINS.autoplay(this) : null,
189
+ chapters: BookReader.PLUGINS.chapters ? new BookReader.PLUGINS.chapters(this) : null,
190
+ experiments: BookReader.PLUGINS.experiments ? new BookReader.PLUGINS.experiments(this) : null,
191
+ iframe: BookReader.PLUGINS.iframe ? new BookReader.PLUGINS.iframe(this) : null,
192
+ search: BookReader.PLUGINS.search ? new BookReader.PLUGINS.search(this) : null,
193
+ resume: BookReader.PLUGINS.resume ? new BookReader.PLUGINS.resume(this) : null,
194
+ textSelection: BookReader.PLUGINS.textSelection ? new BookReader.PLUGINS.textSelection(this) : null,
195
+ translate: BookReader.PLUGINS.translate ? new BookReader.PLUGINS.translate(this) : null,
196
+ tts: BookReader.PLUGINS.tts ? new BookReader.PLUGINS.tts(this) : null,
197
+ };
198
+
199
+ // Delete anything that's null
200
+ for (const [pluginName, plugin] of Object.entries(this.plugins)) {
201
+ if (!plugin) delete this.plugins[pluginName];
202
+ }
203
+
204
+ // Now construct the rest of the plugins
205
+ for (const [pluginName, PluginClass] of Object.entries(BookReader.PLUGINS)) {
206
+ if (this.plugins[pluginName] || !PluginClass) continue;
207
+ this.plugins[pluginName] = new PluginClass(this);
208
+ }
209
+
210
+ // And call setup on them
211
+ for (const [pluginName, plugin] of Object.entries(this.plugins)) {
212
+ try {
213
+ plugin.setup(this.options.plugins?.[pluginName] ?? {});
214
+ // Write the options back; this way the plugin is the source of truth,
215
+ // and BR just contains a reference to it.
216
+ this.options.plugins[pluginName] = plugin.options;
217
+ } catch (e) {
218
+ console.error(`Error setting up plugin ${pluginName}`, e);
219
+ }
220
+ }
221
+
222
+ if (this.plugins.search?.options.enabled) {
223
+ // Expose the search method for convenience / backward compat
224
+ this.search = this.plugins.search.search.bind(this.plugins.search);
225
+ }
226
+
106
227
  /** @type {number} @deprecated some past iterations set this */
107
228
  this.numLeafs = undefined;
108
229
 
109
- /** Overridden by plugin.search.js */
110
- this.enableSearch = false;
111
-
112
230
  /**
113
231
  * Store viewModeOrder states
114
232
  * @var {boolean}
@@ -152,24 +270,16 @@ BookReader.prototype.setup = function(options) {
152
270
  this.ui = options.ui;
153
271
  this.uiAutoHide = options.uiAutoHide;
154
272
 
155
- this.thumbWidth = 100; // will be overridden during prepareThumbnailView
273
+ this.thumbWidth = 100; // will be overridden during this._modes.modeThumb.prepare();
156
274
  this.thumbRowBuffer = options.thumbRowBuffer;
157
275
  this.thumbColumns = options.thumbColumns;
158
276
  this.thumbMaxLoading = options.thumbMaxLoading;
159
277
  this.thumbPadding = options.thumbPadding;
160
278
  this.displayedRows = [];
161
-
162
279
  this.displayedIndices = [];
163
- /** @deprecated Unused; will be remove in v5 */
164
- this.imgs = {};
165
- /** @deprecated No longer used; will be remove in v5 */
166
- this.prefetchedImgs = {}; //an object with numeric keys corresponding to page index, reduce
167
280
 
168
281
  this.animating = false;
169
- this.flipSpeed = options.flipSpeed;
170
- this.flipDelay = options.flipDelay;
171
- this.twoPagePopUp = null;
172
- this.leafEdgeTmp = null;
282
+ this.flipSpeed = utils.parseAnimationSpeed(options.flipSpeed) || 400;
173
283
 
174
284
  /**
175
285
  * Represents the first displayed index
@@ -178,8 +288,7 @@ BookReader.prototype.setup = function(options) {
178
288
  * @property {number|null} firstIndex
179
289
  */
180
290
  this.firstIndex = null;
181
- this.lastDisplayableIndex2up = null;
182
- this.isFullscreenActive = false;
291
+ this.isFullscreenActive = options.startFullscreen || false;
183
292
  this.lastScroll = null;
184
293
 
185
294
  this.showLogo = options.showLogo;
@@ -212,26 +321,22 @@ BookReader.prototype.setup = function(options) {
212
321
 
213
322
  // Assign the data methods
214
323
  this.data = options.data;
215
- if (options.getNumLeafs) BookReader.prototype.getNumLeafs = options.getNumLeafs;
216
- if (options.getPageWidth) BookReader.prototype.getPageWidth = options.getPageWidth;
217
- if (options.getPageHeight) BookReader.prototype.getPageHeight = options.getPageHeight;
218
- if (options.getPageURI) BookReader.prototype.getPageURI = options.getPageURI;
219
- if (options.getPageSide) BookReader.prototype.getPageSide = options.getPageSide;
220
- if (options.getPageNum) BookReader.prototype.getPageNum = options.getPageNum;
221
- if (options.getPageProp) BookReader.prototype.getPageProp = options.getPageProp;
222
- if (options.getSpreadIndices) BookReader.prototype.getSpreadIndices = options.getSpreadIndices;
223
- if (options.leafNumToIndex) BookReader.prototype.leafNumToIndex = options.leafNumToIndex;
224
324
 
225
325
  /** @type {{[name: string]: JQuery}} */
226
326
  this.refs = {};
227
327
 
228
- /**
229
- * @private (for now) Models are largely state storing classes. This might be too much
230
- * granularity, but time will tell!
231
- */
232
- this._models = {
233
- book: new BookModel(this),
234
- };
328
+ /** The book being displayed in BookReader*/
329
+ this.book = new BookModel(this);
330
+
331
+ if (options.getNumLeafs) this.book.getNumLeafs = options.getNumLeafs.bind(this);
332
+ if (options.getPageWidth) this.book.getPageWidth = options.getPageWidth.bind(this);
333
+ if (options.getPageHeight) this.book.getPageHeight = options.getPageHeight.bind(this);
334
+ if (options.getPageURI) this.book.getPageURI = options.getPageURI.bind(this);
335
+ if (options.getPageSide) this.book.getPageSide = options.getPageSide.bind(this);
336
+ if (options.getPageNum) this.book.getPageNum = options.getPageNum.bind(this);
337
+ if (options.getPageProp) this.book.getPageProp = options.getPageProp.bind(this);
338
+ if (options.getSpreadIndices) this.book.getSpreadIndices = options.getSpreadIndices.bind(this);
339
+ if (options.leafNumToIndex) this.book.leafNumToIndex = options.leafNumToIndex.bind(this);
235
340
 
236
341
  /**
237
342
  * @private Components are 'subchunks' of bookreader functionality, usually UI related
@@ -245,14 +350,14 @@ BookReader.prototype.setup = function(options) {
245
350
  };
246
351
 
247
352
  this._modes = {
248
- mode1Up: new Mode1Up(this, this._models.book),
249
- mode2Up: new Mode2Up(this, this._models.book),
250
- modeThumb: new ModeThumb(this, this._models.book),
353
+ mode1Up: new Mode1Up(this, this.book),
354
+ mode2Up: new Mode2Up(this, this.book),
355
+ modeThumb: new ModeThumb(this, this.book),
251
356
  };
252
357
 
253
358
  /** Stores classes which we want to expose (selectively) some methods as overridable */
254
359
  this._overrideable = {
255
- '_models.book': this._models.book,
360
+ 'book': this.book,
256
361
  '_components.navbar': this._components.navbar,
257
362
  '_components.toolbar': this._components.toolbar,
258
363
  '_modes.mode1Up': this._modes.mode1Up,
@@ -261,21 +366,74 @@ BookReader.prototype.setup = function(options) {
261
366
  };
262
367
 
263
368
  /** Image cache for general image fetching */
264
- this.imageCache = new ImageCache(this._models.book, {
369
+ this.imageCache = new ImageCache(this.book, {
265
370
  useSrcSet: this.options.useSrcSet,
266
371
  reduceSet: this.reduceSet,
372
+ renderPageURI: options.renderPageURI.bind(this),
267
373
  });
374
+
375
+ /**
376
+ * Flag if BookReader has "focus" for keyboard shortcuts
377
+ * Initially true, set to false when:
378
+ * - BookReader scrolled out of view
379
+ * Set to true when:
380
+ * - BookReader scrolled into view
381
+ */
382
+ this.hasKeyFocus = true;
268
383
  };
269
384
 
270
- /** @deprecated unused outside Mode2Up */
271
- Object.defineProperty(BookReader.prototype, 'leafEdgeL', {
272
- get() { return this._modes.mode2Up.leafEdgeL; },
273
- set(newVal) { this._modes.mode2Up.leafEdgeL = newVal; }
274
- });
275
- /** @deprecated unused outside Mode2Up */
276
- Object.defineProperty(BookReader.prototype, 'leafEdgeR', {
277
- get() { return this._modes.mode2Up.leafEdgeR; },
278
- set(newVal) { this._modes.mode2Up.leafEdgeR = newVal; }
385
+ BookReader.prototype.initializePlugin = function(pluginName) {
386
+ const plugin = new BookReader.PLUGINS[pluginName](this);
387
+ this.plugins[pluginName] = plugin;
388
+ try {
389
+ plugin.setup(this.options.plugins?.[pluginName] ?? {});
390
+ this.options.plugins[pluginName] = plugin.options;
391
+ } catch (e) {
392
+ console.error(`Error setting up plugin ${pluginName} outside of regular cycle`, e);
393
+ throw e;
394
+ }
395
+
396
+ try {
397
+ plugin.init();
398
+ } catch (e) {
399
+ console.error(`Error initializing plugin ${pluginName} outside of regular cycle`, e);
400
+ throw e;
401
+ }
402
+ };
403
+ /**
404
+ * Get all the HTML Elements that are being/can be rendered.
405
+ * Includes cached elements which might be rendered again.
406
+ */
407
+ BookReader.prototype.getActivePageContainerElements = function() {
408
+ let containerEls = Object.values(this._modes.mode2Up.mode2UpLit.pageContainerCache).map(pc => pc.$container[0])
409
+ .concat(Object.values(this._modes.mode1Up.mode1UpLit.pageContainerCache).map(pc => pc.$container[0]));
410
+ if (this.mode == this.constModeThumb) {
411
+ containerEls = containerEls.concat(this.$('.BRpagecontainer').toArray());
412
+ }
413
+ return containerEls;
414
+ };
415
+
416
+ /**
417
+ * Get the HTML Elements for the rendered page. Note there can be more than one, since
418
+ * (at least as of writing) different modes can maintain different caches.
419
+ * @param {PageIndex} pageIndex
420
+ * @returns {HTMLElement[]}
421
+ */
422
+ BookReader.prototype.getActivePageContainerElementsForIndex = function(pageIndex) {
423
+ return [
424
+ this._modes.mode2Up.mode2UpLit.pageContainerCache[pageIndex]?.$container?.[0],
425
+ this._modes.mode1Up.mode1UpLit.pageContainerCache[pageIndex]?.$container?.[0],
426
+ ...(this.mode == this.constModeThumb ? this.$(`.pagediv${pageIndex}`).toArray() : []),
427
+ ].filter(x => x);
428
+ };
429
+
430
+ Object.defineProperty(BookReader.prototype, 'activeMode', {
431
+ /** @return {Mode1Up | Mode2Up | ModeThumb} */
432
+ get() { return {
433
+ 1: this._modes.mode1Up,
434
+ 2: this._modes.mode2Up,
435
+ 3: this._modes.modeThumb,
436
+ }[this.mode]; },
279
437
  });
280
438
 
281
439
  /**
@@ -290,9 +448,9 @@ BookReader.util = utils;
290
448
  * @private
291
449
  */
292
450
  BookReader.prototype.extendParams = function(params, newParams) {
293
- var modifiedNewParams = $.extend({}, newParams);
451
+ const modifiedNewParams = $.extend({}, newParams);
294
452
  if ('undefined' != typeof(modifiedNewParams.page)) {
295
- var pageIndex = this._models.book.parsePageString(modifiedNewParams.page);
453
+ const pageIndex = this.book.parsePageString(modifiedNewParams.page);
296
454
  if (!isNaN(pageIndex))
297
455
  modifiedNewParams.index = pageIndex;
298
456
  delete modifiedNewParams.page;
@@ -306,7 +464,7 @@ BookReader.prototype.extendParams = function(params, newParams) {
306
464
  * @return {object} the parsed params
307
465
  */
308
466
  BookReader.prototype.initParams = function() {
309
- var params = {};
467
+ const params = {};
310
468
  // Flag initializing for updateFromParams()
311
469
  params.init = true;
312
470
 
@@ -323,8 +481,8 @@ BookReader.prototype.initParams = function() {
323
481
 
324
482
  // If we have a title leaf, use that as the default instead of index 0,
325
483
  // but only use as default if book has a few pages
326
- if ('undefined' != typeof(this.titleLeaf) && this._models.book.getNumLeafs() > 2) {
327
- params.index = this._models.book.leafNumToIndex(this.titleLeaf);
484
+ if ('undefined' != typeof(this.titleLeaf) && this.book.getNumLeafs() > 2) {
485
+ params.index = this.book.leafNumToIndex(this.titleLeaf);
328
486
  } else {
329
487
  params.index = 0;
330
488
  }
@@ -339,9 +497,9 @@ BookReader.prototype.initParams = function() {
339
497
  }
340
498
 
341
499
  // Check for Resume plugin
342
- if (this.options.enablePageResume) {
500
+ if (this.plugins.resume?.options.enabled) {
343
501
  // Check cookies
344
- const val = this.getResumeValue();
502
+ const val = this.plugins.resume.getResumeValue();
345
503
  if (val !== null) {
346
504
  // If page index different from default
347
505
  if (params.index !== val) {
@@ -355,7 +513,7 @@ BookReader.prototype.initParams = function() {
355
513
  // Check for URL plugin
356
514
  if (this.options.enableUrlPlugin) {
357
515
  // Params explicitly set in URL take precedence over all other methods
358
- var urlParams = this.paramsFromFragment(this.urlReadFragment());
516
+ let urlParams = this.paramsFromFragment(this.urlReadFragment());
359
517
 
360
518
  // Get params if hash fragment available with 'history' urlMode
361
519
  const hasHashURL = !Object.keys(urlParams).length && this.urlReadHashFragment();
@@ -375,23 +533,24 @@ BookReader.prototype.initParams = function() {
375
533
  }
376
534
 
377
535
  // Check for Search plugin
378
- if (this.options.enableSearch) {
536
+ if (this.plugins.search?.options.enabled) {
537
+ const sp = this.plugins.search;
379
538
  // Go to first result only if no default or URL page
380
- this.options.goToFirstResult = !params.pageFound;
539
+ sp.options.goToFirstResult = !params.pageFound;
381
540
 
382
541
  // If initialSearchTerm not set
383
- if (!this.options.initialSearchTerm) {
542
+ if (!sp.initialSearchTerm) {
384
543
  // Look for any term in URL
385
544
  if (params.search) {
386
545
  // Old style: /search/[term]
387
- this.options.initialSearchTerm = params.search;
388
- this.searchTerm = params.search;
546
+ sp.options.initialSearchTerm = params.search;
547
+ sp.searchTerm = params.search;
389
548
  } else {
390
549
  // If we have a query string: q=[term]
391
550
  const searchParams = new URLSearchParams(this.readQueryString());
392
551
  const searchTerm = searchParams.get('q');
393
552
  if (searchTerm) {
394
- this.options.initialSearchTerm = utils.decodeURIComponentPlus(searchTerm);
553
+ sp.options.initialSearchTerm = utils.decodeURIComponentPlus(searchTerm);
395
554
  }
396
555
  }
397
556
  }
@@ -434,31 +593,57 @@ BookReader.prototype.readQueryString = function() {
434
593
  * Determines the initial mode for starting if a mode is not already
435
594
  * present in the params argument
436
595
  * @param {object} params
437
- * @return {number} the mode
596
+ * @return {1 | 2 | 3} the initial mode
438
597
  */
439
598
  BookReader.prototype.getInitialMode = function(params) {
440
- // Use params or browser width to set view mode
441
- var windowWidth = $(window).width();
442
- var nextMode;
443
- if ('undefined' != typeof(params.mode)) {
444
- nextMode = params.mode;
445
- } else if (this.ui == 'full'
446
- && this.enableMobileNav
447
- && this.isFullscreenActive
448
- && windowWidth <= this.onePageMinBreakpoint
449
- ) {
450
- // In full mode, we set the default based on width
451
- nextMode = this.constMode1up;
599
+ // if mobile breakpoint, we always show this.constMode1up mode
600
+ const windowWidth = $(window).width();
601
+ const isMobile = windowWidth && windowWidth <= this.onePageMinBreakpoint;
602
+
603
+ let initialMode;
604
+ if (params.mode) {
605
+ initialMode = params.mode;
606
+ } else if (isMobile) {
607
+ initialMode = this.constMode1up;
452
608
  } else {
453
- nextMode = this.constMode2up;
609
+ initialMode = this.constMode2up;
610
+ }
611
+
612
+ if (!this.canSwitchToMode(initialMode)) {
613
+ initialMode = this.constMode1up;
454
614
  }
455
615
 
456
- if (!this.canSwitchToMode(nextMode)) {
457
- nextMode = this.constMode1up;
616
+ // override defaults mode via `options.defaults` metadata
617
+ if (this.options.defaults) {
618
+ try {
619
+ initialMode = _modeStringToNumber(this.options.defaults);
620
+ } catch (e) {
621
+ // Can ignore this error
622
+ }
458
623
  }
459
- return nextMode;
624
+
625
+ return initialMode;
460
626
  };
461
627
 
628
+ /**
629
+ * Converts a mode string to a the mode numeric constant
630
+ * @param {'mode/1up'|'mode/2up'|'mode/thumb'} modeString
631
+ * @return {1 | 2 | 3}
632
+ */
633
+ export function _modeStringToNumber(modeString) {
634
+ const MAPPING = {
635
+ 'mode/1up': 1,
636
+ 'mode/2up': 2,
637
+ 'mode/thumb': 3,
638
+ };
639
+
640
+ if (!(modeString in MAPPING)) {
641
+ throw new OptionsParseError(`Invalid mode string: ${modeString}`);
642
+ }
643
+
644
+ return MAPPING[modeString];
645
+ }
646
+
462
647
  /**
463
648
  * This is called by the client to initialize BookReader.
464
649
  * It renders onto the DOM. It should only be called once.
@@ -467,8 +652,8 @@ BookReader.prototype.init = function() {
467
652
  this.init.initComplete = false;
468
653
  this.pageScale = this.reduce; // preserve current reduce
469
654
 
470
- var params = this.initParams();
471
-
655
+ const params = this.initParams();
656
+ this.urlPlugin?.pullFromAddressBar();
472
657
  this.firstIndex = params.index ? params.index : 0;
473
658
 
474
659
  // Setup Navbars and other UI
@@ -497,41 +682,45 @@ BookReader.prototype.init = function() {
497
682
  // Explicitly ensure this.mode exists for initNavbar() below
498
683
  this.mode = params.mode;
499
684
 
500
- if (this.ui == "embed" && this.options.showNavbar) {
501
- this.initEmbedNavbar();
502
- } else {
503
- if (this.options.showToolbar) {
504
- this.initToolbar(this.mode, this.ui); // Build inside of toolbar div
505
- }
506
- if (this.options.showNavbar) {
507
- this.initNavbar();
685
+ // Display Navigation
686
+ if (this.options.showToolbar) {
687
+ this.initToolbar(this.mode, this.ui); // Build inside of toolbar div
688
+ }
689
+ if (this.options.showNavbar) { // default navigation
690
+ const $navBar = this.initNavbar();
691
+
692
+ // extend navbar with plugins
693
+ for (const plugin of Object.values(this.plugins)) {
694
+ plugin.extendNavBar($navBar);
508
695
  }
509
696
  }
510
697
 
511
698
  // Switch navbar controls on mobile/desktop
512
- this.switchNavbarControls();
699
+ this._components.navbar.switchNavbarControls();
513
700
 
514
701
  this.resizeBRcontainer();
515
702
  this.updateFromParams(params);
516
703
  this.initUIStrings();
517
704
 
518
705
  // Bind to events
519
-
520
- this.bindNavigationHandlers();
706
+ this._components.navbar.bindControlClickHandlers();
707
+ for (const plugin of Object.values(this.plugins)) {
708
+ plugin._bindNavigationHandlers();
709
+ }
521
710
  this.setupKeyListeners();
522
711
 
523
712
  this.lastScroll = (new Date().getTime());
524
- this.refs.$brContainer.bind('scroll', this, function(e) {
713
+ this.refs.$brContainer.on('scroll', this, function(e) {
525
714
  // Note, this scroll event fires for both user, and js generated calls
526
715
  // It is functioning in some cases as the primary triggerer for rendering
527
716
  e.data.lastScroll = (new Date().getTime());
528
- if (e.data.constMode2up != e.data.mode) {
717
+ if (e.data.constModeThumb == e.data.mode) {
529
718
  e.data.drawLeafsThrottled();
530
719
  }
531
720
  });
532
721
 
533
722
  if (this.options.autoResize) {
534
- $(window).bind('resize', this, function(e) {
723
+ $(window).on('resize', this, function(e) {
535
724
  e.data.resize();
536
725
  });
537
726
  $(window).on("orientationchange", this, function(e) {
@@ -544,18 +733,30 @@ BookReader.prototype.init = function() {
544
733
  }
545
734
 
546
735
  // If not searching, set to allow on-going fragment changes
547
- if (!this.options.initialSearchTerm) {
736
+ if (!this.plugins.search?.options.initialSearchTerm) {
548
737
  this.suppressFragmentChange = false;
549
738
  }
550
739
 
740
+ if (this.options.startFullscreen) {
741
+ this.enterFullscreen(true);
742
+ }
743
+
744
+ // Init plugins
745
+ for (const [pluginName, plugin] of Object.entries(this.plugins)) {
746
+ try {
747
+ plugin.init();
748
+ }
749
+ catch (e) {
750
+ console.error(`Error initializing plugin ${pluginName}`, e);
751
+ }
752
+ }
753
+
551
754
  this.init.initComplete = true;
552
755
  this.trigger(BookReader.eventNames.PostInit);
553
756
 
554
757
  // Must be called after this.init.initComplete set to true to allow
555
758
  // BookReader.prototype.resize to run.
556
- if (this.options.startFullscreen) {
557
- this.toggleFullscreen();
558
- }
759
+
559
760
  };
560
761
 
561
762
  /**
@@ -564,7 +765,6 @@ BookReader.prototype.init = function() {
564
765
  */
565
766
  BookReader.prototype.trigger = function(name, props = this) {
566
767
  const eventName = 'BookReader:' + name;
567
- $(document).trigger(eventName, props);
568
768
 
569
769
  utils.polyfillCustomEvent(window);
570
770
  window.dispatchEvent(new CustomEvent(eventName, {
@@ -572,14 +772,29 @@ BookReader.prototype.trigger = function(name, props = this) {
572
772
  composed: true,
573
773
  detail: { props },
574
774
  }));
775
+ $(document).trigger(eventName, props);
776
+ };
777
+
778
+ BookReader.prototype.on = function(name, callback) {
779
+ $(document).on('BookReader:' + name, callback);
780
+ };
781
+
782
+ BookReader.prototype.off = function(name, callback) {
783
+ $(document).off('BookReader:' + name, callback);
575
784
  };
576
785
 
786
+ /**
787
+ * @deprecated Use .on and .off instead
788
+ */
577
789
  BookReader.prototype.bind = function(name, callback) {
578
- $(document).bind('BookReader:' + name, callback);
790
+ return this.on(name, callback);
579
791
  };
580
792
 
793
+ /**
794
+ * @deprecated Use .on and .off instead
795
+ */
581
796
  BookReader.prototype.unbind = function(name, callback) {
582
- $(document).unbind('BookReader:' + name, callback);
797
+ return this.off(name, callback);
583
798
  };
584
799
 
585
800
  /**
@@ -591,135 +806,144 @@ BookReader.prototype.resize = function() {
591
806
  this.resizeBRcontainer();
592
807
 
593
808
  // Switch navbar controls on mobile/desktop
594
- this.switchNavbarControls();
809
+ this._components.navbar.switchNavbarControls();
595
810
 
596
811
  if (this.constMode1up == this.mode) {
597
812
  if (this.onePage.autofit != 'none') {
598
- this.resizePageView1up();
813
+ this._modes.mode1Up.resizePageView();
599
814
  this.centerPageView();
600
- if (this.enableSearch) this.updateSearchHilites(); //deletes highlights but does not call remove()
601
815
  } else {
602
816
  this.centerPageView();
603
817
  this.displayedIndices = [];
604
- if (this.enableSearch) this.updateSearchHilites(); //deletes highlights but does not call remove()
605
818
  this.drawLeafsThrottled();
606
819
  }
607
820
  } else if (this.constModeThumb == this.mode) {
608
- this.prepareThumbnailView();
821
+ this._modes.modeThumb.prepare();
609
822
  } else {
610
- // We only need to prepare again in autofit (size of spread changes)
611
- if (this.twoPage.autofit) {
612
- // most common path, esp. for archive.org books
613
- this.prepareTwoPageView();
614
- } else {
615
- // used when zoomed in
616
- // Re-center if the scrollbars have disappeared
617
- var center = this.twoPageGetViewCenter();
618
- var doRecenter = false;
619
- if (this.twoPage.totalWidth < this.refs.$brContainer.prop('clientWidth')) {
620
- center.percentageX = 0.5;
621
- doRecenter = true;
622
- }
623
- if (this.twoPage.totalHeight < this.refs.$brContainer.prop('clientHeight')) {
624
- center.percentageY = 0.5;
625
- doRecenter = true;
626
- }
627
- if (doRecenter) {
628
- this.twoPageCenterView(center.percentageX, center.percentageY);
629
- }
630
- }
823
+ this._modes.mode2Up.resizePageView();
631
824
  }
632
825
  this.trigger(BookReader.eventNames.resize);
633
826
  };
634
827
 
635
828
  /**
636
- * Binds keyboard event listeners
829
+ * Binds keyboard and keyboard focus event listeners
637
830
  */
638
- BookReader.prototype.setupKeyListeners = function() {
639
- var self = this;
640
-
641
- var KEY_PGUP = 33;
642
- var KEY_PGDOWN = 34;
643
- var KEY_END = 35;
644
- var KEY_HOME = 36;
645
-
646
- var KEY_LEFT = 37;
647
- var KEY_UP = 38;
648
- var KEY_RIGHT = 39;
649
- var KEY_DOWN = 40;
650
- // The minus(-) and equal(=) keys have different mappings for different browsers
651
- var KEY_MINUS = 189; // Chrome
652
- var KEY_MINUS_F = 173; // Firefox
653
- var KEY_NUMPAD_SUBTRACT = 109;
654
- var KEY_EQUAL = 187; // Chrome
655
- var KEY_EQUAL_F = 61; // Firefox
656
- var KEY_NUMPAD_ADD = 107;
657
-
658
- // We use document here instead of window to avoid a bug in jQuery on IE7
659
- $(document).keydown(function(e) {
660
-
661
- // Keyboard navigation
662
- if (!self.keyboardNavigationIsDisabled(e)) {
663
- switch (e.keyCode) {
664
- case KEY_PGUP:
665
- case KEY_UP:
666
- // In 1up mode page scrolling is handled by browser
667
- if (self.constMode2up == self.mode) {
668
- e.preventDefault();
669
- self.prev();
670
- }
671
- break;
672
- case KEY_DOWN:
673
- case KEY_PGDOWN:
674
- if (self.constMode2up == self.mode) {
675
- e.preventDefault();
676
- self.next();
831
+ BookReader.prototype.setupKeyListeners = function () {
832
+
833
+ // Keyboard focus by BookReader in viewport
834
+ //
835
+ // Intersection observer and callback sets BookReader keyboard
836
+ // "focus" flag off when the BookReader is not in the viewport.
837
+ if (window.IntersectionObserver) {
838
+ const observer = new IntersectionObserver((entries) => {
839
+ entries.forEach((entry) => {
840
+ if (entry.intersectionRatio === 0) {
841
+ this.hasKeyFocus = false;
842
+ } else {
843
+ this.hasKeyFocus = true;
677
844
  }
678
- break;
679
- case KEY_END:
845
+ });
846
+ }, {
847
+ root: null,
848
+ rootMargin: '0px',
849
+ threshold: [0, 0.05, 1],
850
+ });
851
+ observer.observe(this.refs.$br[0]);
852
+ }
853
+
854
+ // Keyboard listeners
855
+ document.addEventListener('keydown', (e) => {
856
+
857
+ // Ignore if BookReader "focus" flag not set
858
+ if (!this.hasKeyFocus) {
859
+ return;
860
+ }
861
+
862
+ // Ignore if modifiers are active.
863
+ if (e.getModifierState('Control') ||
864
+ e.getModifierState('Shift') ||
865
+ e.getModifierState('Alt') ||
866
+ e.getModifierState('Meta') ||
867
+ e.getModifierState('Win') /* hack for IE */) {
868
+ return;
869
+ }
870
+
871
+ // Ignore in input elements
872
+ if (utils.isInputActive()) {
873
+ return;
874
+ }
875
+
876
+ // KeyboardEvent code values:
877
+ // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
878
+ switch (e.key) {
879
+
880
+ // Page navigation
881
+ case "Home":
882
+ e.preventDefault();
883
+ this.first();
884
+ break;
885
+ case "End":
886
+ e.preventDefault();
887
+ this.last();
888
+ break;
889
+ case "PageDown":
890
+ // In 1up and thumb mode page scrolling handled by browser
891
+ if (this.constMode2up === this.mode) {
680
892
  e.preventDefault();
681
- self.last();
682
- break;
683
- case KEY_HOME:
893
+ this.next();
894
+ }
895
+ break;
896
+ case "PageUp":
897
+ // In 1up and thumb mode page scrolling handled by browser
898
+ if (this.constMode2up === this.mode) {
684
899
  e.preventDefault();
685
- self.first();
686
- break;
687
- case KEY_LEFT:
688
- if (self.constModeThumb != self.mode) {
689
- e.preventDefault();
690
- self.left();
691
- }
692
- break;
693
- case KEY_RIGHT:
694
- if (self.constModeThumb != self.mode) {
695
- e.preventDefault();
696
- self.right();
697
- }
698
- break;
699
- case KEY_MINUS:
700
- case KEY_MINUS_F:
701
- case KEY_NUMPAD_SUBTRACT:
900
+ this.prev();
901
+ }
902
+ break;
903
+ case "ArrowLeft":
904
+ case "Left": // hack for IE and old Gecko
905
+ // No y-scrolling in thumb mode
906
+ if (this.constModeThumb != this.mode) {
702
907
  e.preventDefault();
703
- self.zoom(-1);
704
- break;
705
- case KEY_EQUAL:
706
- case KEY_EQUAL_F:
707
- case KEY_NUMPAD_ADD:
908
+ this.left();
909
+ }
910
+ break;
911
+ case "ArrowRight":
912
+ case "Right": // hack for IE and old Gecko
913
+ // No y-scrolling in thumb mode
914
+ if (this.constModeThumb != this.mode) {
708
915
  e.preventDefault();
709
- self.zoom(+1);
710
- break;
916
+ this.right();
711
917
  }
918
+ break;
919
+ // Zoom
920
+ case '-':
921
+ case 'Subtract':
922
+ e.preventDefault();
923
+ this.zoom(-1);
924
+ break;
925
+ case '+':
926
+ case '=':
927
+ case 'Add':
928
+ e.preventDefault();
929
+ this.zoom(1);
930
+ break;
931
+ // Fullscreen
932
+ case 'F':
933
+ case 'f':
934
+ e.preventDefault();
935
+ this.toggleFullscreen();
936
+ break;
712
937
  }
713
938
  });
714
939
  };
715
940
 
716
941
  BookReader.prototype.drawLeafs = function() {
717
942
  if (this.constMode1up == this.mode) {
718
- this.drawLeafsOnePage();
719
- } else if (this.constModeThumb == this.mode) {
720
- this.drawLeafsThumbnail();
943
+ // Not needed for Mode1Up anymore
944
+ return;
721
945
  } else {
722
- this.drawLeafsTwoPage();
946
+ this.activeMode.drawLeafs();
723
947
  }
724
948
  };
725
949
 
@@ -728,11 +952,17 @@ BookReader.prototype.drawLeafs = function() {
728
952
  * @param {PageIndex} index
729
953
  */
730
954
  BookReader.prototype._createPageContainer = function(index) {
731
- return new PageContainer(this._models.book.getPage(index, false), {
955
+ const pageContainer = new PageContainer(this.book.getPage(index, false), {
732
956
  isProtected: this.protected,
733
957
  imageCache: this.imageCache,
734
- loadingImage: this.imagesBaseURL + 'loading.gif',
735
958
  });
959
+
960
+ // Call plugin handlers
961
+ for (const plugin of Object.values(this.plugins)) {
962
+ plugin._configurePageContainer(pageContainer);
963
+ }
964
+
965
+ return pageContainer;
736
966
  };
737
967
 
738
968
  BookReader.prototype.bindGestures = function(jElement) {
@@ -742,8 +972,8 @@ BookReader.prototype.bindGestures = function(jElement) {
742
972
  // when you move the book with one finger and then add another
743
973
  // finger to pinch. Gestures are aware of scroll state.
744
974
 
745
- var self = this;
746
- var numTouches = 1;
975
+ const self = this;
976
+ let numTouches = 1;
747
977
 
748
978
  jElement.unbind('touchmove').bind('touchmove', function(e) {
749
979
  if (e.originalEvent.cancelable) numTouches = e.originalEvent.touches.length;
@@ -764,60 +994,25 @@ BookReader.prototype.bindGestures = function(jElement) {
764
994
  });
765
995
  };
766
996
 
767
- /** @deprecated Not used outside ModeThumb */
768
- BookReader.prototype.drawLeafsThumbnail = ModeThumb.prototype.drawLeafs;
769
- exposeOverrideableMethod(ModeThumb, '_modes.modeThumb', 'drawLeafs', 'drawLeafsThumbnail');
770
- /** @deprecated Not used outside ModeThumb */
771
- BookReader.prototype.lazyLoadThumbnails = ModeThumb.prototype.lazyLoadThumbnails;
772
- exposeOverrideableMethod(ModeThumb, '_modes.modeThumb', 'lazyLoadThumbnails', 'lazyLoadThumbnails');
773
- BookReader.prototype.lazyLoadImage = ModeThumb.prototype.lazyLoadImage;
774
- exposeOverrideableMethod(ModeThumb, '_modes.modeThumb', 'lazyLoadImage', 'lazyLoadImage');
775
- /** @deprecated Internal use only */
776
- BookReader.prototype.zoomThumb = ModeThumb.prototype.zoom;
777
- exposeOverrideableMethod(ModeThumb, '_modes.modeThumb', 'zoom', 'zoomThumb');
778
- /** @deprecated Not used outside ModeThumb */
779
- BookReader.prototype.getThumbnailWidth = ModeThumb.prototype.getThumbnailWidth;
780
- exposeOverrideableMethod(ModeThumb, '_modes.modeThumb', 'getThumbnailWidth', 'getThumbnailWidth');
781
- /** @deprecated Not used outside ModeThumb */
782
- BookReader.prototype.prepareThumbnailView = ModeThumb.prototype.prepare;
783
- exposeOverrideableMethod(ModeThumb, '_modes.modeThumb', 'prepare', 'prepareThumbnailView');
784
-
785
997
  /**
786
998
  * A throttled version of drawLeafs
787
999
  */
788
1000
  BookReader.prototype.drawLeafsThrottled = utils.throttle(
789
1001
  BookReader.prototype.drawLeafs,
790
- 250 // 250 ms gives quick feedback, but doesn't eat cpu
1002
+ 250, // 250 ms gives quick feedback, but doesn't eat cpu
791
1003
  );
792
1004
 
793
1005
  /**
794
1006
  * @param {number} direction Pass 1 to zoom in, anything else to zoom out
795
1007
  */
796
1008
  BookReader.prototype.zoom = function(direction) {
797
- switch (this.mode) {
798
- case this.constMode1up:
799
- if (direction == 1) {
800
- // XXX other cases
801
- this.zoom1up('in');
802
- } else {
803
- this.zoom1up('out');
804
- }
805
- break;
806
- case this.constMode2up:
807
- if (direction == 1) {
808
- // XXX other cases
809
- this.zoom2up('in');
810
- } else {
811
- this.zoom2up('out');
812
- }
813
- break;
814
- case this.constModeThumb:
815
- // XXX update zoomThumb for named directions
816
- this.zoomThumb(direction);
817
- break;
1009
+ if (direction == 1) {
1010
+ this.activeMode.zoom('in');
1011
+ } else {
1012
+ this.activeMode.zoom('out');
818
1013
  }
819
-
820
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1014
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1015
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
821
1016
  return;
822
1017
  };
823
1018
 
@@ -834,19 +1029,31 @@ BookReader.prototype.resizeBRcontainer = function(animate) {
834
1029
  if (animate) {
835
1030
  this.refs.$brContainer.animate({
836
1031
  top: this.getToolBarHeight(),
837
- bottom: this.getFooterHeight()
1032
+ bottom: this.getFooterHeight(),
838
1033
  }, this.constResizeAnimationDuration, 'linear');
839
1034
  } else {
840
1035
  this.refs.$brContainer.css({
841
1036
  top: this.getToolBarHeight(),
842
- bottom: this.getFooterHeight()
1037
+ bottom: this.getFooterHeight(),
843
1038
  });
844
1039
  }
845
1040
  };
846
1041
 
1042
+ /**
1043
+ * Scrolls the given element into view within the active mode's scroll container.
1044
+ * @param {Element} el
1045
+ * @param {object} [options]
1046
+ * @param {'auto' | 'smooth'} [options.behavior]
1047
+ * @param {'start' | 'center' | 'end' | 'nearest'} [options.block]
1048
+ * @param {'start' | 'center' | 'end' | 'nearest'} [options.inline]
1049
+ */
1050
+ BookReader.prototype.scrollIntoView = function(el, options) {
1051
+ singleScrollIntoView(el, { ...options, scrollContainer: this.activeMode.scrollContainer });
1052
+ };
1053
+
847
1054
  BookReader.prototype.centerPageView = function() {
848
- var scrollWidth = this.refs.$brContainer.prop('scrollWidth');
849
- var clientWidth = this.refs.$brContainer.prop('clientWidth');
1055
+ const scrollWidth = this.refs.$brContainer.prop('scrollWidth');
1056
+ const clientWidth = this.refs.$brContainer.prop('clientWidth');
850
1057
  if (scrollWidth > clientWidth) {
851
1058
  this.refs.$brContainer.prop('scrollLeft', (scrollWidth - clientWidth) / 2);
852
1059
  }
@@ -930,14 +1137,16 @@ BookReader.prototype._reduceSort = (a, b) => a.reduce - b.reduce;
930
1137
 
931
1138
  /**
932
1139
  * Attempts to jump to page
933
- * @param {string}
1140
+ * @param {string} pageNum
1141
+ * @param {object} options
1142
+ * @param {boolean} [options.ariaLive = false]
934
1143
  * @return {boolean} Returns true if page could be found, false otherwise.
935
1144
  */
936
- BookReader.prototype.jumpToPage = function(pageNum) {
937
- var pageIndex = this._models.book.parsePageString(pageNum);
1145
+ BookReader.prototype.jumpToPage = function(pageNum, {ariaLive = false} = {}) {
1146
+ const pageIndex = this.book.parsePageString(pageNum);
938
1147
 
939
1148
  if ('undefined' != typeof(pageIndex)) {
940
- this.jumpToIndex(pageIndex);
1149
+ this.jumpToIndex(pageIndex, { ariaLive });
941
1150
  return true;
942
1151
  }
943
1152
 
@@ -950,44 +1159,48 @@ BookReader.prototype.jumpToPage = function(pageNum) {
950
1159
  * @param {PageIndex} index
951
1160
  */
952
1161
  BookReader.prototype._isIndexDisplayed = function(index) {
953
- // One up "caches" pages +- current, so exclude those in the test.
954
- return this.constMode1up == this.mode ? this.displayedIndices.slice(1, -1).includes(index) :
955
- this.displayedIndices ? this.displayedIndices.includes(index) :
956
- this.currentIndex() == index;
1162
+ return this.displayedIndices ? this.displayedIndices.includes(index) :
1163
+ this.currentIndex() == index;
957
1164
  };
958
1165
 
959
1166
  /**
960
1167
  * Changes the current page
961
- * @param {PageIndex} index
962
- * @param {number} [pageX]
963
- * @param {number} [pageY]
964
- * @param {boolean} [noAnimate]
965
- */
966
- BookReader.prototype.jumpToIndex = function(index, pageX, pageY, noAnimate) {
1168
+ * @param {PageIndex | 'left' | 'right' | 'next' | 'prev'} indexOrDirection
1169
+ * @param {object} options
1170
+ * @param {boolean} [options.allowUnviewable] - whether to allow jumping to unviewable pages; default false
1171
+ * @param {number} [options.pageX] Position on page ; not implemented
1172
+ * @param {number} [options.pageY] Position on page ; not implemented
1173
+ * @param {boolean} [options.noAnimate]
1174
+ * @param {number | 'fast' | 'slow'} [options.flipSpeed]
1175
+ * @param {boolean} [options.ariaLive]
1176
+ * @param {boolean} [options.triggerStop] - whether to trigger the stop event; default true; maybe deprecated?
1177
+ */
1178
+ BookReader.prototype.jumpToIndex = function(indexOrDirection, {pageX = 0, pageY = 0, noAnimate = false, flipSpeed = null, ariaLive = false, triggerStop = true, allowUnviewable = false} = {}) {
1179
+ const page = this.activeMode.parsePageSpecifier(indexOrDirection);
1180
+ flipSpeed = utils.parseAnimationSpeed(flipSpeed) || this.flipSpeed;
1181
+ if (!page || page.index == this.currentIndex()) {
1182
+ return;
1183
+ }
1184
+
967
1185
  // Don't jump into specific unviewable page
968
- const page = this._models.book.getPage(index);
969
- if (!page.isViewable && page.unviewablesStart != page.index) {
1186
+ if (!allowUnviewable && !page.isViewable && page.unviewablesStart != page.index) {
970
1187
  // If already in unviewable range, jump to end of that range
971
1188
  const alreadyInPreview = this._isIndexDisplayed(page.unviewablesStart);
972
- const newIndex = alreadyInPreview ? page.findNext({ combineConsecutiveUnviewables: true }).index : page.unviewablesStart;
973
- return this.jumpToIndex(newIndex, pageX, pageY, noAnimate);
974
- }
975
-
976
- this.trigger(BookReader.eventNames.stop);
977
-
978
- if (this.constMode2up == this.mode) {
979
- this._modes.mode2Up.jumpToIndex(index);
980
- } else if (this.constModeThumb == this.mode) {
981
- this._modes.modeThumb.jumpToIndex(index);
982
- } else { // 1up
983
- this._modes.mode1Up.jumpToIndex(index, pageX, pageY, noAnimate);
1189
+ const newIndex = alreadyInPreview ? page.findNext({ combineConsecutiveUnviewables: true })?.index : page.unviewablesStart;
1190
+ // Rare, but a book can end on an unviewable page, so this could be undefined
1191
+ if (typeof newIndex !== 'undefined') {
1192
+ this.jumpToIndex(newIndex, { pageX, pageY, noAnimate, flipSpeed, ariaLive });
1193
+ }
1194
+ } else {
1195
+ if (triggerStop) this.trigger(BookReader.eventNames.stop);
1196
+ this.trigger(BookReader.eventNames.beforePageChanged, { index: page.index, ariaLive });
1197
+ this.activeMode.jumpToIndex(page.index, { pageX, pageY, noAnimate, flipSpeed, ariaLive });
984
1198
  }
985
1199
  };
986
1200
 
987
1201
  /**
988
1202
  * Return mode or 1up if initial thumb
989
1203
  * @param {number}
990
- * @see BookReader.prototype.drawLeafsThumbnail
991
1204
  */
992
1205
  BookReader.prototype.getPrevReadMode = function(mode) {
993
1206
  if (mode === BookReader.constMode1up || mode === BookReader.constMode2up) {
@@ -1000,7 +1213,7 @@ BookReader.prototype.getPrevReadMode = function(mode) {
1000
1213
 
1001
1214
  /**
1002
1215
  * Switches the mode (eg 1up 2up thumb)
1003
- * @param {number}
1216
+ * @param {number|'1up' | '2up' | 'thumb'}
1004
1217
  * @param {object} [options]
1005
1218
  * @param {boolean} [options.suppressFragmentChange = false]
1006
1219
  * @param {boolean} [options.onInit = false] - this
@@ -1010,9 +1223,21 @@ BookReader.prototype.switchMode = function(
1010
1223
  {
1011
1224
  suppressFragmentChange = false,
1012
1225
  init = false,
1013
- pageFound = false
1014
- } = {}
1226
+ pageFound = false,
1227
+ } = {},
1015
1228
  ) {
1229
+ if (typeof mode === 'string') {
1230
+ mode = {
1231
+ '1up': this.constMode1up,
1232
+ '2up': this.constMode2up,
1233
+ 'thumb': this.constModeThumb,
1234
+ }[mode];
1235
+ }
1236
+
1237
+ if (!mode) {
1238
+ throw new Error(`Invalid mode: ${mode}`);
1239
+ }
1240
+
1016
1241
  // Skip checks before init() complete
1017
1242
  if (this.init.initComplete) {
1018
1243
  if (mode === this.mode) {
@@ -1024,10 +1249,13 @@ BookReader.prototype.switchMode = function(
1024
1249
  }
1025
1250
 
1026
1251
  this.trigger(BookReader.eventNames.stop);
1027
- if (this.enableSearch) this.removeSearchHilites();
1028
1252
 
1029
1253
  this.prevReadMode = this.getPrevReadMode(this.mode);
1030
1254
 
1255
+ if (this.mode != mode) {
1256
+ this.activeMode.unprepare?.();
1257
+ }
1258
+
1031
1259
  this.mode = mode;
1032
1260
 
1033
1261
  // reinstate scale if moving from thumbnail view
@@ -1040,42 +1268,32 @@ BookReader.prototype.switchMode = function(
1040
1268
 
1041
1269
  // XXX maybe better to preserve zoom in each mode
1042
1270
  if (this.constMode1up == mode) {
1043
- this.onePageCalculateReductionFactors();
1044
- this.reduce = this.quantizeReduce(this.reduce, this.onePage.reductionFactors);
1045
- this.prepareOnePageView();
1271
+ this._modes.mode1Up.prepare();
1046
1272
  } else if (this.constModeThumb == mode) {
1047
1273
  this.reduce = this.quantizeReduce(this.reduce, this.reductionFactors);
1048
- this.prepareThumbnailView();
1274
+ this._modes.modeThumb.prepare();
1049
1275
  } else {
1050
- // $$$ why don't we save autofit?
1051
- // this.twoPage.autofit = null; // Take zoom level from other mode
1052
- // spread indices not set, so let's set them
1053
- if (init || !pageFound) {
1054
- this.setSpreadIndices();
1055
- }
1056
-
1057
- this.twoPageCalculateReductionFactors(); // this sets this.twoPage && this.reduce
1058
- this.prepareTwoPageView();
1059
- this.twoPageCenterView(0.5, 0.5); // $$$ TODO preserve center
1276
+ this._modes.mode2Up.prepare();
1060
1277
  }
1061
1278
 
1062
1279
  if (!(this.suppressFragmentChange || suppressFragmentChange)) {
1063
1280
  this.trigger(BookReader.eventNames.fragmentChange);
1064
1281
  }
1065
- var eventName = mode + 'PageViewSelected';
1282
+ const eventName = mode + 'PageViewSelected';
1066
1283
  this.trigger(BookReader.eventNames[eventName]);
1067
1284
 
1068
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1285
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1286
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1069
1287
  };
1070
1288
 
1071
1289
  BookReader.prototype.updateBrClasses = function() {
1072
- var modeToClass = {};
1290
+ const modeToClass = {};
1073
1291
  modeToClass[this.constMode1up] = 'BRmode1up';
1074
- modeToClass[this.constMode2up] = 'BRmode2Up';
1292
+ modeToClass[this.constMode2up] = 'BRmode2up';
1075
1293
  modeToClass[this.constModeThumb] = 'BRmodeThumb';
1076
1294
 
1077
1295
  this.refs.$br
1078
- .removeClass('BRmode1up BRmode2Up BRmodeThumb')
1296
+ .removeClass('BRmode1up BRmode2up BRmodeThumb')
1079
1297
  .addClass(modeToClass[this.mode]);
1080
1298
 
1081
1299
  if (this.isFullscreen()) {
@@ -1095,31 +1313,30 @@ BookReader.prototype.isFullscreen = function() {
1095
1313
  * Toggles fullscreen
1096
1314
  * @param { boolean } bindKeyboardControls
1097
1315
  */
1098
- BookReader.prototype.toggleFullscreen = function(bindKeyboardControls = true) {
1316
+ BookReader.prototype.toggleFullscreen = async function(bindKeyboardControls = true) {
1099
1317
  if (this.isFullscreen()) {
1100
- this.exitFullScreen();
1318
+ await this.exitFullScreen();
1101
1319
  } else {
1102
- this.enterFullscreen(bindKeyboardControls);
1320
+ await this.enterFullscreen(bindKeyboardControls);
1103
1321
  }
1104
1322
  };
1105
1323
 
1106
1324
  /**
1107
1325
  * Enters fullscreen
1108
1326
  * including:
1109
- * - animation
1110
1327
  * - binds keyboard controls
1111
1328
  * - fires custom event
1112
1329
  * @param { boolean } bindKeyboardControls
1113
1330
  */
1114
- BookReader.prototype.enterFullscreen = function(bindKeyboardControls = true) {
1331
+ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = true) {
1332
+ this.refs.$br.addClass('BRfullscreenAnimation');
1115
1333
  const currentIndex = this.currentIndex();
1116
- this.refs.$brContainer.css('opacity', 0);
1117
1334
 
1118
1335
  if (bindKeyboardControls) {
1119
1336
  this._fullscreenCloseHandler = (e) => {
1120
1337
  if (e.keyCode === 27) this.toggleFullscreen();
1121
1338
  };
1122
- $(document).keyup(this._fullscreenCloseHandler);
1339
+ $(document).on("keyup", this._fullscreenCloseHandler);
1123
1340
  }
1124
1341
 
1125
1342
  const windowWidth = $(window).width();
@@ -1128,15 +1345,34 @@ BookReader.prototype.enterFullscreen = function(bindKeyboardControls = true) {
1128
1345
  }
1129
1346
 
1130
1347
  this.isFullscreenActive = true;
1131
- this.animating = true;
1132
- this.refs.$brContainer.animate({opacity: 1}, 'fast', 'linear',() => {
1133
- this.resize();
1134
- this.jumpToIndex(currentIndex);
1135
- this.animating = false;
1136
- });
1137
1348
 
1138
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1349
+ // Change tooltip of fullscreen button
1350
+ this.$('.BRnav .BRicon.full').attr('title', 'Exit fullscreen');
1351
+ this.$('.BRnav .BRicon.full .BRtooltip').text('Exit fullscreen');
1352
+
1353
+ // prioritize class updates so CSS can propagate
1354
+ this.updateBrClasses();
1355
+ if (this.activeMode instanceof Mode1Up) {
1356
+ this.activeMode.mode1UpLit.scale = this.activeMode.mode1UpLit.computeDefaultScale(this.book.getPage(currentIndex));
1357
+ // Need the new scale to be applied before calling jumpToIndex
1358
+ this.activeMode.mode1UpLit.requestUpdate();
1359
+ await this.activeMode.mode1UpLit.updateComplete;
1360
+ }
1361
+ this.jumpToIndex(currentIndex);
1362
+
1363
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1364
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1365
+ // Add "?view=theater"
1366
+ this.trigger(BookReader.eventNames.fragmentChange);
1367
+ // trigger event here, so that animations,
1368
+ // class updates happen before book-nav relays to web components
1139
1369
  this.trigger(BookReader.eventNames.fullscreenToggled);
1370
+
1371
+ // resize book after all events & css updates
1372
+ await new Promise(resolve => setTimeout(resolve, 0));
1373
+
1374
+ this.resize();
1375
+ this.refs.$br.removeClass('BRfullscreenAnimation');
1140
1376
  };
1141
1377
 
1142
1378
  /**
@@ -1146,27 +1382,51 @@ BookReader.prototype.enterFullscreen = function(bindKeyboardControls = true) {
1146
1382
  * - fires custom event
1147
1383
  * @param { boolean } bindKeyboardControls
1148
1384
  */
1149
- BookReader.prototype.exitFullScreen = function() {
1150
- this.refs.$brContainer.css('opacity', 0);
1151
-
1152
- $(document).unbind('keyup', this._fullscreenCloseHandler);
1153
-
1154
- var windowWidth = $(window).width();
1385
+ BookReader.prototype.exitFullScreen = async function () {
1386
+ this.refs.$br.addClass('BRfullscreenAnimation');
1387
+ $(document).off('keyup', this._fullscreenCloseHandler);
1155
1388
 
1389
+ const windowWidth = $(window).width();
1156
1390
  const canShow2up = this.options.controls.twoPage.visible;
1157
1391
  if (canShow2up && (windowWidth <= this.onePageMinBreakpoint)) {
1158
1392
  this.switchMode(this.constMode2up);
1159
1393
  }
1160
1394
 
1161
1395
  this.isFullscreenActive = false;
1162
- this.updateBrClasses();
1163
- this.animating = true;
1164
- this.refs.$brContainer.animate({opacity: 1}, 'fast', 'linear', () => {
1165
- this.resize();
1166
- this.animating = false;
1167
- });
1168
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1396
+
1397
+ this.$('.BRnav .BRicon.full').attr('title', 'Go fullscreen');
1398
+ this.$('.BRnav .BRicon.full .BRtooltip').text('Go fullscreen');
1399
+
1400
+ // Trigger fullscreen event immediately
1401
+ // so that book-nav can relay to web components
1169
1402
  this.trigger(BookReader.eventNames.fullscreenToggled);
1403
+
1404
+ this.updateBrClasses();
1405
+ await new Promise(resolve => setTimeout(resolve, 0));
1406
+ this.resize();
1407
+
1408
+ if (this.activeMode instanceof Mode1Up) {
1409
+ this.activeMode.mode1UpLit.scale = this.activeMode.mode1UpLit.computeDefaultScale(this.book.getPage(this.currentIndex()));
1410
+ this.activeMode.mode1UpLit.requestUpdate();
1411
+ await this.activeMode.mode1UpLit.updateComplete;
1412
+ }
1413
+
1414
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1415
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1416
+ // Remove "?view=theater"
1417
+ this.trigger(BookReader.eventNames.fragmentChange);
1418
+ this.refs.$br.removeClass('BRfullscreenAnimation');
1419
+ };
1420
+
1421
+ /**
1422
+ * Toggles the mobile slider and page controls
1423
+ */
1424
+ BookReader.prototype.toggleSlider = function () {
1425
+ const toggleButton = this.refs.$BRnav.find('.toggle_slider');
1426
+ const mobileControls = this.refs.$br.find('.BRnavMobile');
1427
+
1428
+ toggleButton.toggleClass('active');
1429
+ mobileControls.toggleClass('docked');
1170
1430
  };
1171
1431
 
1172
1432
  /**
@@ -1180,7 +1440,7 @@ BookReader.prototype.currentIndex = function() {
1180
1440
  return this.firstIndex; // $$$ TODO page in center of view would be better
1181
1441
  } else if (this.mode == this.constMode2up) {
1182
1442
  // Only allow indices that are actually present in book
1183
- return utils.clamp(this.firstIndex, 0, this._models.book.getNumLeafs() - 1);
1443
+ return utils.clamp(this.firstIndex, 0, this.book.getNumLeafs() - 1);
1184
1444
  } else {
1185
1445
  throw 'currentIndex called for unimplemented mode ' + this.mode;
1186
1446
  }
@@ -1195,13 +1455,11 @@ BookReader.prototype.currentIndex = function() {
1195
1455
  */
1196
1456
  BookReader.prototype.updateFirstIndex = function(
1197
1457
  index,
1198
- { suppressFragmentChange = false } = {}
1458
+ { suppressFragmentChange = false } = {},
1199
1459
  ) {
1200
- // Called multiple times when defaults contains "mode/1up",
1201
- // including after init(). Skip fragment change if no index change
1202
- if (this.firstIndex === index) {
1203
- suppressFragmentChange = true;
1204
- }
1460
+ // If there's no change, do nothing
1461
+ if (this.firstIndex === index) return;
1462
+
1205
1463
  this.firstIndex = index;
1206
1464
  if (!(this.suppressFragmentChange || suppressFragmentChange)) {
1207
1465
  this.trigger(BookReader.eventNames.fragmentChange);
@@ -1209,22 +1467,21 @@ BookReader.prototype.updateFirstIndex = function(
1209
1467
  // If there's an initial search we stop suppressing global URL changes
1210
1468
  // when local suppression ends
1211
1469
  // This seems to correctly handle multiple calls during mode/1up
1212
- if (this.options.initialSearchTerm && !suppressFragmentChange) {
1470
+ if (this.plugins.search?.options.initialSearchTerm && !suppressFragmentChange) {
1213
1471
  this.suppressFragmentChange = false;
1214
1472
  }
1215
- this.trigger('pageChanged');
1216
- this.updateNavIndexThrottled(index);
1473
+
1474
+ this.trigger(BookReader.eventNames.pageChanged);
1475
+
1476
+ // event to know if user is actively reading
1477
+ this.trigger(BookReader.eventNames.userAction);
1217
1478
  };
1218
1479
 
1219
1480
  /**
1220
1481
  * Flip the right page over onto the left
1221
1482
  */
1222
- BookReader.prototype.right = function() {
1223
- if ('rl' != this.pageProgression) {
1224
- this.next();
1225
- } else {
1226
- this.prev();
1227
- }
1483
+ BookReader.prototype.right = function({ ariaLive = true, triggerStop = true } = {}) {
1484
+ this.jumpToIndex('right', { ariaLive, triggerStop });
1228
1485
  };
1229
1486
 
1230
1487
  /**
@@ -1241,12 +1498,8 @@ BookReader.prototype.rightmost = function() {
1241
1498
  /**
1242
1499
  * Flip the left page over onto the right
1243
1500
  */
1244
- BookReader.prototype.left = function() {
1245
- if ('rl' != this.pageProgression) {
1246
- this.prev();
1247
- } else {
1248
- this.next();
1249
- }
1501
+ BookReader.prototype.left = function({ ariaLive = true, triggerStop = true } = {}) {
1502
+ this.jumpToIndex('left', { ariaLive, triggerStop });
1250
1503
  };
1251
1504
 
1252
1505
  /**
@@ -1260,283 +1513,42 @@ BookReader.prototype.leftmost = function() {
1260
1513
  }
1261
1514
  };
1262
1515
 
1263
- BookReader.prototype.next = function() {
1264
- if (this.constMode2up == this.mode) {
1265
- this.trigger(BookReader.eventNames.stop);
1266
- this.flipFwdToIndex(null);
1267
- } else {
1268
- if (this.firstIndex < this.lastDisplayableIndex()) {
1269
- this.jumpToIndex(this.firstIndex + 1);
1270
- }
1271
- }
1272
- };
1273
-
1274
- BookReader.prototype.prev = function() {
1275
- const isOnFrontPage = this.firstIndex < 1;
1276
- if (isOnFrontPage) return;
1277
-
1278
- if (this.constMode2up == this.mode) {
1279
- this.trigger(BookReader.eventNames.stop);
1280
- this.flipBackToIndex(null);
1281
- } else {
1282
- if (this.firstIndex >= 1) {
1283
- this.jumpToIndex(this.firstIndex - 1);
1284
- }
1285
- }
1286
- };
1287
-
1288
- BookReader.prototype.first = function() {
1289
- this.jumpToIndex(this.firstDisplayableIndex());
1290
- };
1291
-
1292
- BookReader.prototype.last = function() {
1293
- this.jumpToIndex(this.lastDisplayableIndex());
1294
- };
1295
-
1296
- /**
1297
- * Scrolls down one screen view
1298
- */
1299
- BookReader.prototype.scrollDown = function() {
1300
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1301
- if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) {
1302
- // Whole pages are visible, scroll whole page only
1303
- return this.next();
1304
- }
1305
-
1306
- this.refs.$brContainer.stop(true).animate(
1307
- { scrollTop: '+=' + this._scrollAmount() + 'px'},
1308
- 400, 'easeInOutExpo'
1309
- );
1310
- return true;
1311
- } else {
1312
- return false;
1313
- }
1314
- };
1315
-
1316
1516
  /**
1317
- * Scrolls up one screen view
1517
+ * @param {object} options
1518
+ * @param {boolean} [options.triggerStop = true]
1519
+ * @param {number | 'fast' | 'slow'} [options.flipSpeed]
1520
+ * @param {boolean} [options.ariaLive = true]
1318
1521
  */
1319
- BookReader.prototype.scrollUp = function() {
1320
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1321
- if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) {
1322
- // Whole pages are visible, scroll whole page only
1323
- return this.prev();
1324
- }
1325
-
1326
- this.refs.$brContainer.stop(true).animate(
1327
- { scrollTop: '-=' + this._scrollAmount() + 'px'},
1328
- 400, 'easeInOutExpo'
1329
- );
1330
- return true;
1331
- } else {
1332
- return false;
1333
- }
1522
+ BookReader.prototype.next = function({
1523
+ triggerStop = true,
1524
+ flipSpeed = null,
1525
+ ariaLive = true,
1526
+ } = {}) {
1527
+ this.jumpToIndex('next', { ariaLive, flipSpeed, triggerStop });
1334
1528
  };
1335
1529
 
1336
1530
  /**
1337
- * The amount to scroll vertically in integer pixels
1531
+ * @param {object} options
1532
+ * @param {boolean} [options.triggerStop = true]
1533
+ * @param {number | 'fast' | 'slow'} [options.flipSpeed]
1534
+ * @param {boolean} [options.ariaLive = true]
1338
1535
  */
1339
- BookReader.prototype._scrollAmount = function() {
1340
- if (this.constMode1up == this.mode) {
1341
- // Overlap by % of page size
1342
- return parseInt(this.refs.$brContainer.prop('clientHeight') - this._models.book.getPageHeight(this.currentIndex()) / this.reduce * 0.03);
1343
- }
1344
-
1345
- return parseInt(0.9 * this.refs.$brContainer.prop('clientHeight'));
1536
+ BookReader.prototype.prev = function({
1537
+ triggerStop = true,
1538
+ flipSpeed = null,
1539
+ ariaLive = true,
1540
+ } = {}) {
1541
+ this.jumpToIndex('prev', { ariaLive, flipSpeed, triggerStop });
1346
1542
  };
1347
1543
 
1348
- /**
1349
- * @deprecated No longer used; will be remove in v5
1350
- */
1351
- BookReader.prototype.prefetchImg = async function(index, fetchNow = false) {
1352
- console.warn('Call to deprecated function: BookReader.prefetchImg. No-op.');
1544
+ BookReader.prototype.first = function({ ariaLive = true, triggerStop = false } = {}) {
1545
+ this.jumpToIndex(0, { ariaLive, triggerStop });
1353
1546
  };
1354
1547
 
1355
- /**
1356
- * @deprecated No longer used; will be remove in v5
1357
- */
1358
- BookReader.prototype.pruneUnusedImgs = function() {
1359
- console.warn('Call to deprecated function: BookReader.pruneUnused. No-op.');
1548
+ BookReader.prototype.last = function({ ariaLive = true, triggerStop = false } = {}) {
1549
+ this.jumpToIndex(this.book.getNumLeafs() - 1, { ariaLive, triggerStop });
1360
1550
  };
1361
1551
 
1362
- /************************/
1363
- /** Mode1Up extensions **/
1364
- /************************/
1365
- /** @deprecated not used outside BookReader */
1366
- BookReader.prototype.prepareOnePageView = Mode1Up.prototype.prepare;
1367
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'prepare', 'prepareOnePageView');
1368
- /** @deprecated not used outside BookReader */
1369
- BookReader.prototype.drawLeafsOnePage = Mode1Up.prototype.drawLeafs;
1370
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'drawLeafs', 'drawLeafsOnePage');
1371
- /** @deprecated not used outside BookReader */
1372
- BookReader.prototype.zoom1up = Mode1Up.prototype.zoom;
1373
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'zoom', 'zoom1up');
1374
- /** @deprecated not used outside Mode1Up */
1375
- BookReader.prototype.onePageGetAutofitWidth = Mode1Up.prototype.getAutofitWidth;
1376
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'getAutofitWidth', 'onePageGetAutofitWidth');
1377
- /** @deprecated not used outside Mode1Up, BookReader */
1378
- BookReader.prototype.onePageGetAutofitHeight = Mode1Up.prototype.getAutofitHeight;
1379
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'getAutofitHeight', 'onePageGetAutofitHeight');
1380
- /** @deprecated not used outside Mode1Up, BookReader */
1381
- BookReader.prototype.onePageGetPageTop = Mode1Up.prototype.getPageTop;
1382
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'getPageTop', 'onePageGetPageTop');
1383
- /** @deprecated not used outside Mode1Up, BookReader */
1384
- BookReader.prototype.onePageCalculateReductionFactors = Mode1Up.prototype.calculateReductionFactors;
1385
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'calculateReductionFactors', 'onePageCalculateReductionFactors');
1386
- /** @deprecated not used outside Mode1Up, BookReader */
1387
- BookReader.prototype.resizePageView1up = Mode1Up.prototype.resizePageView;
1388
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'resizePageView', 'resizePageView1up');
1389
- /** @deprecated not used outside Mode1Up */
1390
- BookReader.prototype.onePageCalculateViewDimensions = Mode1Up.prototype.calculateViewDimensions;
1391
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'calculateViewDimensions', 'onePageCalculateViewDimensions');
1392
- /** @deprecated not used outside Mode1Up */
1393
- BookReader.prototype.centerX1up = Mode1Up.prototype.centerX;
1394
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'centerX', 'centerX1up');
1395
- /** @deprecated not used outside Mode1Up */
1396
- BookReader.prototype.centerY1up = Mode1Up.prototype.centerY;
1397
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'centerY', 'centerY1up');
1398
-
1399
- /************************/
1400
- /** Mode2Up extensions **/
1401
- /************************/
1402
- /** @deprecated not used outside Mode2Up */
1403
- BookReader.prototype.zoom2up = Mode2Up.prototype.zoom;
1404
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'zoom', 'zoom2up');
1405
- BookReader.prototype.twoPageGetAutofitReduce = Mode2Up.prototype.getAutofitReduce;
1406
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getAutofitReduce', 'twoPageGetAutofitReduce');
1407
- BookReader.prototype.flipBackToIndex = Mode2Up.prototype.flipBackToIndex;
1408
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipBackToIndex', 'flipBackToIndex');
1409
- BookReader.prototype.flipFwdToIndex = Mode2Up.prototype.flipFwdToIndex;
1410
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipFwdToIndex', 'flipFwdToIndex');
1411
- BookReader.prototype.setHilightCss2UP = Mode2Up.prototype.setHilightCss;
1412
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setHilightCss', 'setHilightCss2UP');
1413
- /** @deprecated not used outside Mode2Up */
1414
- BookReader.prototype.setClickHandler2UP = Mode2Up.prototype.setClickHandler;
1415
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setClickHandler', 'setClickHandler2UP');
1416
- /** @deprecated not used outside Mode2Up */
1417
- BookReader.prototype.drawLeafsTwoPage = Mode2Up.prototype.drawLeafs;
1418
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'drawLeafs', 'drawLeafsTwoPage');
1419
- /** @deprecated not used outside BookReader */
1420
- BookReader.prototype.prepareTwoPageView = Mode2Up.prototype.prepareTwoPageView;
1421
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'prepareTwoPageView', 'prepareTwoPageView');
1422
- /** @deprecated not used outside Mode2Up */
1423
- BookReader.prototype.prepareTwoPagePopUp = Mode2Up.prototype.preparePopUp;
1424
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'preparePopUp', 'prepareTwoPagePopUp');
1425
- /** @deprecated not used outside BookReader, Mode2Up */
1426
- BookReader.prototype.calculateSpreadSize = Mode2Up.prototype.calculateSpreadSize;
1427
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'calculateSpreadSize', 'calculateSpreadSize');
1428
- /** @deprecated not used outside BookReader, Mode2Up */
1429
- BookReader.prototype.getIdealSpreadSize = Mode2Up.prototype.getIdealSpreadSize;
1430
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getIdealSpreadSize', 'getIdealSpreadSize');
1431
- /** @deprecated not used outside BookReader, Mode2Up */
1432
- BookReader.prototype.getSpreadSizeFromReduce = Mode2Up.prototype.getSpreadSizeFromReduce;
1433
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getSpreadSizeFromReduce', 'getSpreadSizeFromReduce');
1434
- /** @deprecated unused */
1435
- BookReader.prototype.twoPageIsZoomedIn = Mode2Up.prototype.isZoomedIn;
1436
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'isZoomedIn', 'twoPageIsZoomedIn');
1437
- /** @deprecated not used outside BookReader */
1438
- BookReader.prototype.twoPageCalculateReductionFactors = Mode2Up.prototype.calculateReductionFactors;
1439
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'calculateReductionFactors', 'twoPageCalculateReductionFactors');
1440
- /** @deprecated unused */
1441
- BookReader.prototype.twoPageSetCursor = Mode2Up.prototype.setCursor;
1442
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setCursor', 'twoPageSetCursor');
1443
- /** @deprecated unused outside BookReader, Mode2Up */
1444
- BookReader.prototype.flipLeftToRight = Mode2Up.prototype.flipLeftToRight;
1445
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipLeftToRight', 'flipLeftToRight');
1446
- /** @deprecated unused outside BookReader, Mode2Up */
1447
- BookReader.prototype.flipRightToLeft = Mode2Up.prototype.flipRightToLeft;
1448
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipRightToLeft', 'flipRightToLeft');
1449
- /** @deprecated unused outside Mode2Up */
1450
- BookReader.prototype.setMouseHandlers2UP = Mode2Up.prototype.setMouseHandlers;
1451
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setMouseHandlers', 'setMouseHandlers2UP');
1452
- /** @deprecated unused outside BookReader, Mode2Up */
1453
- BookReader.prototype.prepareFlipLeftToRight = Mode2Up.prototype.prepareFlipLeftToRight;
1454
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'prepareFlipLeftToRight', 'prepareFlipLeftToRight');
1455
- /** @deprecated unused outside BookReader, Mode2Up */
1456
- BookReader.prototype.prepareFlipRightToLeft = Mode2Up.prototype.prepareFlipRightToLeft;
1457
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'prepareFlipRightToLeft', 'prepareFlipRightToLeft');
1458
- /** @deprecated unused outside Mode2Up */
1459
- BookReader.prototype.getPageWidth2UP = Mode2Up.prototype.getPageWidth;
1460
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getPageWidth', 'getPageWidth2UP');
1461
- /** @deprecated unused outside Mode2Up */
1462
- BookReader.prototype.twoPageGutter = Mode2Up.prototype.gutter;
1463
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'gutter', 'twoPageGutter');
1464
- /** @deprecated unused outside Mode2Up */
1465
- BookReader.prototype.twoPageTop = Mode2Up.prototype.top;
1466
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'top', 'twoPageTop');
1467
- /** @deprecated unused outside Mode2Up */
1468
- BookReader.prototype.twoPageCoverWidth = Mode2Up.prototype.coverWidth;
1469
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'coverWidth', 'twoPageCoverWidth');
1470
- /** @deprecated unused outside Mode2Up */
1471
- BookReader.prototype.twoPageGetViewCenter = Mode2Up.prototype.getViewCenter;
1472
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getViewCenter', 'twoPageGetViewCenter');
1473
- /** @deprecated unused outside BookReader, Mode2Up */
1474
- BookReader.prototype.twoPageCenterView = Mode2Up.prototype.centerView;
1475
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'centerView', 'twoPageCenterView');
1476
- /** @deprecated unused outside Mode2Up */
1477
- BookReader.prototype.twoPageFlipAreaHeight = Mode2Up.prototype.flipAreaHeight;
1478
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipAreaHeight', 'twoPageFlipAreaHeight');
1479
- /** @deprecated unused outside Mode2Up */
1480
- BookReader.prototype.twoPageFlipAreaWidth = Mode2Up.prototype.flipAreaWidth;
1481
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipAreaWidth', 'twoPageFlipAreaWidth');
1482
- /** @deprecated unused outside BookReader, Mode2Up */
1483
- BookReader.prototype.twoPageFlipAreaTop = Mode2Up.prototype.flipAreaTop;
1484
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipAreaTop', 'twoPageFlipAreaTop');
1485
- /** @deprecated unused outside Mode2Up */
1486
- BookReader.prototype.twoPageLeftFlipAreaLeft = Mode2Up.prototype.leftFlipAreaLeft;
1487
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'leftFlipAreaLeft', 'twoPageLeftFlipAreaLeft');
1488
- /** @deprecated unused outside Mode2Up */
1489
- BookReader.prototype.twoPageRightFlipAreaLeft = Mode2Up.prototype.rightFlipAreaLeft;
1490
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'rightFlipAreaLeft', 'twoPageRightFlipAreaLeft');
1491
- /** @deprecated unused outside BookReader, Mode2Up */
1492
- BookReader.prototype.gutterOffsetForIndex = Mode2Up.prototype.gutterOffsetForIndex;
1493
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'gutterOffsetForIndex', 'gutterOffsetForIndex');
1494
- /** @deprecated unused outside BookReader, Mode2Up */
1495
- BookReader.prototype.leafEdgeWidth = Mode2Up.prototype.leafEdgeWidth;
1496
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'leafEdgeWidth', 'leafEdgeWidth');
1497
- /** @deprecated unused outside BookReader, Mode2Up */
1498
- BookReader.prototype.jumpIndexForLeftEdgePageX = Mode2Up.prototype.jumpIndexForLeftEdgePageX;
1499
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'jumpIndexForLeftEdgePageX', 'jumpIndexForLeftEdgePageX');
1500
- /** @deprecated unused outside BookReader, Mode2Up */
1501
- BookReader.prototype.jumpIndexForRightEdgePageX = Mode2Up.prototype.jumpIndexForRightEdgePageX;
1502
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'jumpIndexForRightEdgePageX', 'jumpIndexForRightEdgePageX');
1503
- /** @deprecated unused outside Mode2Up */
1504
- BookReader.prototype.prefetch = Mode2Up.prototype.prefetch;
1505
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'prefetch', 'prefetch');
1506
- /** @deprecated unused outside Mode2Up */
1507
- BookReader.prototype.setSpreadIndices = Mode2Up.prototype.setSpreadIndices;
1508
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setSpreadIndices', 'setSpreadIndices');
1509
- /**
1510
- * Immediately stop flip animations. Callbacks are triggered.
1511
- */
1512
- BookReader.prototype.stopFlipAnimations = function() {
1513
- this.trigger(BookReader.eventNames.stop);
1514
-
1515
- // Stop animation, clear queue, trigger callbacks
1516
- if (this.leafEdgeTmp) {
1517
- $(this.leafEdgeTmp).stop(false, true);
1518
- }
1519
- jQuery.each(this._modes.mode2Up.pageContainers, function() {
1520
- $(this.$container).stop(false, true);
1521
- });
1522
-
1523
- // And again since animations also queued in callbacks
1524
- if (this.leafEdgeTmp) {
1525
- $(this.leafEdgeTmp).stop(false, true);
1526
- }
1527
- jQuery.each(this._modes.mode2Up.pageContainers, function() {
1528
- $(this.$container).stop(false, true);
1529
- });
1530
- };
1531
-
1532
- /**
1533
- * Returns true if keyboard navigation should be disabled for the event
1534
- * @param {Event}
1535
- * @return {boolean}
1536
- */
1537
- BookReader.prototype.keyboardNavigationIsDisabled = function(event) {
1538
- return event.target.tagName == "INPUT";
1539
- };
1540
1552
 
1541
1553
  /**
1542
1554
  * @template TClass extends { br: BookReader }
@@ -1560,30 +1572,9 @@ function exposeOverrideableMethod(Class, classKey, method, brMethod = method) {
1560
1572
  /***********************/
1561
1573
  /** Navbar extensions **/
1562
1574
  /***********************/
1575
+ /** This cannot be removed yet because plugin.tts.js overrides it */
1563
1576
  BookReader.prototype.initNavbar = Navbar.prototype.init;
1564
1577
  exposeOverrideableMethod(Navbar, '_components.navbar', 'init', 'initNavbar');
1565
- BookReader.prototype.switchNavbarControls = Navbar.prototype.switchNavbarControls;
1566
- exposeOverrideableMethod(Navbar, '_components.navbar', 'switchNavbarControls');
1567
- BookReader.prototype.updateViewModeButton = Navbar.prototype.updateViewModeButton;
1568
- exposeOverrideableMethod(Navbar, '_components.navbar', 'updateViewModeButton');
1569
- BookReader.prototype.getNavPageNumString = Navbar.prototype.getNavPageNumString;
1570
- exposeOverrideableMethod(Navbar, '_components.navbar', 'getNavPageNumString');
1571
- /** @deprecated */
1572
- BookReader.prototype.initEmbedNavbar = Navbar.prototype.initEmbed;
1573
- exposeOverrideableMethod(Navbar, '_components.navbar', 'initEmbed', 'initEmbedNavbar');
1574
- /** @deprecated unused */
1575
- BookReader.prototype.getNavPageNumHtml = getNavPageNumHtml;
1576
- /** @deprecated unused outside this file */
1577
- BookReader.prototype.updateNavPageNum = Navbar.prototype.updateNavPageNum;
1578
- exposeOverrideableMethod(Navbar, '_components.navbar', 'updateNavPageNum');
1579
- /** @deprecated unused outside this file */
1580
- BookReader.prototype.updateNavIndex = Navbar.prototype.updateNavIndex;
1581
- exposeOverrideableMethod(Navbar, '_components.navbar', 'updateNavIndex');
1582
- /** @deprecated unused outside this file */
1583
- BookReader.prototype.updateNavIndexThrottled = utils.throttle(BookReader.prototype.updateNavIndex, 250, false);
1584
- /** @deprecated unused */
1585
- BookReader.prototype.updateNavIndexDebounced = utils.debounce(BookReader.prototype.updateNavIndex, 500, false);
1586
-
1587
1578
 
1588
1579
  /************************/
1589
1580
  /** Toolbar extensions **/
@@ -1598,515 +1589,15 @@ BookReader.prototype.buildInfoDiv = Toolbar.prototype.buildInfoDiv;
1598
1589
  exposeOverrideableMethod(Toolbar, '_components.toolbar', 'buildInfoDiv');
1599
1590
  BookReader.prototype.getToolBarHeight = Toolbar.prototype.getToolBarHeight;
1600
1591
  exposeOverrideableMethod(Toolbar, '_components.toolbar', 'getToolBarHeight');
1601
- /** @deprecated zoom no longer in toolbar */
1602
- BookReader.prototype.updateToolbarZoom = Toolbar.prototype.updateToolbarZoom;
1603
- exposeOverrideableMethod(Toolbar, '_components.toolbar', 'updateToolbarZoom');
1604
- /** @deprecated unused */
1605
- BookReader.prototype.blankInfoDiv = blankInfoDiv;
1606
- /** @deprecated unused */
1607
- BookReader.prototype.blankShareDiv = blankShareDiv;
1608
- /** @deprecated unused */
1609
- BookReader.prototype.createPopup = createPopup;
1610
-
1611
- /**
1612
- * Bind navigation handlers
1613
- */
1614
- BookReader.prototype.bindNavigationHandlers = function() {
1615
- const self = this;
1616
-
1617
- // Note the mobile plugin attaches itself to body, so we need to select outside
1618
- const jIcons = this.$('.BRicon').add('.BRmobileMenu .BRicon');
1619
- // Map of jIcon class -> click handler
1620
- const navigationControls = {
1621
- book_left: () => {
1622
- this.trigger(BookReader.eventNames.stop);
1623
- this.left();
1624
- },
1625
- book_right: () => {
1626
- this.trigger(BookReader.eventNames.stop);
1627
- this.right();
1628
- },
1629
- book_up: () => {
1630
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1631
- this.scrollUp();
1632
- } else {
1633
- this.prev();
1634
- }
1635
- },
1636
- book_down: () => {
1637
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1638
- this.scrollDown();
1639
- } else {
1640
- this.next();
1641
- }
1642
- },
1643
- book_top: this.first.bind(this),
1644
- book_bottom: this.last.bind(this),
1645
- book_leftmost: this.leftmost.bind(this),
1646
- book_rightmost: this.rightmost.bind(this),
1647
- onepg: () => {
1648
- this.switchMode(self.constMode1up);
1649
- },
1650
- thumb: () => {
1651
- this.switchMode(self.constModeThumb);
1652
- },
1653
- twopg: () => {
1654
- this.switchMode(self.constMode2up);
1655
- },
1656
- zoom_in: () => {
1657
- this.trigger(BookReader.eventNames.stop);
1658
- this.zoom(1);
1659
- this.trigger(BookReader.eventNames.zoomIn);
1660
- },
1661
- zoom_out: () => {
1662
- this.trigger(BookReader.eventNames.stop);
1663
- this.zoom(-1);
1664
- this.trigger(BookReader.eventNames.zoomOut);
1665
- },
1666
- full: () => {
1667
- if (this.ui == 'embed') {
1668
- var url = this.$('.BRembedreturn a').attr('href');
1669
- window.open(url);
1670
- } else {
1671
- this.toggleFullscreen();
1672
- }
1673
- },
1674
- };
1675
-
1676
- jIcons.filter('.fit').bind('fit', function() {
1677
- // XXXmang implement autofit zoom
1678
- });
1679
-
1680
- for (const control in navigationControls) {
1681
- jIcons.filter(`.${control}`).on('click.bindNavigationHandlers', () => {
1682
- navigationControls[control]();
1683
- return false;
1684
- });
1685
- }
1686
-
1687
- var $brNavCntlBtmEl = this.$('.BRnavCntlBtm');
1688
- var $brNavCntlTopEl = this.$('.BRnavCntlTop');
1689
-
1690
- this.$('.BRnavCntl').click(
1691
- function() {
1692
- var promises = [];
1693
- // TODO don't use magic constants
1694
- // TODO move this to a function
1695
- if ($brNavCntlBtmEl.hasClass('BRdn')) {
1696
- if (self.refs.$BRtoolbar)
1697
- promises.push(self.refs.$BRtoolbar.animate(
1698
- {top: self.getToolBarHeight() * -1}
1699
- ).promise());
1700
- promises.push(self.$('.BRfooter').animate({bottom: self.getFooterHeight() * -1}).promise());
1701
- $brNavCntlBtmEl.addClass('BRup').removeClass('BRdn');
1702
- $brNavCntlTopEl.addClass('BRdn').removeClass('BRup');
1703
- self.$('.BRnavCntlBtm.BRnavCntl').animate({height:'45px'});
1704
- self.$('.BRnavCntl').delay(1000).animate({opacity:.75}, 1000);
1705
- } else {
1706
- if (self.refs.$BRtoolbar)
1707
- promises.push(self.refs.$BRtoolbar.animate({top:0}).promise());
1708
- promises.push(self.$('.BRfooter').animate({bottom:0}).promise());
1709
- $brNavCntlBtmEl.addClass('BRdn').removeClass('BRup');
1710
- $brNavCntlTopEl.addClass('BRup').removeClass('BRdn');
1711
- self.$('.BRnavCntlBtm.BRnavCntl').animate({height:'30px'});
1712
- self.$('.BRvavCntl').animate({opacity:1});
1713
- }
1714
- $.when.apply($, promises).done(function() {
1715
- // Only do full resize in auto mode and need to recalc. size
1716
- if (self.mode == self.constMode2up && self.twoPage.autofit != null
1717
- && self.twoPage.autofit != 'none'
1718
- ) {
1719
- self.resize();
1720
- } else if (self.mode == self.constMode1up && self.onePage.autofit != null
1721
- && self.onePage.autofit != 'none') {
1722
- self.resize();
1723
- } else {
1724
- // Don't do a full resize to avoid redrawing images
1725
- self.resizeBRcontainer();
1726
- }
1727
- });
1728
- }
1729
- );
1730
- $brNavCntlBtmEl.mouseover(function() {
1731
- if ($(this).hasClass('BRup')) {
1732
- self.$('.BRnavCntl').animate({opacity:1},250);
1733
- }
1734
- }).mouseleave(function() {
1735
- if ($(this).hasClass('BRup')) {
1736
- self.$('.BRnavCntl').animate({opacity:.75},250);
1737
- }
1738
- });
1739
- $brNavCntlTopEl.mouseover(function() {
1740
- if ($(this).hasClass('BRdn')) {
1741
- self.$('.BRnavCntl').animate({opacity:1},250);
1742
- }
1743
- }).mouseleave(function() {
1744
- if ($(this).hasClass('BRdn')) {
1745
- self.$('.BRnavCntl').animate({opacity:.75},250);
1746
- }
1747
- });
1748
-
1749
- this.initSwipeData();
1750
-
1751
- $(document).off('mousemove.navigation', this.el);
1752
- $(document).on(
1753
- 'mousemove.navigation',
1754
- this.el,
1755
- { 'br': this },
1756
- this.navigationMousemoveHandler
1757
- );
1758
-
1759
- $(document).off('mousedown.swipe', '.BRpageimage');
1760
- $(document).on(
1761
- 'mousedown.swipe',
1762
- '.BRpageimage',
1763
- { 'br': this },
1764
- this.swipeMousedownHandler
1765
- );
1766
-
1767
- this.bindMozTouchHandlers();
1768
- };
1769
-
1770
- /**
1771
- * Unbind navigation handlers
1772
- */
1773
- BookReader.prototype.unbindNavigationHandlers = function() {
1774
- $(document).off('mousemove.navigation', this.el);
1775
- };
1776
-
1777
- /**
1778
- * Handle mousemove related to navigation. Bind at #BookReader level to allow autohide.
1779
- */
1780
- BookReader.prototype.navigationMousemoveHandler = function(event) {
1781
- // $$$ possibly not great to be calling this for every mousemove
1782
- if (event.data['br'].uiAutoHide) {
1783
- // 77px is an approximate height of the Internet Archive Top Nav
1784
- // 75 & 76 (pixels) provide used in this context is checked against the IA top nav height
1785
- var navkey = $(document).height() - 75;
1786
- if ((event.pageY < 76) || (event.pageY > navkey)) {
1787
- // inside or near navigation elements
1788
- event.data['br'].hideNavigation();
1789
- } else {
1790
- event.data['br'].showNavigation();
1791
- }
1792
- }
1793
- };
1794
-
1795
- BookReader.prototype.initSwipeData = function(clientX, clientY) {
1796
- /*
1797
- * Based on the really quite awesome "Today's Guardian" at http://guardian.gyford.com/
1798
- */
1799
- this._swipe = {
1800
- mightBeSwiping: false,
1801
- didSwipe: false,
1802
- mightBeDraggin: false,
1803
- didDrag: false,
1804
- startTime: (new Date).getTime(),
1805
- startX: clientX,
1806
- startY: clientY,
1807
- lastX: clientX,
1808
- lastY: clientY,
1809
- deltaX: 0,
1810
- deltaY: 0,
1811
- deltaT: 0
1812
- };
1813
- };
1814
-
1815
- BookReader.prototype.swipeMousedownHandler = function(event) {
1816
- var self = event.data['br'];
1817
-
1818
- // We should be the last bubble point for the page images
1819
- // Disable image drag and select, but keep right-click
1820
- if (event.which == 3) {
1821
- return !self.protected;
1822
- }
1823
-
1824
- $(event.target).bind('mouseout.swipe',
1825
- { 'br': self},
1826
- self.swipeMouseupHandler
1827
- ).bind('mouseup.swipe',
1828
- { 'br': self},
1829
- self.swipeMouseupHandler
1830
- ).bind('mousemove.swipe',
1831
- { 'br': self },
1832
- self.swipeMousemoveHandler
1833
- );
1834
-
1835
- self.initSwipeData(event.clientX, event.clientY);
1836
- self._swipe.mightBeSwiping = true;
1837
- self._swipe.mightBeDragging = true;
1838
-
1839
- event.preventDefault();
1840
- event.returnValue = false;
1841
- event.cancelBubble = true;
1842
- return false;
1843
- };
1844
-
1845
- BookReader.prototype.swipeMousemoveHandler = function(event) {
1846
- var self = event.data['br'];
1847
- var _swipe = self._swipe;
1848
- if (! _swipe.mightBeSwiping) {
1849
- return;
1850
- }
1851
-
1852
- // Update swipe data
1853
- _swipe.deltaX = event.clientX - _swipe.startX;
1854
- _swipe.deltaY = event.clientY - _swipe.startY;
1855
- _swipe.deltaT = (new Date).getTime() - _swipe.startTime;
1856
-
1857
- var absX = Math.abs(_swipe.deltaX);
1858
- var absY = Math.abs(_swipe.deltaY);
1859
-
1860
- // Minimum distance in the amount of tim to trigger the swipe
1861
- var minSwipeLength = Math.min(self.refs.$br.width() / 5, 80);
1862
- var maxSwipeTime = 400;
1863
-
1864
- // Check for horizontal swipe
1865
- if (absX > absY && (absX > minSwipeLength) && _swipe.deltaT < maxSwipeTime) {
1866
- _swipe.mightBeSwiping = false; // only trigger once
1867
- _swipe.didSwipe = true;
1868
- if (self.mode == self.constMode2up) {
1869
- if (_swipe.deltaX < 0) {
1870
- self.right();
1871
- } else {
1872
- self.left();
1873
- }
1874
- }
1875
- }
1876
-
1877
- if ( _swipe.deltaT > maxSwipeTime && !_swipe.didSwipe) {
1878
- if (_swipe.mightBeDragging) {
1879
- // Dragging
1880
- _swipe.didDrag = true;
1881
- self.refs.$brContainer
1882
- .scrollTop(self.refs.$brContainer.scrollTop() - event.clientY + _swipe.lastY)
1883
- .scrollLeft(self.refs.$brContainer.scrollLeft() - event.clientX + _swipe.lastX);
1884
- }
1885
- }
1886
- _swipe.lastX = event.clientX;
1887
- _swipe.lastY = event.clientY;
1888
-
1889
- event.preventDefault();
1890
- event.returnValue = false;
1891
- event.cancelBubble = true;
1892
- return false;
1893
- };
1894
-
1895
- BookReader.prototype.swipeMouseupHandler = function(event) {
1896
- var _swipe = event.data['br']._swipe;
1897
- _swipe.mightBeSwiping = false;
1898
- _swipe.mightBeDragging = false;
1899
-
1900
- $(event.target).unbind('mouseout.swipe').unbind('mouseup.swipe').unbind('mousemove.swipe');
1901
-
1902
- if (_swipe.didSwipe || _swipe.didDrag) {
1903
- // Swallow event if completed swipe gesture
1904
- event.preventDefault();
1905
- event.returnValue = false;
1906
- event.cancelBubble = true;
1907
- return false;
1908
- }
1909
- return true;
1910
- };
1911
-
1912
- BookReader.prototype.bindMozTouchHandlers = function() {
1913
- var self = this;
1914
-
1915
- // Currently only want touch handlers in 2up
1916
- this.refs.$br.bind('MozTouchDown', function(event) {
1917
- if (this.mode == self.constMode2up) {
1918
- event.preventDefault();
1919
- }
1920
- })
1921
- .bind('MozTouchMove', function(event) {
1922
- if (this.mode == self.constMode2up) {
1923
- event.preventDefault();
1924
- }
1925
- })
1926
- .bind('MozTouchUp', function(event) {
1927
- if (this.mode == self.constMode2up) {
1928
- event.preventDefault();
1929
- }
1930
- });
1931
- };
1932
-
1933
- /**
1934
- * Returns true if the navigation elements are currently visible
1935
- * @return {boolean}
1936
- */
1937
- BookReader.prototype.navigationIsVisible = function() {
1938
- // $$$ doesn't account for transitioning states, nav must be fully visible to return true
1939
- var toolpos = this.refs.$BRtoolbar.position();
1940
- var tooltop = toolpos.top;
1941
- return tooltop == 0;
1942
- };
1943
-
1944
- /**
1945
- * Main controller that sets navigation into view.
1946
- * Defaults to SHOW the navigation chrome
1947
- */
1948
- BookReader.prototype.setNavigationView = function brSetNavigationView(hide) {
1949
- var animationLength = this.constNavAnimationDuration;
1950
- var animationType = 'linear';
1951
- var resizePageContainer = function resizePageContainer () {
1952
- /* main page container fills whole container */
1953
- if (this.constMode2up !== this.mode) {
1954
- var animate = true;
1955
- this.resizeBRcontainer(animate);
1956
- }
1957
- this.trigger(BookReader.eventNames.navToggled);
1958
- }.bind(this);
1959
-
1960
- var toolbarHeight = 0;
1961
- var navbarHeight = 0;
1962
- if (hide) {
1963
- toolbarHeight = this.getToolBarHeight() * -1;
1964
- navbarHeight = this.getFooterHeight() * -1;
1965
-
1966
- this.refs.$BRtoolbar.addClass('js-menu-hide');
1967
- this.refs.$BRfooter.addClass('js-menu-hide');
1968
- } else {
1969
- this.refs.$BRtoolbar.removeClass('js-menu-hide');
1970
- this.refs.$BRfooter.removeClass('js-menu-hide');
1971
- }
1972
-
1973
- this.refs.$BRtoolbar.animate(
1974
- { top: toolbarHeight },
1975
- animationLength,
1976
- animationType,
1977
- resizePageContainer
1978
- );
1979
- this.refs.$BRfooter.animate(
1980
- { bottom: navbarHeight },
1981
- animationLength,
1982
- animationType,
1983
- resizePageContainer
1984
- );
1985
- };
1986
- /**
1987
- * Hide navigation elements, if visible
1988
- */
1989
- BookReader.prototype.hideNavigation = function() {
1990
- // Check if navigation is showing
1991
- if (this.navigationIsVisible()) {
1992
- var hide = true;
1993
- this.setNavigationView(hide);
1994
- }
1995
- };
1996
-
1997
- /**
1998
- * Show navigation elements
1999
- */
2000
- BookReader.prototype.showNavigation = function() {
2001
- // Check if navigation is hidden
2002
- if (!this.navigationIsVisible()) {
2003
- this.setNavigationView();
2004
- }
2005
- };
2006
-
2007
- /**
2008
- * Returns the index of the first visible page, dependent on the mode.
2009
- * $$$ Currently we cannot display the front/back cover in 2-up and will need to update
2010
- * this function when we can as part of https://bugs.launchpad.net/gnubook/+bug/296788
2011
- * @return {number}
2012
- */
2013
- BookReader.prototype.firstDisplayableIndex = function() {
2014
- if (this.mode != this.constMode2up) {
2015
- return 0;
2016
- }
2017
-
2018
- if ('rl' != this.pageProgression) {
2019
- // LTR
2020
- if (this._models.book.getPageSide(0) == 'L') {
2021
- return 0;
2022
- } else {
2023
- return -1;
2024
- }
2025
- } else {
2026
- // RTL
2027
- if (this._models.book.getPageSide(0) == 'R') {
2028
- return 0;
2029
- } else {
2030
- return -1;
2031
- }
2032
- }
2033
- };
2034
-
2035
- /**
2036
- * Returns the index of the last visible page, dependent on the mode.
2037
- * $$$ Currently we cannot display the front/back cover in 2-up and will need to update
2038
- * this function when we can as part of https://bugs.launchpad.net/gnubook/+bug/296788
2039
- * @return {number}
2040
- */
2041
- BookReader.prototype.lastDisplayableIndex = function() {
2042
-
2043
- var lastIndex = this._models.book.getNumLeafs() - 1;
2044
-
2045
- if (this.mode != this.constMode2up) {
2046
- return lastIndex;
2047
- }
2048
-
2049
- if ('rl' != this.pageProgression) {
2050
- // LTR
2051
- if (this._models.book.getPageSide(lastIndex) == 'R') {
2052
- return lastIndex;
2053
- } else {
2054
- return lastIndex + 1;
2055
- }
2056
- } else {
2057
- // RTL
2058
- if (this._models.book.getPageSide(lastIndex) == 'L') {
2059
- return lastIndex;
2060
- } else {
2061
- return lastIndex + 1;
2062
- }
2063
- }
2064
- };
2065
-
2066
1592
 
2067
1593
  /**************************/
2068
1594
  /** BookModel extensions **/
2069
1595
  /**************************/
2070
- /** @deprecated not used outside */
2071
- BookReader.prototype.getMedianPageSize = BookModel.prototype.getMedianPageSize;
2072
- exposeOverrideableMethod(BookModel, '_models.book', 'getMedianPageSize');
2073
- BookReader.prototype._getPageWidth = BookModel.prototype._getPageWidth;
2074
- exposeOverrideableMethod(BookModel, '_models.book', '_getPageWidth');
2075
- BookReader.prototype._getPageHeight = BookModel.prototype._getPageHeight;
2076
- exposeOverrideableMethod(BookModel, '_models.book', '_getPageHeight');
2077
- BookReader.prototype.getPageIndex = BookModel.prototype.getPageIndex;
2078
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageIndex');
2079
- /** @deprecated not used outside */
2080
- BookReader.prototype.getPageIndices = BookModel.prototype.getPageIndices;
2081
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageIndices');
2082
- BookReader.prototype.getPageName = BookModel.prototype.getPageName;
2083
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageName');
2084
- BookReader.prototype.getNumLeafs = BookModel.prototype.getNumLeafs;
2085
- exposeOverrideableMethod(BookModel, '_models.book', 'getNumLeafs');
2086
- BookReader.prototype.getPageWidth = BookModel.prototype.getPageWidth;
2087
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageWidth');
2088
- BookReader.prototype.getPageHeight = BookModel.prototype.getPageHeight;
2089
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageHeight');
1596
+ // Must modify petabox extension, which expects this on the prototype
1597
+ // before removing.
2090
1598
  BookReader.prototype.getPageURI = BookModel.prototype.getPageURI;
2091
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageURI');
2092
- BookReader.prototype.getPageSide = BookModel.prototype.getPageSide;
2093
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageSide');
2094
- BookReader.prototype.getPageNum = BookModel.prototype.getPageNum;
2095
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageNum');
2096
- BookReader.prototype.getPageProp = BookModel.prototype.getPageProp;
2097
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageProp');
2098
- BookReader.prototype.getSpreadIndices = BookModel.prototype.getSpreadIndices;
2099
- exposeOverrideableMethod(BookModel, '_models.book', 'getSpreadIndices');
2100
- BookReader.prototype.leafNumToIndex = BookModel.prototype.leafNumToIndex;
2101
- exposeOverrideableMethod(BookModel, '_models.book', 'leafNumToIndex');
2102
- BookReader.prototype.parsePageString = BookModel.prototype.parsePageString;
2103
- exposeOverrideableMethod(BookModel, '_models.book', 'parsePageString');
2104
- /** @deprecated unused */
2105
- BookReader.prototype._getDataFlattened = BookModel.prototype._getDataFlattened;
2106
- exposeOverrideableMethod(BookModel, '_models.book', '_getDataFlattened');
2107
- /** @deprecated unused */
2108
- BookReader.prototype._getDataProp = BookModel.prototype._getDataProp;
2109
- exposeOverrideableMethod(BookModel, '_models.book', '_getDataProp');
1599
+ exposeOverrideableMethod(BookModel, 'book', 'getPageURI');
1600
+
2110
1601
 
2111
1602
  // Parameter related functions
2112
1603
 
@@ -2125,7 +1616,7 @@ BookReader.prototype.updateFromParams = function(params) {
2125
1616
  if (mode) {
2126
1617
  this.switchMode(
2127
1618
  mode,
2128
- { init: init, suppressFragmentChange: !fragmentChange }
1619
+ { init: init, suppressFragmentChange: !fragmentChange },
2129
1620
  );
2130
1621
  }
2131
1622
 
@@ -2137,7 +1628,7 @@ BookReader.prototype.updateFromParams = function(params) {
2137
1628
  }
2138
1629
  } else if ('undefined' != typeof(params.page)) {
2139
1630
  // $$$ this assumes page numbers are unique
2140
- if (params.page != this._models.book.getPageNum(this.currentIndex())) {
1631
+ if (params.page != this.book.getPageNum(this.currentIndex())) {
2141
1632
  this.jumpToPage(params.page);
2142
1633
  }
2143
1634
  }
@@ -2146,8 +1637,8 @@ BookReader.prototype.updateFromParams = function(params) {
2146
1637
  // process /search
2147
1638
  // @deprecated for urlMode 'history'
2148
1639
  // Continues to work for urlMode 'hash'
2149
- if (this.enableSearch && 'undefined' != typeof(params.search)) {
2150
- if (this.searchTerm !== params.search) {
1640
+ if (this.plugins.search?.enabled && 'undefined' != typeof(params.search)) {
1641
+ if (this.plugins.search.searchTerm !== params.search) {
2151
1642
  this.$('.BRsearchInput').val(params.search);
2152
1643
  }
2153
1644
  }
@@ -2171,7 +1662,7 @@ BookReader.prototype.canSwitchToMode = function(mode) {
2171
1662
  // check there are enough pages to display
2172
1663
  // $$$ this is a workaround for the mis-feature that we can't display
2173
1664
  // short books in 2up mode
2174
- if (this._models.book.getNumLeafs() < 2) {
1665
+ if (this.book.getNumLeafs() < 2) {
2175
1666
  return false;
2176
1667
  }
2177
1668
  }
@@ -2179,31 +1670,6 @@ BookReader.prototype.canSwitchToMode = function(mode) {
2179
1670
  return true;
2180
1671
  };
2181
1672
 
2182
-
2183
- /**
2184
- * @deprecated. Use PageModel.getURISrcSet. Slated for removal in v5.
2185
- * Returns the srcset with correct URIs or void string if out of range
2186
- * Also makes the reduce argument optional
2187
- * @param {number} index
2188
- * @param {number} [reduce]
2189
- * @param {number} [rotate]
2190
- * @return {string}
2191
- */
2192
- BookReader.prototype._getPageURISrcset = function(index, reduce, rotate) {
2193
- const page = this._models.book.getPage(index, false);
2194
- // Synthesize page
2195
- if (!page) return "";
2196
-
2197
- // reduce not passed in
2198
- // $$$ this probably won't work for thumbnail mode
2199
- if ('undefined' == typeof(reduce)) {
2200
- reduce = page.height / this.twoPage.height;
2201
- }
2202
-
2203
- return page.getURISrcSet(reduce, rotate);
2204
- };
2205
-
2206
-
2207
1673
  /**
2208
1674
  * Returns the page URI or transparent image if out of range
2209
1675
  * Also makes the reduce argument optional
@@ -2213,7 +1679,7 @@ BookReader.prototype._getPageURISrcset = function(index, reduce, rotate) {
2213
1679
  * @return {string}
2214
1680
  */
2215
1681
  BookReader.prototype._getPageURI = function(index, reduce, rotate) {
2216
- const page = this._models.book.getPage(index, false);
1682
+ const page = this.book.getPage(index, false);
2217
1683
  // Synthesize page
2218
1684
  if (!page) return this.imagesBaseURL + "transparent.png";
2219
1685
 
@@ -2252,7 +1718,7 @@ BookReader.prototype.showProgressPopup = function(msg, onCloseCallback) {
2252
1718
 
2253
1719
  const bar = document.createElement("div");
2254
1720
  $(bar).css({
2255
- height: '20px'
1721
+ height: '20px',
2256
1722
  }).prop('className', 'BRprogressbar');
2257
1723
  $(this.popup).append(bar);
2258
1724
 
@@ -2279,7 +1745,7 @@ BookReader.prototype.initUIStrings = function() {
2279
1745
  // the toolbar and nav bar easier
2280
1746
 
2281
1747
  // Setup tooltips -- later we could load these from a file for i18n
2282
- var titles = {
1748
+ const titles = {
2283
1749
  '.logo': 'Go to Archive.org', // $$$ update after getting OL record
2284
1750
  '.zoom_in': 'Zoom in',
2285
1751
  '.zoom_out': 'Zoom out',
@@ -2292,15 +1758,12 @@ BookReader.prototype.initUIStrings = function() {
2292
1758
  '.bookmark': 'Bookmark this page',
2293
1759
  '.share': 'Share this book',
2294
1760
  '.info': 'About this book',
2295
- '.full': 'Toggle fullscreen',
1761
+ '.full': 'Go fullscreen',
1762
+ '.toggle_slider': 'Toggle page controls',
2296
1763
  '.book_left': 'Flip left',
2297
1764
  '.book_right': 'Flip right',
2298
- '.book_up': 'Page up',
2299
- '.book_down': 'Page down',
2300
1765
  '.play': 'Play',
2301
1766
  '.pause': 'Pause',
2302
- '.BRdn': 'Show/hide nav bar', // Would have to keep updating on state change to have just "Hide nav bar"
2303
- '.BRup': 'Show/hide nav bar',
2304
1767
  '.book_top': 'First page',
2305
1768
  '.book_bottom': 'Last page',
2306
1769
  '.book_leftmost': 'First page',
@@ -2311,7 +1774,7 @@ BookReader.prototype.initUIStrings = function() {
2311
1774
  titles['.book_rightmost'] = 'First page';
2312
1775
  }
2313
1776
 
2314
- for (var icon in titles) {
1777
+ for (const icon in titles) {
2315
1778
  this.$(icon).prop('title', titles[icon]);
2316
1779
  }
2317
1780
  };
@@ -2322,7 +1785,7 @@ BookReader.prototype.initUIStrings = function() {
2322
1785
  BookReader.prototype.reloadImages = function() {
2323
1786
  this.refs.$brContainer.find('img').each(function(index, elem) {
2324
1787
  if (!elem.complete || elem.naturalHeight === 0) {
2325
- var src = elem.src;
1788
+ const src = elem.src;
2326
1789
  elem.src = '';
2327
1790
  setTimeout(function() {
2328
1791
  elem.src = src;
@@ -2336,10 +1799,10 @@ BookReader.prototype.reloadImages = function() {
2336
1799
  * @return {number}
2337
1800
  */
2338
1801
  BookReader.prototype.getFooterHeight = function() {
2339
- var $heightEl = this.mode == this.constMode2up ? this.refs.$BRfooter : this.refs.$BRnav;
1802
+ const $heightEl = this.mode == this.constMode2up ? this.refs.$BRfooter : this.refs.$BRnav;
2340
1803
  if ($heightEl && this.refs.$BRfooter) {
2341
- var outerHeight = $heightEl.outerHeight();
2342
- var bottom = parseInt(this.refs.$BRfooter.css('bottom'));
1804
+ const outerHeight = $heightEl.outerHeight();
1805
+ const bottom = parseInt(this.refs.$BRfooter.css('bottom'));
2343
1806
  if (!isNaN(outerHeight) && !isNaN(bottom)) {
2344
1807
  return outerHeight + bottom;
2345
1808
  }
@@ -2355,10 +1818,11 @@ BookReader.prototype.getFooterHeight = function() {
2355
1818
  * @return {Object}
2356
1819
  */
2357
1820
  BookReader.prototype.paramsFromCurrent = function() {
2358
- var params = {};
1821
+ const params = {};
2359
1822
 
2360
- var index = this.currentIndex();
2361
- var pageNum = this._models.book.getPageNum(index);
1823
+ // Path params
1824
+ const index = this.currentIndex();
1825
+ const pageNum = this.book.getPageNum(index);
2362
1826
  if ((pageNum === 0) || pageNum) {
2363
1827
  params.page = pageNum;
2364
1828
  }
@@ -2366,12 +1830,19 @@ BookReader.prototype.paramsFromCurrent = function() {
2366
1830
  params.index = index;
2367
1831
  params.mode = this.mode;
2368
1832
 
1833
+ // Unused params
2369
1834
  // $$$ highlight
2370
1835
  // $$$ region
2371
1836
 
2372
- // search
2373
- if (this.enableSearch) {
2374
- params.search = this.searchTerm;
1837
+ // Querystring params
1838
+ // View
1839
+ const fullscreenView = 'theater';
1840
+ if (this.isFullscreenActive) {
1841
+ params.view = fullscreenView;
1842
+ }
1843
+ // Search
1844
+ if (this.plugins.search?.enabled) {
1845
+ params.search = this.plugins.search.searchTerm;
2375
1846
  }
2376
1847
 
2377
1848
  return params;
@@ -2390,7 +1861,7 @@ BookReader.prototype.paramsFromCurrent = function() {
2390
1861
  * @return {Object}
2391
1862
  */
2392
1863
  BookReader.prototype.paramsFromFragment = function(fragment) {
2393
- var params = {};
1864
+ const params = {};
2394
1865
 
2395
1866
  // For backwards compatibility we allow an initial # character
2396
1867
  // (as from window.location.hash) but don't require it
@@ -2399,7 +1870,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2399
1870
  }
2400
1871
 
2401
1872
  // Simple #nn syntax
2402
- var oldStyleLeafNum = parseInt( /^\d+$/.exec(fragment) );
1873
+ const oldStyleLeafNum = parseInt( /^\d+$/.exec(fragment) );
2403
1874
  if ( !isNaN(oldStyleLeafNum) ) {
2404
1875
  params.index = oldStyleLeafNum;
2405
1876
 
@@ -2408,9 +1879,9 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2408
1879
  }
2409
1880
 
2410
1881
  // Split into key-value pairs
2411
- var urlArray = fragment.split('/');
2412
- var urlHash = {};
2413
- for (var i = 0; i < urlArray.length; i += 2) {
1882
+ const urlArray = fragment.split('/');
1883
+ const urlHash = {};
1884
+ for (let i = 0; i < urlArray.length; i += 2) {
2414
1885
  urlHash[urlArray[i]] = urlArray[i + 1];
2415
1886
  }
2416
1887
 
@@ -2426,7 +1897,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2426
1897
  // Index and page
2427
1898
  if ('undefined' != typeof(urlHash['page'])) {
2428
1899
  // page was set -- may not be int
2429
- params.page = urlHash['page'];
1900
+ params.page = decodeURIComponent(urlHash['page']);
2430
1901
  }
2431
1902
 
2432
1903
  // $$$ process /region
@@ -2458,11 +1929,10 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2458
1929
  * @return {string}
2459
1930
  */
2460
1931
  BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2461
- const separator = '/';
2462
1932
  const fragments = [];
2463
1933
 
2464
1934
  if ('undefined' != typeof(params.page)) {
2465
- fragments.push('page', params.page);
1935
+ fragments.push('page', encodeURIComponent(params.page));
2466
1936
  } else {
2467
1937
  if ('undefined' != typeof(params.index)) {
2468
1938
  // Don't have page numbering but we do have the index
@@ -2488,15 +1958,18 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2488
1958
 
2489
1959
  // search
2490
1960
  if (params.search && urlMode === 'hash') {
2491
- fragments.push('search', params.search);
1961
+ fragments.push('search', utils.encodeURIComponentPlus(params.search));
2492
1962
  }
2493
1963
 
2494
- return utils.encodeURIComponentPlus(fragments.join(separator)).replace(/%2F/g, '/');
1964
+ return fragments.join('/');
2495
1965
  };
2496
1966
 
2497
1967
  /**
2498
1968
  * Create, update querystring from the params object
2499
1969
  *
1970
+ * Handles:
1971
+ * view=
1972
+ * q=
2500
1973
  * @param {Object} params
2501
1974
  * @param {string} currQueryString
2502
1975
  * @param {string} [urlMode]
@@ -2505,30 +1978,58 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2505
1978
  BookReader.prototype.queryStringFromParams = function(
2506
1979
  params,
2507
1980
  currQueryString,
2508
- urlMode = 'hash'
1981
+ urlMode = 'hash',
2509
1982
  ) {
2510
1983
  const newParams = new URLSearchParams(currQueryString);
1984
+
1985
+ // Never write back UI-only params that are consumed on load
1986
+ // TODO: Should ideally stick around until next URL change
1987
+ newParams.delete('focus');
1988
+
1989
+ if (params.view) {
1990
+ // Set ?view=theater when fullscreen
1991
+ newParams.set('view', params.view);
1992
+ } else {
1993
+ // Remove
1994
+ newParams.delete('view');
1995
+ }
1996
+
2511
1997
  if (params.search && urlMode === 'history') {
2512
1998
  newParams.set('q', params.search);
2513
1999
  }
2514
- // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/toString
2515
- // Note: This method returns the query string without the question mark.
2000
+
2516
2001
  const result = newParams.toString();
2517
- return result ? '?' + result : '';
2002
+ return result ? `?${result}` : '';
2518
2003
  };
2519
2004
 
2520
2005
  /**
2521
- * Helper to select within instance's elements
2006
+ * @private
2007
+ * Tries to open a page by requesting the server to make it viewable.
2008
+ * @param {PageModel} page
2009
+ * @returns {Promise<PageIndex[]>} indices of the pages that became viewable
2522
2010
  */
2523
- BookReader.prototype.$ = function(selector) {
2524
- return this.refs.$br.find(selector);
2011
+ BookReader.prototype.requestPage = async function(page) {
2012
+ if (page.isViewable) return [];
2013
+ if (!this.options.requestPage) return [];
2014
+
2015
+ const newlyAvailablePages = await this.options.requestPage.call(this, page);
2016
+ for (const pageIndex of newlyAvailablePages) {
2017
+ this.book.getPage(pageIndex).makeViewable();
2018
+ }
2019
+
2020
+ // Trigger an update of book
2021
+ this._modes.mode1Up.mode1UpLit.updatePages();
2022
+ if (this.activeMode == this._modes.mode1Up) {
2023
+ await this._modes.mode1Up.mode1UpLit.updateComplete;
2024
+ }
2025
+ return newlyAvailablePages;
2525
2026
  };
2526
2027
 
2527
2028
  /**
2528
- * Polyfill for deprecated method
2029
+ * Helper to select within instance's elements
2529
2030
  */
2530
- jQuery.curCSS = function(element, prop, val) {
2531
- return jQuery(element).css(prop, val);
2031
+ BookReader.prototype.$ = function(selector) {
2032
+ return this.refs.$br.find(selector);
2532
2033
  };
2533
2034
 
2534
2035
  window.BookReader = BookReader;