@internetarchive/bookreader 5.0.0-10-alpha-3 → 5.0.0-101

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 (374) hide show
  1. package/BookReader/474.js +2 -0
  2. package/BookReader/474.js.map +1 -0
  3. package/BookReader/BookReader.css +431 -1134
  4. package/BookReader/BookReader.js +1 -1
  5. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  6. package/BookReader/BookReader.js.map +1 -1
  7. package/BookReader/bergamot-translator-worker.js +2966 -0
  8. package/BookReader/bergamot-translator-worker.wasm +0 -0
  9. package/BookReader/hypothesis/LICENSE +50 -0
  10. package/BookReader/hypothesis/README.md +55 -0
  11. package/BookReader/hypothesis/build/boot.js +1 -0
  12. package/BookReader/hypothesis/build/manifest.json +20 -0
  13. package/BookReader/hypothesis/build/scripts/annotator.bundle.js +184 -0
  14. package/BookReader/hypothesis/build/scripts/annotator.bundle.js.map +1 -0
  15. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js +798 -0
  16. package/BookReader/hypothesis/build/scripts/sidebar.bundle.js.map +1 -0
  17. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js +711 -0
  18. package/BookReader/hypothesis/build/scripts/ui-playground.bundle.js.map +1 -0
  19. package/BookReader/hypothesis/build/styles/annotator.css +2235 -0
  20. package/BookReader/hypothesis/build/styles/annotator.css.map +1 -0
  21. package/BookReader/hypothesis/build/styles/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  22. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  23. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  24. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  25. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  26. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Bold.woff2 +0 -0
  27. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  28. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Italic.woff2 +0 -0
  29. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Main-Regular.woff2 +0 -0
  30. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  31. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Math-Italic.woff2 +0 -0
  32. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  33. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  34. package/BookReader/hypothesis/build/styles/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  35. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Script-Regular.woff2 +0 -0
  36. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  37. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  38. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  39. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  40. package/BookReader/hypothesis/build/styles/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  41. package/BookReader/hypothesis/build/styles/highlights.css +2 -0
  42. package/BookReader/hypothesis/build/styles/highlights.css.map +1 -0
  43. package/BookReader/hypothesis/build/styles/katex.min.css +2 -0
  44. package/BookReader/hypothesis/build/styles/katex.min.css.map +1 -0
  45. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css +2 -0
  46. package/BookReader/hypothesis/build/styles/pdfjs-overrides.css.map +1 -0
  47. package/BookReader/hypothesis/build/styles/sidebar.css +2731 -0
  48. package/BookReader/hypothesis/build/styles/sidebar.css.map +1 -0
  49. package/BookReader/hypothesis/build/styles/ui-playground.css +2659 -0
  50. package/BookReader/hypothesis/build/styles/ui-playground.css.map +1 -0
  51. package/BookReader/hypothesis/package.json +126 -0
  52. package/BookReader/ia-bookreader-bundle.js +1782 -0
  53. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +7 -0
  54. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  55. package/BookReader/icons/1up.svg +1 -1
  56. package/BookReader/icons/2up.svg +1 -1
  57. package/BookReader/icons/advance.svg +1 -1
  58. package/BookReader/icons/chevron-right.svg +1 -1
  59. package/BookReader/icons/close-circle-dark.svg +1 -1
  60. package/BookReader/icons/close-circle.svg +1 -1
  61. package/BookReader/icons/fullscreen.svg +1 -1
  62. package/BookReader/icons/fullscreen_exit.svg +1 -1
  63. package/BookReader/icons/hamburger.svg +1 -1
  64. package/BookReader/icons/left-arrow.svg +1 -1
  65. package/BookReader/icons/magnify-minus.svg +1 -1
  66. package/BookReader/icons/magnify-plus.svg +1 -1
  67. package/BookReader/icons/magnify.svg +1 -1
  68. package/BookReader/icons/pause.svg +1 -1
  69. package/BookReader/icons/play.svg +1 -1
  70. package/BookReader/icons/playback-speed.svg +1 -1
  71. package/BookReader/icons/read-aloud.svg +1 -1
  72. package/BookReader/icons/review.svg +1 -1
  73. package/BookReader/icons/thumbnails.svg +1 -1
  74. package/BookReader/icons/voice.svg +1 -0
  75. package/BookReader/icons/volume-full.svg +1 -1
  76. package/BookReader/images/BRicons.svg +3 -3
  77. package/BookReader/images/books_graphic.svg +1 -1
  78. package/BookReader/images/hypothesis.ico +0 -0
  79. package/BookReader/images/icon_book.svg +1 -1
  80. package/BookReader/images/icon_bookmark.svg +1 -1
  81. package/BookReader/images/icon_experiment.svg +1 -0
  82. package/BookReader/images/icon_gear.svg +1 -1
  83. package/BookReader/images/icon_hamburger.svg +1 -1
  84. package/BookReader/images/icon_home.svg +1 -1
  85. package/BookReader/images/icon_info.svg +1 -1
  86. package/BookReader/images/icon_one_page.svg +1 -1
  87. package/BookReader/images/icon_pause.svg +1 -1
  88. package/BookReader/images/icon_play.svg +1 -1
  89. package/BookReader/images/icon_playback-rate.svg +1 -1
  90. package/BookReader/images/icon_search_button.svg +1 -1
  91. package/BookReader/images/icon_share.svg +1 -1
  92. package/BookReader/images/icon_skip-ahead.svg +1 -1
  93. package/BookReader/images/icon_skip-back.svg +1 -1
  94. package/BookReader/images/icon_speaker.svg +1 -1
  95. package/BookReader/images/icon_speaker_open.svg +1 -1
  96. package/BookReader/images/icon_thumbnails.svg +1 -1
  97. package/BookReader/images/icon_toc.svg +1 -1
  98. package/BookReader/images/icon_two_pages.svg +1 -1
  99. package/BookReader/images/marker_chap-off.svg +1 -1
  100. package/BookReader/images/marker_chap-on.svg +1 -1
  101. package/BookReader/images/marker_srch-on.svg +1 -1
  102. package/BookReader/images/translate.svg +1 -0
  103. package/BookReader/images/unviewable_page.png +0 -0
  104. package/BookReader/jquery-3.js +2 -0
  105. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  106. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  107. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  108. package/BookReader/plugins/plugin.autoplay.js +1 -1
  109. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  110. package/BookReader/plugins/plugin.chapters.js +25 -1
  111. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  112. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  113. package/BookReader/plugins/plugin.experiments.js +3 -0
  114. package/BookReader/plugins/plugin.experiments.js.LICENSE.txt +1 -0
  115. package/BookReader/plugins/plugin.experiments.js.map +1 -0
  116. package/BookReader/plugins/plugin.iframe.js +1 -1
  117. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  118. package/BookReader/plugins/plugin.iiif.js +2 -0
  119. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  120. package/BookReader/plugins/plugin.resume.js +1 -1
  121. package/BookReader/plugins/plugin.resume.js.map +1 -1
  122. package/BookReader/plugins/plugin.search.js +2 -1
  123. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  124. package/BookReader/plugins/plugin.search.js.map +1 -1
  125. package/BookReader/plugins/plugin.text_selection.js +2 -1
  126. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  127. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  128. package/BookReader/plugins/plugin.translate.js +137 -0
  129. package/BookReader/plugins/plugin.translate.js.LICENSE.txt +1 -0
  130. package/BookReader/plugins/plugin.translate.js.map +1 -0
  131. package/BookReader/plugins/plugin.tts.js +1 -1
  132. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  133. package/BookReader/plugins/plugin.tts.js.map +1 -1
  134. package/BookReader/plugins/plugin.url.js +1 -1
  135. package/BookReader/plugins/plugin.url.js.map +1 -1
  136. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  137. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  138. package/BookReader/plugins/translator-worker.js +2 -0
  139. package/BookReader/plugins/translator-worker.js.map +1 -0
  140. package/BookReader/silence.mp3 +0 -0
  141. package/BookReader/translator-worker.js +475 -0
  142. package/BookReader/webcomponents-bundle.js +3 -0
  143. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  144. package/BookReader/webcomponents-bundle.js.map +1 -0
  145. package/README.md +14 -3
  146. package/jsconfig.json +19 -0
  147. package/package.json +84 -64
  148. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  149. package/src/BookNavigator/assets/button-base.js +2 -1
  150. package/src/BookNavigator/assets/ia-logo.js +17 -0
  151. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  152. package/src/BookNavigator/assets/icon_close.js +1 -1
  153. package/src/BookNavigator/book-navigator.js +620 -0
  154. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  155. package/src/BookNavigator/bookmarks/bookmark-edit.js +2 -3
  156. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  157. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +2 -2
  158. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  159. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  160. package/src/BookNavigator/delete-modal-actions.js +1 -1
  161. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  162. package/src/BookNavigator/downloads/downloads.js +24 -4
  163. package/src/BookNavigator/search/search-provider.js +55 -27
  164. package/src/BookNavigator/search/search-results.js +25 -11
  165. package/src/BookNavigator/sharing.js +27 -0
  166. package/src/BookNavigator/viewable-files.js +95 -0
  167. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +13 -12
  168. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +9 -9
  169. package/src/BookReader/BookModel.js +92 -46
  170. package/src/BookReader/DragScrollable.js +233 -0
  171. package/src/BookReader/ImageCache.js +49 -16
  172. package/src/BookReader/Mode1Up.js +58 -360
  173. package/src/BookReader/Mode1UpLit.js +393 -0
  174. package/src/BookReader/Mode2Up.js +75 -1318
  175. package/src/BookReader/Mode2UpLit.js +787 -0
  176. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  177. package/src/BookReader/ModeSmoothZoom.js +312 -0
  178. package/src/BookReader/ModeThumb.js +20 -12
  179. package/src/BookReader/Navbar/Navbar.js +130 -53
  180. package/src/BookReader/PageContainer.js +120 -23
  181. package/src/BookReader/ReduceSet.js +2 -2
  182. package/src/BookReader/Toolbar/Toolbar.js +18 -40
  183. package/src/BookReader/events.js +2 -3
  184. package/src/BookReader/options.js +87 -16
  185. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  186. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  187. package/src/BookReader/utils/SelectionObserver.js +45 -0
  188. package/src/BookReader/utils/classes.js +1 -1
  189. package/src/BookReader/utils.js +136 -12
  190. package/src/BookReader.js +641 -1192
  191. package/src/BookReaderPlugin.js +52 -0
  192. package/src/assets/icons/magnify-minus.svg +3 -7
  193. package/src/assets/icons/magnify-plus.svg +3 -7
  194. package/src/assets/icons/voice.svg +1 -0
  195. package/src/assets/images/hypothesis.ico +0 -0
  196. package/src/assets/images/icon_experiment.svg +1 -0
  197. package/src/assets/images/translate.svg +1 -0
  198. package/src/assets/images/unviewable_page.png +0 -0
  199. package/src/assets/silence.mp3 +0 -0
  200. package/src/css/BookReader.scss +1 -5
  201. package/src/css/_BRBookmarks.scss +1 -1
  202. package/src/css/_BRComponent.scss +1 -1
  203. package/src/css/_BRicon.scss +8 -2
  204. package/src/css/_BRmain.scss +16 -3
  205. package/src/css/_BRnav.scss +12 -66
  206. package/src/css/_BRpages.scss +171 -42
  207. package/src/css/_BRsearch.scss +69 -30
  208. package/src/css/_BRtoolbar.scss +5 -5
  209. package/src/css/_TextSelection.scss +129 -24
  210. package/src/css/_colorbox.scss +2 -2
  211. package/src/css/_controls.scss +24 -7
  212. package/src/css/_icons.scss +1 -1
  213. package/src/ia-bookreader/ia-bookreader.js +224 -0
  214. package/src/plugins/plugin.archive_analytics.js +84 -78
  215. package/src/plugins/plugin.autoplay.js +99 -104
  216. package/src/plugins/plugin.chapters.js +314 -205
  217. package/src/plugins/plugin.experiments.js +321 -0
  218. package/src/plugins/plugin.iframe.js +1 -1
  219. package/src/plugins/plugin.iiif.js +141 -0
  220. package/src/plugins/plugin.resume.js +54 -51
  221. package/src/plugins/plugin.text_selection.js +510 -219
  222. package/src/plugins/plugin.vendor-fullscreen.js +5 -5
  223. package/src/plugins/search/plugin.search.js +370 -392
  224. package/src/plugins/search/utils.js +43 -0
  225. package/src/plugins/search/view.js +49 -67
  226. package/src/plugins/translate/TranslationManager.js +162 -0
  227. package/src/plugins/translate/plugin.translate.js +523 -0
  228. package/src/plugins/tts/AbstractTTSEngine.js +78 -49
  229. package/src/plugins/tts/FestivalTTSEngine.js +20 -30
  230. package/src/plugins/tts/PageChunk.js +33 -21
  231. package/src/plugins/tts/PageChunkIterator.js +11 -17
  232. package/src/plugins/tts/WebTTSEngine.js +131 -91
  233. package/src/plugins/tts/plugin.tts.js +344 -350
  234. package/src/plugins/tts/utils.js +49 -47
  235. package/src/plugins/url/UrlPlugin.js +191 -0
  236. package/src/plugins/{plugin.url.js → url/plugin.url.js} +44 -15
  237. package/src/util/TextSelectionManager.js +282 -0
  238. package/src/util/browserSniffing.js +33 -1
  239. package/src/util/cache.js +20 -0
  240. package/src/util/docCookies.js +21 -2
  241. package/src/util/strings.js +1 -0
  242. package/.babelrc +0 -12
  243. package/.dependabot/config.yml +0 -6
  244. package/.eslintrc.js +0 -50
  245. package/.gitattributes +0 -2
  246. package/.github/ISSUE_TEMPLATE/bug.md +0 -32
  247. package/.github/ISSUE_TEMPLATE/feature-request.md +0 -30
  248. package/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -15
  249. package/.github/workflows/node.js.yml +0 -37
  250. package/.github/workflows/npm-publish.yml +0 -47
  251. package/.testcaferc.json +0 -5
  252. package/BookReader/bookreader-component-bundle.js +0 -1450
  253. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  254. package/BookReader/bookreader-component-bundle.js.map +0 -1
  255. package/BookReader/jquery-1.10.1.js +0 -2
  256. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  257. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  258. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  259. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  260. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  261. package/BookReaderDemo/BookReaderDemo.css +0 -41
  262. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -115
  263. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  264. package/BookReaderDemo/BookReaderJSSimple.js +0 -55
  265. package/BookReaderDemo/IIIFBookReader.js +0 -207
  266. package/BookReaderDemo/assets/v5/Bookreader-logo-cool-grad.svg +0 -1
  267. package/BookReaderDemo/assets/v5/Bookreader-logo-flat.svg +0 -1
  268. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-cool-grad.png +0 -0
  269. package/BookReaderDemo/assets/v5/Bookreader-logo-hex-flat.png +0 -0
  270. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.png +0 -0
  271. package/BookReaderDemo/assets/v5/Bookreader-logo-lines.svg +0 -1
  272. package/BookReaderDemo/assets/v5/Bookreader-logo-warm.svg +0 -1
  273. package/BookReaderDemo/assets/v5/bookreader-logo-renders@1x.png +0 -0
  274. package/BookReaderDemo/assets/v5/bookreader-logo-renders@2x.png +0 -0
  275. package/BookReaderDemo/assets/v5/bookreader-v5-screenshot.png +0 -0
  276. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  277. package/BookReaderDemo/demo-advanced.html +0 -33
  278. package/BookReaderDemo/demo-autoplay.html +0 -38
  279. package/BookReaderDemo/demo-embed-iframe-src.html +0 -84
  280. package/BookReaderDemo/demo-embed.html +0 -26
  281. package/BookReaderDemo/demo-fullscreen-mobile.html +0 -36
  282. package/BookReaderDemo/demo-fullscreen.html +0 -33
  283. package/BookReaderDemo/demo-iiif.html +0 -34
  284. package/BookReaderDemo/demo-iiif.js +0 -26
  285. package/BookReaderDemo/demo-internetarchive.html +0 -74
  286. package/BookReaderDemo/demo-multiple.html +0 -43
  287. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  288. package/BookReaderDemo/demo-preview-pages.html +0 -1092
  289. package/BookReaderDemo/demo-simple.html +0 -34
  290. package/BookReaderDemo/demo-vendor-fullscreen.html +0 -36
  291. package/BookReaderDemo/immersion-1up.html +0 -64
  292. package/BookReaderDemo/immersion-mode.html +0 -35
  293. package/BookReaderDemo/toggle_controls.html +0 -53
  294. package/BookReaderDemo/view_mode.html +0 -39
  295. package/BookReaderDemo/viewmode-cycle.html +0 -41
  296. package/CHANGELOG.md +0 -493
  297. package/CONTRIBUTING.md +0 -7
  298. package/codecov.yml +0 -17
  299. package/index.html +0 -31
  300. package/karma.conf.js +0 -23
  301. package/screenshot.png +0 -0
  302. package/scripts/postversion.js +0 -10
  303. package/scripts/preversion.js +0 -14
  304. package/scripts/version.js +0 -26
  305. package/src/BookNavigator/BookModel.js +0 -14
  306. package/src/BookNavigator/BookNavigator.js +0 -446
  307. package/src/BookNavigator/assets/book-loader.js +0 -27
  308. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  309. package/src/BookNavigator/search/a-search-result.js +0 -55
  310. package/src/BookReader/DebugConsole.js +0 -54
  311. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  312. package/src/ItemNavigator/ItemNavigator.js +0 -376
  313. package/src/ItemNavigator/providers/sharing.js +0 -29
  314. package/src/css/_MobileNav.scss +0 -194
  315. package/src/dragscrollable-br.js +0 -261
  316. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  317. package/src/plugins/plugin.mobile_nav.js +0 -287
  318. package/tests/BookReader/BookModel.test.js +0 -312
  319. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  320. package/tests/BookReader/DebugConsole.test.js +0 -25
  321. package/tests/BookReader/ImageCache.test.js +0 -150
  322. package/tests/BookReader/Mode1Up.test.js +0 -164
  323. package/tests/BookReader/Mode2Up.test.js +0 -247
  324. package/tests/BookReader/Navbar/Navbar.test.js +0 -169
  325. package/tests/BookReader/PageContainer.test.js +0 -115
  326. package/tests/BookReader/ReduceSet.test.js +0 -38
  327. package/tests/BookReader/Toolbar/Toolbar.test.js +0 -26
  328. package/tests/BookReader/utils/classes.test.js +0 -88
  329. package/tests/BookReader/utils.test.js +0 -109
  330. package/tests/BookReader.options.test.js +0 -39
  331. package/tests/BookReader.test.js +0 -301
  332. package/tests/e2e/README.md +0 -75
  333. package/tests/e2e/autoplay.test.js +0 -13
  334. package/tests/e2e/base.test.js +0 -35
  335. package/tests/e2e/helpers/base.js +0 -263
  336. package/tests/e2e/helpers/debug.js +0 -13
  337. package/tests/e2e/helpers/desktopSearch.js +0 -72
  338. package/tests/e2e/helpers/mobileSearch.js +0 -85
  339. package/tests/e2e/helpers/mockSearch.js +0 -93
  340. package/tests/e2e/helpers/rightToLeft.js +0 -29
  341. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  342. package/tests/e2e/models/BookReader.js +0 -11
  343. package/tests/e2e/models/Navigation.js +0 -56
  344. package/tests/e2e/rightToLeft.test.js +0 -20
  345. package/tests/e2e/viewmode.test.js +0 -37
  346. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  347. package/tests/karma/BookNavigator/bookmarks/bookmark-edit.test.js +0 -133
  348. package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +0 -222
  349. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  350. package/tests/karma/BookNavigator/search/search-results.test.js +0 -240
  351. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  352. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  353. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  354. package/tests/plugins/plugin.autoplay.test.js +0 -52
  355. package/tests/plugins/plugin.chapters.test.js +0 -130
  356. package/tests/plugins/plugin.iframe.test.js +0 -42
  357. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  358. package/tests/plugins/plugin.resume.test.js +0 -98
  359. package/tests/plugins/plugin.text_selection.test.js +0 -203
  360. package/tests/plugins/plugin.url.test.js +0 -129
  361. package/tests/plugins/plugin.vendor-fullscreen.test.js +0 -65
  362. package/tests/plugins/search/plugin.search.test.js +0 -173
  363. package/tests/plugins/search/plugin.search.view.test.js +0 -106
  364. package/tests/plugins/tts/AbstractTTSEngine.test.js +0 -153
  365. package/tests/plugins/tts/FestivalTTSEngine.test.js +0 -59
  366. package/tests/plugins/tts/PageChunk.test.js +0 -57
  367. package/tests/plugins/tts/PageChunkIterator.test.js +0 -179
  368. package/tests/plugins/tts/WebTTSEngine.test.js +0 -126
  369. package/tests/plugins/tts/utils.test.js +0 -133
  370. package/tests/util/browserSniffing.test.js +0 -56
  371. package/tests/util/docCookies.test.js +0 -15
  372. package/tests/util/strings.test.js +0 -63
  373. package/tests/utils.js +0 -42
  374. package/webpack.config.js +0 -86
package/src/BookReader.js CHANGED
@@ -19,54 +19,77 @@ 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';
33
29
  import { exposeOverrideable } from './BookReader/utils/classes.js';
34
- import { Navbar, getNavPageNumHtml } from './BookReader/Navbar/Navbar.js';
35
- import { DEFAULT_OPTIONS } from './BookReader/options.js';
30
+ import { Navbar } from './BookReader/Navbar/Navbar.js';
31
+ import { DEFAULT_OPTIONS, OptionsParseError } from './BookReader/options.js';
36
32
  /** @typedef {import('./BookReader/options.js').BookReaderOptions} BookReaderOptions */
37
33
  /** @typedef {import('./BookReader/options.js').ReductionFactor} ReductionFactor */
38
34
  /** @typedef {import('./BookReader/BookModel.js').PageIndex} PageIndex */
39
35
  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';
36
+ import { Toolbar } from './BookReader/Toolbar/Toolbar.js';
47
37
  import { BookModel } from './BookReader/BookModel.js';
48
38
  import { Mode1Up } from './BookReader/Mode1Up.js';
49
39
  import { Mode2Up } from './BookReader/Mode2Up.js';
50
- import { ModeThumb } from './BookReader/ModeThumb';
40
+ import { ModeThumb } from './BookReader/ModeThumb.js';
51
41
  import { ImageCache } from './BookReader/ImageCache.js';
52
42
  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
- }
43
+ import { NAMED_REDUCE_SETS } from './BookReader/ReduceSet.js';
58
44
 
59
45
  /**
60
46
  * BookReader
61
- * @param {BookReaderOptions} options
47
+ * @param {Partial<BookReaderOptions>} overrides
62
48
  * TODO document all options properties
63
49
  * @constructor
64
50
  */
65
51
  export default function BookReader(overrides = {}) {
66
- const options = jQuery.extend(true, {}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
52
+ const options = BookReader.extendOptions({}, BookReader.defaultOptions, overrides, BookReader.optionOverrides);
67
53
  this.setup(options);
68
54
  }
69
55
 
56
+ /**
57
+ * Extend the default options for BookReader
58
+ * Accepts any number of option objects and merges them in order.
59
+ *
60
+ * Does a shallow merge of everything except plugin options. These are individually merged.
61
+ * @param {...Partial<BookReaderOptions>} newOptions
62
+ */
63
+ BookReader.extendOptions = function(...newOptions) {
64
+ if (newOptions.length <= 1) {
65
+ return newOptions[0];
66
+ }
67
+ /** @type {Array<keyof BookReaderOptions>} */
68
+ const shallowOverrides = ['onePage', 'twoPage', 'vars'];
69
+ /** @type {Array<keyof BookReaderOptions>} */
70
+ const mapOverrides = ['plugins', 'controls'];
71
+
72
+ const result = newOptions.shift();
73
+ for (const opts of newOptions) {
74
+ for (const [key, value] of Object.entries(opts)) {
75
+ if (shallowOverrides.includes(key)) {
76
+ result[key] ||= {};
77
+ result[key] = Object.assign(result[key], value);
78
+ } else if (mapOverrides.includes(key)) {
79
+ result[key] ||= {};
80
+ for (const [name, mapValue] of Object.entries(value)) {
81
+ result[key][name] ||= {};
82
+ Object.assign(result[key][name], mapValue);
83
+ }
84
+ } else {
85
+ result[key] = value;
86
+ }
87
+ }
88
+ }
89
+
90
+ return result;
91
+ };
92
+
70
93
  BookReader.version = PACKAGE_JSON.version;
71
94
 
72
95
  // Mode constants
@@ -76,6 +99,41 @@ BookReader.constMode1up = 1;
76
99
  BookReader.constMode2up = 2;
77
100
  /** thumbnails view */
78
101
  BookReader.constModeThumb = 3;
102
+
103
+ // Although this can actualy have any BookReaderPlugin subclass as value, we
104
+ // hardcode the known plugins here for type checking
105
+ BookReader.PLUGINS = {
106
+ /** @type {typeof import('./plugins/plugin.archive_analytics.js').ArchiveAnalyticsPlugin | null}*/
107
+ archiveAnalytics: null,
108
+ /** @type {typeof import('./plugins/plugin.autoplay.js').AutoplayPlugin | null}*/
109
+ autoplay: null,
110
+ /** @type {typeof import('./plugins/plugin.chapters.js').ChaptersPlugin | null}*/
111
+ chapters: null,
112
+ /** @type {typeof import('./plugins/plugin.experiments.js').ExperimentsPlugin | null}*/
113
+ experiments: null,
114
+ /** @type {typeof import('./plugins/plugin.resume.js').ResumePlugin | null}*/
115
+ resume: null,
116
+ /** @type {typeof import('./plugins/search/plugin.search.js').SearchPlugin | null}*/
117
+ search: null,
118
+ /** @type {typeof import('./plugins/plugin.text_selection.js').TextSelectionPlugin | null}*/
119
+ textSelection: null,
120
+ /** @type {typeof import('./plugins/translate/plugin.translate.js').TranslatePlugin | null}*/
121
+ translate: null,
122
+ /** @type {typeof import('./plugins/tts/plugin.tts.js').TtsPlugin | null}*/
123
+ tts: null,
124
+ };
125
+
126
+ /**
127
+ * @param {string} pluginName
128
+ * @param {new (...args: any[]) => import('./BookReaderPlugin.js').BookReaderPlugin} plugin
129
+ */
130
+ BookReader.registerPlugin = function(pluginName, plugin) {
131
+ if (BookReader.PLUGINS[pluginName]) {
132
+ console.warn(`Plugin ${pluginName} already registered. Overwriting.`);
133
+ }
134
+ BookReader.PLUGINS[pluginName] = plugin;
135
+ };
136
+
79
137
  /** image cache */
80
138
  BookReader.imageCache = null;
81
139
 
@@ -103,12 +161,63 @@ BookReader.prototype.setup = function(options) {
103
161
  // Store the options used to setup bookreader
104
162
  this.options = options;
105
163
 
164
+ /** @type {import('@/src/BookNavigator/book-navigator.js').BookNavigator} */
165
+ this.shell;
166
+
167
+ // Base server used by some api calls
168
+ /** @deprecated */
169
+ this.bookId = options.bookId;
170
+ /** @deprecated */
171
+ this.server = options.server;
172
+ /** @deprecated */
173
+ this.subPrefix = options.subPrefix;
174
+ /** @deprecated */
175
+ this.bookPath = options.bookPath;
176
+
177
+ // Construct the usual plugins first to get type hints
178
+ this.plugins = {
179
+ archiveAnalytics: BookReader.PLUGINS.archiveAnalytics ? new BookReader.PLUGINS.archiveAnalytics(this) : null,
180
+ autoplay: BookReader.PLUGINS.autoplay ? new BookReader.PLUGINS.autoplay(this) : null,
181
+ chapters: BookReader.PLUGINS.chapters ? new BookReader.PLUGINS.chapters(this) : null,
182
+ experiments: BookReader.PLUGINS.experiments ? new BookReader.PLUGINS.experiments(this) : null,
183
+ search: BookReader.PLUGINS.search ? new BookReader.PLUGINS.search(this) : null,
184
+ resume: BookReader.PLUGINS.resume ? new BookReader.PLUGINS.resume(this) : null,
185
+ textSelection: BookReader.PLUGINS.textSelection ? new BookReader.PLUGINS.textSelection(this) : null,
186
+ translate: BookReader.PLUGINS.translate ? new BookReader.PLUGINS.translate(this) : null,
187
+ tts: BookReader.PLUGINS.tts ? new BookReader.PLUGINS.tts(this) : null,
188
+ };
189
+
190
+ // Delete anything that's null
191
+ for (const [pluginName, plugin] of Object.entries(this.plugins)) {
192
+ if (!plugin) delete this.plugins[pluginName];
193
+ }
194
+
195
+ // Now construct the rest of the plugins
196
+ for (const [pluginName, PluginClass] of Object.entries(BookReader.PLUGINS)) {
197
+ if (this.plugins[pluginName] || !PluginClass) continue;
198
+ this.plugins[pluginName] = new PluginClass(this);
199
+ }
200
+
201
+ // And call setup on them
202
+ for (const [pluginName, plugin] of Object.entries(this.plugins)) {
203
+ try {
204
+ plugin.setup(this.options.plugins?.[pluginName] ?? {});
205
+ // Write the options back; this way the plugin is the source of truth,
206
+ // and BR just contains a reference to it.
207
+ this.options.plugins[pluginName] = plugin.options;
208
+ } catch (e) {
209
+ console.error(`Error setting up plugin ${pluginName}`, e);
210
+ }
211
+ }
212
+
213
+ if (this.plugins.search?.options.enabled) {
214
+ // Expose the search method for convenience / backward compat
215
+ this.search = this.plugins.search.search.bind(this.plugins.search);
216
+ }
217
+
106
218
  /** @type {number} @deprecated some past iterations set this */
107
219
  this.numLeafs = undefined;
108
220
 
109
- /** Overridden by plugin.search.js */
110
- this.enableSearch = false;
111
-
112
221
  /**
113
222
  * Store viewModeOrder states
114
223
  * @var {boolean}
@@ -152,24 +261,16 @@ BookReader.prototype.setup = function(options) {
152
261
  this.ui = options.ui;
153
262
  this.uiAutoHide = options.uiAutoHide;
154
263
 
155
- this.thumbWidth = 100; // will be overridden during prepareThumbnailView
264
+ this.thumbWidth = 100; // will be overridden during this._modes.modeThumb.prepare();
156
265
  this.thumbRowBuffer = options.thumbRowBuffer;
157
266
  this.thumbColumns = options.thumbColumns;
158
267
  this.thumbMaxLoading = options.thumbMaxLoading;
159
268
  this.thumbPadding = options.thumbPadding;
160
269
  this.displayedRows = [];
161
-
162
270
  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
271
 
168
272
  this.animating = false;
169
- this.flipSpeed = options.flipSpeed;
170
- this.flipDelay = options.flipDelay;
171
- this.twoPagePopUp = null;
172
- this.leafEdgeTmp = null;
273
+ this.flipSpeed = utils.parseAnimationSpeed(options.flipSpeed) || 400;
173
274
 
174
275
  /**
175
276
  * Represents the first displayed index
@@ -178,8 +279,7 @@ BookReader.prototype.setup = function(options) {
178
279
  * @property {number|null} firstIndex
179
280
  */
180
281
  this.firstIndex = null;
181
- this.lastDisplayableIndex2up = null;
182
- this.isFullscreenActive = false;
282
+ this.isFullscreenActive = options.startFullscreen || false;
183
283
  this.lastScroll = null;
184
284
 
185
285
  this.showLogo = options.showLogo;
@@ -212,26 +312,22 @@ BookReader.prototype.setup = function(options) {
212
312
 
213
313
  // Assign the data methods
214
314
  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
315
 
225
316
  /** @type {{[name: string]: JQuery}} */
226
317
  this.refs = {};
227
318
 
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
- };
319
+ /** The book being displayed in BookReader*/
320
+ this.book = new BookModel(this);
321
+
322
+ if (options.getNumLeafs) this.book.getNumLeafs = options.getNumLeafs.bind(this);
323
+ if (options.getPageWidth) this.book.getPageWidth = options.getPageWidth.bind(this);
324
+ if (options.getPageHeight) this.book.getPageHeight = options.getPageHeight.bind(this);
325
+ if (options.getPageURI) this.book.getPageURI = options.getPageURI.bind(this);
326
+ if (options.getPageSide) this.book.getPageSide = options.getPageSide.bind(this);
327
+ if (options.getPageNum) this.book.getPageNum = options.getPageNum.bind(this);
328
+ if (options.getPageProp) this.book.getPageProp = options.getPageProp.bind(this);
329
+ if (options.getSpreadIndices) this.book.getSpreadIndices = options.getSpreadIndices.bind(this);
330
+ if (options.leafNumToIndex) this.book.leafNumToIndex = options.leafNumToIndex.bind(this);
235
331
 
236
332
  /**
237
333
  * @private Components are 'subchunks' of bookreader functionality, usually UI related
@@ -245,14 +341,14 @@ BookReader.prototype.setup = function(options) {
245
341
  };
246
342
 
247
343
  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),
344
+ mode1Up: new Mode1Up(this, this.book),
345
+ mode2Up: new Mode2Up(this, this.book),
346
+ modeThumb: new ModeThumb(this, this.book),
251
347
  };
252
348
 
253
349
  /** Stores classes which we want to expose (selectively) some methods as overridable */
254
350
  this._overrideable = {
255
- '_models.book': this._models.book,
351
+ 'book': this.book,
256
352
  '_components.navbar': this._components.navbar,
257
353
  '_components.toolbar': this._components.toolbar,
258
354
  '_modes.mode1Up': this._modes.mode1Up,
@@ -261,21 +357,74 @@ BookReader.prototype.setup = function(options) {
261
357
  };
262
358
 
263
359
  /** Image cache for general image fetching */
264
- this.imageCache = new ImageCache(this._models.book, {
360
+ this.imageCache = new ImageCache(this.book, {
265
361
  useSrcSet: this.options.useSrcSet,
266
362
  reduceSet: this.reduceSet,
363
+ renderPageURI: options.renderPageURI.bind(this),
267
364
  });
365
+
366
+ /**
367
+ * Flag if BookReader has "focus" for keyboard shortcuts
368
+ * Initially true, set to false when:
369
+ * - BookReader scrolled out of view
370
+ * Set to true when:
371
+ * - BookReader scrolled into view
372
+ */
373
+ this.hasKeyFocus = true;
268
374
  };
269
375
 
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; }
376
+ BookReader.prototype.initializePlugin = function(pluginName) {
377
+ const plugin = new BookReader.PLUGINS[pluginName](this);
378
+ this.plugins[pluginName] = plugin;
379
+ try {
380
+ plugin.setup(this.options.plugins?.[pluginName] ?? {});
381
+ this.options.plugins[pluginName] = plugin.options;
382
+ } catch (e) {
383
+ console.error(`Error setting up plugin ${pluginName} outside of regular cycle`, e);
384
+ throw e;
385
+ }
386
+
387
+ try {
388
+ plugin.init();
389
+ } catch (e) {
390
+ console.error(`Error initializing plugin ${pluginName} outside of regular cycle`, e);
391
+ throw e;
392
+ }
393
+ };
394
+ /**
395
+ * Get all the HTML Elements that are being/can be rendered.
396
+ * Includes cached elements which might be rendered again.
397
+ */
398
+ BookReader.prototype.getActivePageContainerElements = function() {
399
+ let containerEls = Object.values(this._modes.mode2Up.mode2UpLit.pageContainerCache).map(pc => pc.$container[0])
400
+ .concat(Object.values(this._modes.mode1Up.mode1UpLit.pageContainerCache).map(pc => pc.$container[0]));
401
+ if (this.mode == this.constModeThumb) {
402
+ containerEls = containerEls.concat(this.$('.BRpagecontainer').toArray());
403
+ }
404
+ return containerEls;
405
+ };
406
+
407
+ /**
408
+ * Get the HTML Elements for the rendered page. Note there can be more than one, since
409
+ * (at least as of writing) different modes can maintain different caches.
410
+ * @param {PageIndex} pageIndex
411
+ * @returns {HTMLElement[]}
412
+ */
413
+ BookReader.prototype.getActivePageContainerElementsForIndex = function(pageIndex) {
414
+ return [
415
+ this._modes.mode2Up.mode2UpLit.pageContainerCache[pageIndex]?.$container?.[0],
416
+ this._modes.mode1Up.mode1UpLit.pageContainerCache[pageIndex]?.$container?.[0],
417
+ ...(this.mode == this.constModeThumb ? this.$(`.pagediv${pageIndex}`).toArray() : []),
418
+ ].filter(x => x);
419
+ };
420
+
421
+ Object.defineProperty(BookReader.prototype, 'activeMode', {
422
+ /** @return {Mode1Up | Mode2Up | ModeThumb} */
423
+ get() { return {
424
+ 1: this._modes.mode1Up,
425
+ 2: this._modes.mode2Up,
426
+ 3: this._modes.modeThumb,
427
+ }[this.mode]; },
279
428
  });
280
429
 
281
430
  /**
@@ -290,9 +439,9 @@ BookReader.util = utils;
290
439
  * @private
291
440
  */
292
441
  BookReader.prototype.extendParams = function(params, newParams) {
293
- var modifiedNewParams = $.extend({}, newParams);
442
+ const modifiedNewParams = $.extend({}, newParams);
294
443
  if ('undefined' != typeof(modifiedNewParams.page)) {
295
- var pageIndex = this._models.book.parsePageString(modifiedNewParams.page);
444
+ const pageIndex = this.book.parsePageString(modifiedNewParams.page);
296
445
  if (!isNaN(pageIndex))
297
446
  modifiedNewParams.index = pageIndex;
298
447
  delete modifiedNewParams.page;
@@ -306,7 +455,7 @@ BookReader.prototype.extendParams = function(params, newParams) {
306
455
  * @return {object} the parsed params
307
456
  */
308
457
  BookReader.prototype.initParams = function() {
309
- var params = {};
458
+ const params = {};
310
459
  // Flag initializing for updateFromParams()
311
460
  params.init = true;
312
461
 
@@ -323,8 +472,8 @@ BookReader.prototype.initParams = function() {
323
472
 
324
473
  // If we have a title leaf, use that as the default instead of index 0,
325
474
  // 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);
475
+ if ('undefined' != typeof(this.titleLeaf) && this.book.getNumLeafs() > 2) {
476
+ params.index = this.book.leafNumToIndex(this.titleLeaf);
328
477
  } else {
329
478
  params.index = 0;
330
479
  }
@@ -339,9 +488,9 @@ BookReader.prototype.initParams = function() {
339
488
  }
340
489
 
341
490
  // Check for Resume plugin
342
- if (this.options.enablePageResume) {
491
+ if (this.plugins.resume?.options.enabled) {
343
492
  // Check cookies
344
- const val = this.getResumeValue();
493
+ const val = this.plugins.resume.getResumeValue();
345
494
  if (val !== null) {
346
495
  // If page index different from default
347
496
  if (params.index !== val) {
@@ -355,7 +504,7 @@ BookReader.prototype.initParams = function() {
355
504
  // Check for URL plugin
356
505
  if (this.options.enableUrlPlugin) {
357
506
  // Params explicitly set in URL take precedence over all other methods
358
- var urlParams = this.paramsFromFragment(this.urlReadFragment());
507
+ let urlParams = this.paramsFromFragment(this.urlReadFragment());
359
508
 
360
509
  // Get params if hash fragment available with 'history' urlMode
361
510
  const hasHashURL = !Object.keys(urlParams).length && this.urlReadHashFragment();
@@ -375,23 +524,24 @@ BookReader.prototype.initParams = function() {
375
524
  }
376
525
 
377
526
  // Check for Search plugin
378
- if (this.options.enableSearch) {
527
+ if (this.plugins.search?.options.enabled) {
528
+ const sp = this.plugins.search;
379
529
  // Go to first result only if no default or URL page
380
- this.options.goToFirstResult = !params.pageFound;
530
+ sp.options.goToFirstResult = !params.pageFound;
381
531
 
382
532
  // If initialSearchTerm not set
383
- if (!this.options.initialSearchTerm) {
533
+ if (!sp.initialSearchTerm) {
384
534
  // Look for any term in URL
385
535
  if (params.search) {
386
536
  // Old style: /search/[term]
387
- this.options.initialSearchTerm = params.search;
388
- this.searchTerm = params.search;
537
+ sp.options.initialSearchTerm = params.search;
538
+ sp.searchTerm = params.search;
389
539
  } else {
390
540
  // If we have a query string: q=[term]
391
541
  const searchParams = new URLSearchParams(this.readQueryString());
392
542
  const searchTerm = searchParams.get('q');
393
543
  if (searchTerm) {
394
- this.options.initialSearchTerm = utils.decodeURIComponentPlus(searchTerm);
544
+ sp.options.initialSearchTerm = utils.decodeURIComponentPlus(searchTerm);
395
545
  }
396
546
  }
397
547
  }
@@ -434,31 +584,57 @@ BookReader.prototype.readQueryString = function() {
434
584
  * Determines the initial mode for starting if a mode is not already
435
585
  * present in the params argument
436
586
  * @param {object} params
437
- * @return {number} the mode
587
+ * @return {1 | 2 | 3} the initial mode
438
588
  */
439
589
  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;
590
+ // if mobile breakpoint, we always show this.constMode1up mode
591
+ const windowWidth = $(window).width();
592
+ const isMobile = windowWidth && windowWidth <= this.onePageMinBreakpoint;
593
+
594
+ let initialMode;
595
+ if (params.mode) {
596
+ initialMode = params.mode;
597
+ } else if (isMobile) {
598
+ initialMode = this.constMode1up;
452
599
  } else {
453
- nextMode = this.constMode2up;
600
+ initialMode = this.constMode2up;
601
+ }
602
+
603
+ if (!this.canSwitchToMode(initialMode)) {
604
+ initialMode = this.constMode1up;
454
605
  }
455
606
 
456
- if (!this.canSwitchToMode(nextMode)) {
457
- nextMode = this.constMode1up;
607
+ // override defaults mode via `options.defaults` metadata
608
+ if (this.options.defaults) {
609
+ try {
610
+ initialMode = _modeStringToNumber(this.options.defaults);
611
+ } catch (e) {
612
+ // Can ignore this error
613
+ }
458
614
  }
459
- return nextMode;
615
+
616
+ return initialMode;
460
617
  };
461
618
 
619
+ /**
620
+ * Converts a mode string to a the mode numeric constant
621
+ * @param {'mode/1up'|'mode/2up'|'mode/thumb'} modeString
622
+ * @return {1 | 2 | 3}
623
+ */
624
+ export function _modeStringToNumber(modeString) {
625
+ const MAPPING = {
626
+ 'mode/1up': 1,
627
+ 'mode/2up': 2,
628
+ 'mode/thumb': 3,
629
+ };
630
+
631
+ if (!(modeString in MAPPING)) {
632
+ throw new OptionsParseError(`Invalid mode string: ${modeString}`);
633
+ }
634
+
635
+ return MAPPING[modeString];
636
+ }
637
+
462
638
  /**
463
639
  * This is called by the client to initialize BookReader.
464
640
  * It renders onto the DOM. It should only be called once.
@@ -467,7 +643,7 @@ BookReader.prototype.init = function() {
467
643
  this.init.initComplete = false;
468
644
  this.pageScale = this.reduce; // preserve current reduce
469
645
 
470
- var params = this.initParams();
646
+ const params = this.initParams();
471
647
 
472
648
  this.firstIndex = params.index ? params.index : 0;
473
649
 
@@ -497,41 +673,45 @@ BookReader.prototype.init = function() {
497
673
  // Explicitly ensure this.mode exists for initNavbar() below
498
674
  this.mode = params.mode;
499
675
 
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();
676
+ // Display Navigation
677
+ if (this.options.showToolbar) {
678
+ this.initToolbar(this.mode, this.ui); // Build inside of toolbar div
679
+ }
680
+ if (this.options.showNavbar) { // default navigation
681
+ const $navBar = this.initNavbar();
682
+
683
+ // extend navbar with plugins
684
+ for (const plugin of Object.values(this.plugins)) {
685
+ plugin.extendNavBar($navBar);
508
686
  }
509
687
  }
510
688
 
511
689
  // Switch navbar controls on mobile/desktop
512
- this.switchNavbarControls();
690
+ this._components.navbar.switchNavbarControls();
513
691
 
514
692
  this.resizeBRcontainer();
515
693
  this.updateFromParams(params);
516
694
  this.initUIStrings();
517
695
 
518
696
  // Bind to events
519
-
520
- this.bindNavigationHandlers();
697
+ this._components.navbar.bindControlClickHandlers();
698
+ for (const plugin of Object.values(this.plugins)) {
699
+ plugin._bindNavigationHandlers();
700
+ }
521
701
  this.setupKeyListeners();
522
702
 
523
703
  this.lastScroll = (new Date().getTime());
524
- this.refs.$brContainer.bind('scroll', this, function(e) {
704
+ this.refs.$brContainer.on('scroll', this, function(e) {
525
705
  // Note, this scroll event fires for both user, and js generated calls
526
706
  // It is functioning in some cases as the primary triggerer for rendering
527
707
  e.data.lastScroll = (new Date().getTime());
528
- if (e.data.constMode2up != e.data.mode) {
708
+ if (e.data.constModeThumb == e.data.mode) {
529
709
  e.data.drawLeafsThrottled();
530
710
  }
531
711
  });
532
712
 
533
713
  if (this.options.autoResize) {
534
- $(window).bind('resize', this, function(e) {
714
+ $(window).on('resize', this, function(e) {
535
715
  e.data.resize();
536
716
  });
537
717
  $(window).on("orientationchange", this, function(e) {
@@ -544,18 +724,30 @@ BookReader.prototype.init = function() {
544
724
  }
545
725
 
546
726
  // If not searching, set to allow on-going fragment changes
547
- if (!this.options.initialSearchTerm) {
727
+ if (!this.plugins.search?.options.initialSearchTerm) {
548
728
  this.suppressFragmentChange = false;
549
729
  }
550
730
 
731
+ if (this.options.startFullscreen) {
732
+ this.enterFullscreen(true);
733
+ }
734
+
735
+ // Init plugins
736
+ for (const [pluginName, plugin] of Object.entries(this.plugins)) {
737
+ try {
738
+ plugin.init();
739
+ }
740
+ catch (e) {
741
+ console.error(`Error initializing plugin ${pluginName}`, e);
742
+ }
743
+ }
744
+
551
745
  this.init.initComplete = true;
552
746
  this.trigger(BookReader.eventNames.PostInit);
553
747
 
554
748
  // Must be called after this.init.initComplete set to true to allow
555
749
  // BookReader.prototype.resize to run.
556
- if (this.options.startFullscreen) {
557
- this.toggleFullscreen();
558
- }
750
+
559
751
  };
560
752
 
561
753
  /**
@@ -564,7 +756,6 @@ BookReader.prototype.init = function() {
564
756
  */
565
757
  BookReader.prototype.trigger = function(name, props = this) {
566
758
  const eventName = 'BookReader:' + name;
567
- $(document).trigger(eventName, props);
568
759
 
569
760
  utils.polyfillCustomEvent(window);
570
761
  window.dispatchEvent(new CustomEvent(eventName, {
@@ -572,14 +763,29 @@ BookReader.prototype.trigger = function(name, props = this) {
572
763
  composed: true,
573
764
  detail: { props },
574
765
  }));
766
+ $(document).trigger(eventName, props);
575
767
  };
576
768
 
769
+ BookReader.prototype.on = function(name, callback) {
770
+ $(document).on('BookReader:' + name, callback);
771
+ };
772
+
773
+ BookReader.prototype.off = function(name, callback) {
774
+ $(document).off('BookReader:' + name, callback);
775
+ };
776
+
777
+ /**
778
+ * @deprecated Use .on and .off instead
779
+ */
577
780
  BookReader.prototype.bind = function(name, callback) {
578
- $(document).bind('BookReader:' + name, callback);
781
+ return this.on(name, callback);
579
782
  };
580
783
 
784
+ /**
785
+ * @deprecated Use .on and .off instead
786
+ */
581
787
  BookReader.prototype.unbind = function(name, callback) {
582
- $(document).unbind('BookReader:' + name, callback);
788
+ return this.off(name, callback);
583
789
  };
584
790
 
585
791
  /**
@@ -591,135 +797,147 @@ BookReader.prototype.resize = function() {
591
797
  this.resizeBRcontainer();
592
798
 
593
799
  // Switch navbar controls on mobile/desktop
594
- this.switchNavbarControls();
800
+ this._components.navbar.switchNavbarControls();
595
801
 
596
802
  if (this.constMode1up == this.mode) {
597
803
  if (this.onePage.autofit != 'none') {
598
- this.resizePageView1up();
804
+ this._modes.mode1Up.resizePageView();
599
805
  this.centerPageView();
600
- if (this.enableSearch) this.updateSearchHilites(); //deletes highlights but does not call remove()
601
806
  } else {
602
807
  this.centerPageView();
603
808
  this.displayedIndices = [];
604
- if (this.enableSearch) this.updateSearchHilites(); //deletes highlights but does not call remove()
605
809
  this.drawLeafsThrottled();
606
810
  }
607
811
  } else if (this.constModeThumb == this.mode) {
608
- this.prepareThumbnailView();
812
+ this._modes.modeThumb.prepare();
609
813
  } 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
- }
814
+ this._modes.mode2Up.resizePageView();
631
815
  }
632
816
  this.trigger(BookReader.eventNames.resize);
633
817
  };
634
818
 
635
819
  /**
636
- * Binds keyboard event listeners
820
+ * Binds keyboard and keyboard focus event listeners
637
821
  */
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();
822
+ BookReader.prototype.setupKeyListeners = function () {
823
+
824
+ // Keyboard focus by BookReader in viewport
825
+ //
826
+ // Intersection observer and callback sets BookReader keyboard
827
+ // "focus" flag off when the BookReader is not in the viewport.
828
+ if (window.IntersectionObserver) {
829
+ const observer = new IntersectionObserver((entries) => {
830
+ entries.forEach((entry) => {
831
+ if (entry.intersectionRatio === 0) {
832
+ this.hasKeyFocus = false;
833
+ } else {
834
+ this.hasKeyFocus = true;
677
835
  }
678
- break;
679
- case KEY_END:
836
+ });
837
+ }, {
838
+ root: null,
839
+ rootMargin: '0px',
840
+ threshold: [0, 0.05, 1],
841
+ });
842
+ observer.observe(this.refs.$br[0]);
843
+ }
844
+
845
+ // Keyboard listeners
846
+ document.addEventListener('keydown', (e) => {
847
+
848
+ // Ignore if BookReader "focus" flag not set
849
+ if (!this.hasKeyFocus) {
850
+ return;
851
+ }
852
+
853
+ // Ignore if modifiers are active.
854
+ if (e.getModifierState('Control') ||
855
+ e.getModifierState('Alt') ||
856
+ e.getModifierState('Meta') ||
857
+ e.getModifierState('Win') /* hack for IE */) {
858
+ return;
859
+ }
860
+
861
+ // Ignore in input elements
862
+ if (utils.isInputActive()) {
863
+ return;
864
+ }
865
+
866
+ // KeyboardEvent code values:
867
+ // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
868
+ switch (e.key) {
869
+
870
+ // Page navigation
871
+ case "Home":
872
+ e.preventDefault();
873
+ this.first();
874
+ break;
875
+ case "End":
876
+ e.preventDefault();
877
+ this.last();
878
+ break;
879
+ case "ArrowDown":
880
+ case "PageDown":
881
+ case "Down": // hack for IE and old Gecko
882
+ // In 1up and thumb mode page scrolling handled by browser
883
+ if (this.constMode2up === this.mode) {
680
884
  e.preventDefault();
681
- self.last();
682
- break;
683
- case KEY_HOME:
885
+ this.next();
886
+ }
887
+ break;
888
+ case "ArrowUp":
889
+ case "PageUp":
890
+ case "Up": // hack for IE and old Gecko
891
+ // In 1up and thumb mode page scrolling handled by browser
892
+ if (this.constMode2up === this.mode) {
684
893
  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:
894
+ this.prev();
895
+ }
896
+ break;
897
+ case "ArrowLeft":
898
+ case "Left": // hack for IE and old Gecko
899
+ // No y-scrolling in thumb mode
900
+ if (this.constModeThumb != this.mode) {
702
901
  e.preventDefault();
703
- self.zoom(-1);
704
- break;
705
- case KEY_EQUAL:
706
- case KEY_EQUAL_F:
707
- case KEY_NUMPAD_ADD:
902
+ this.left();
903
+ }
904
+ break;
905
+ case "ArrowRight":
906
+ case "Right": // hack for IE and old Gecko
907
+ // No y-scrolling in thumb mode
908
+ if (this.constModeThumb != this.mode) {
708
909
  e.preventDefault();
709
- self.zoom(+1);
710
- break;
910
+ this.right();
711
911
  }
912
+ break;
913
+ // Zoom
914
+ case '-':
915
+ case 'Subtract':
916
+ e.preventDefault();
917
+ this.zoom(-1);
918
+ break;
919
+ case '+':
920
+ case '=':
921
+ case 'Add':
922
+ e.preventDefault();
923
+ this.zoom(1);
924
+ break;
925
+ // Fullscreen
926
+ case 'F':
927
+ case 'f':
928
+ e.preventDefault();
929
+ this.toggleFullscreen();
930
+ break;
712
931
  }
713
932
  });
714
933
  };
715
934
 
716
935
  BookReader.prototype.drawLeafs = function() {
717
936
  if (this.constMode1up == this.mode) {
718
- this.drawLeafsOnePage();
719
- } else if (this.constModeThumb == this.mode) {
720
- this.drawLeafsThumbnail();
937
+ // Not needed for Mode1Up anymore
938
+ return;
721
939
  } else {
722
- this.drawLeafsTwoPage();
940
+ this.activeMode.drawLeafs();
723
941
  }
724
942
  };
725
943
 
@@ -728,11 +946,17 @@ BookReader.prototype.drawLeafs = function() {
728
946
  * @param {PageIndex} index
729
947
  */
730
948
  BookReader.prototype._createPageContainer = function(index) {
731
- return new PageContainer(this._models.book.getPage(index, false), {
949
+ const pageContainer = new PageContainer(this.book.getPage(index, false), {
732
950
  isProtected: this.protected,
733
951
  imageCache: this.imageCache,
734
- loadingImage: this.imagesBaseURL + 'loading.gif',
735
952
  });
953
+
954
+ // Call plugin handlers
955
+ for (const plugin of Object.values(this.plugins)) {
956
+ plugin._configurePageContainer(pageContainer);
957
+ }
958
+
959
+ return pageContainer;
736
960
  };
737
961
 
738
962
  BookReader.prototype.bindGestures = function(jElement) {
@@ -742,8 +966,8 @@ BookReader.prototype.bindGestures = function(jElement) {
742
966
  // when you move the book with one finger and then add another
743
967
  // finger to pinch. Gestures are aware of scroll state.
744
968
 
745
- var self = this;
746
- var numTouches = 1;
969
+ const self = this;
970
+ let numTouches = 1;
747
971
 
748
972
  jElement.unbind('touchmove').bind('touchmove', function(e) {
749
973
  if (e.originalEvent.cancelable) numTouches = e.originalEvent.touches.length;
@@ -764,60 +988,25 @@ BookReader.prototype.bindGestures = function(jElement) {
764
988
  });
765
989
  };
766
990
 
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
991
  /**
786
992
  * A throttled version of drawLeafs
787
993
  */
788
994
  BookReader.prototype.drawLeafsThrottled = utils.throttle(
789
995
  BookReader.prototype.drawLeafs,
790
- 250 // 250 ms gives quick feedback, but doesn't eat cpu
996
+ 250, // 250 ms gives quick feedback, but doesn't eat cpu
791
997
  );
792
998
 
793
999
  /**
794
1000
  * @param {number} direction Pass 1 to zoom in, anything else to zoom out
795
1001
  */
796
1002
  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;
1003
+ if (direction == 1) {
1004
+ this.activeMode.zoom('in');
1005
+ } else {
1006
+ this.activeMode.zoom('out');
818
1007
  }
819
-
820
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1008
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1009
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
821
1010
  return;
822
1011
  };
823
1012
 
@@ -834,19 +1023,19 @@ BookReader.prototype.resizeBRcontainer = function(animate) {
834
1023
  if (animate) {
835
1024
  this.refs.$brContainer.animate({
836
1025
  top: this.getToolBarHeight(),
837
- bottom: this.getFooterHeight()
1026
+ bottom: this.getFooterHeight(),
838
1027
  }, this.constResizeAnimationDuration, 'linear');
839
1028
  } else {
840
1029
  this.refs.$brContainer.css({
841
1030
  top: this.getToolBarHeight(),
842
- bottom: this.getFooterHeight()
1031
+ bottom: this.getFooterHeight(),
843
1032
  });
844
1033
  }
845
1034
  };
846
1035
 
847
1036
  BookReader.prototype.centerPageView = function() {
848
- var scrollWidth = this.refs.$brContainer.prop('scrollWidth');
849
- var clientWidth = this.refs.$brContainer.prop('clientWidth');
1037
+ const scrollWidth = this.refs.$brContainer.prop('scrollWidth');
1038
+ const clientWidth = this.refs.$brContainer.prop('clientWidth');
850
1039
  if (scrollWidth > clientWidth) {
851
1040
  this.refs.$brContainer.prop('scrollLeft', (scrollWidth - clientWidth) / 2);
852
1041
  }
@@ -934,7 +1123,7 @@ BookReader.prototype._reduceSort = (a, b) => a.reduce - b.reduce;
934
1123
  * @return {boolean} Returns true if page could be found, false otherwise.
935
1124
  */
936
1125
  BookReader.prototype.jumpToPage = function(pageNum) {
937
- var pageIndex = this._models.book.parsePageString(pageNum);
1126
+ const pageIndex = this.book.parsePageString(pageNum);
938
1127
 
939
1128
  if ('undefined' != typeof(pageIndex)) {
940
1129
  this.jumpToIndex(pageIndex);
@@ -950,10 +1139,8 @@ BookReader.prototype.jumpToPage = function(pageNum) {
950
1139
  * @param {PageIndex} index
951
1140
  */
952
1141
  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;
1142
+ return this.displayedIndices ? this.displayedIndices.includes(index) :
1143
+ this.currentIndex() == index;
957
1144
  };
958
1145
 
959
1146
  /**
@@ -965,29 +1152,26 @@ BookReader.prototype._isIndexDisplayed = function(index) {
965
1152
  */
966
1153
  BookReader.prototype.jumpToIndex = function(index, pageX, pageY, noAnimate) {
967
1154
  // Don't jump into specific unviewable page
968
- const page = this._models.book.getPage(index);
1155
+ const page = this.book.getPage(index);
1156
+
969
1157
  if (!page.isViewable && page.unviewablesStart != page.index) {
970
1158
  // If already in unviewable range, jump to end of that range
971
1159
  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);
1160
+ const newIndex = alreadyInPreview ? page.findNext({ combineConsecutiveUnviewables: true })?.index : page.unviewablesStart;
1161
+ // Rare, but a book can end on an unviewable page, so this could be undefined
1162
+ if (typeof newIndex !== 'undefined') {
1163
+ this.jumpToIndex(newIndex, pageX, pageY, noAnimate);
1164
+ }
1165
+ } else {
1166
+ this.trigger(BookReader.eventNames.stop);
977
1167
 
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);
1168
+ this.activeMode.jumpToIndex(index, pageX, pageY, noAnimate);
984
1169
  }
985
1170
  };
986
1171
 
987
1172
  /**
988
1173
  * Return mode or 1up if initial thumb
989
1174
  * @param {number}
990
- * @see BookReader.prototype.drawLeafsThumbnail
991
1175
  */
992
1176
  BookReader.prototype.getPrevReadMode = function(mode) {
993
1177
  if (mode === BookReader.constMode1up || mode === BookReader.constMode2up) {
@@ -1000,7 +1184,7 @@ BookReader.prototype.getPrevReadMode = function(mode) {
1000
1184
 
1001
1185
  /**
1002
1186
  * Switches the mode (eg 1up 2up thumb)
1003
- * @param {number}
1187
+ * @param {number|'1up' | '2up' | 'thumb'}
1004
1188
  * @param {object} [options]
1005
1189
  * @param {boolean} [options.suppressFragmentChange = false]
1006
1190
  * @param {boolean} [options.onInit = false] - this
@@ -1010,9 +1194,21 @@ BookReader.prototype.switchMode = function(
1010
1194
  {
1011
1195
  suppressFragmentChange = false,
1012
1196
  init = false,
1013
- pageFound = false
1014
- } = {}
1197
+ pageFound = false,
1198
+ } = {},
1015
1199
  ) {
1200
+ if (typeof mode === 'string') {
1201
+ mode = {
1202
+ '1up': this.constMode1up,
1203
+ '2up': this.constMode2up,
1204
+ 'thumb': this.constModeThumb,
1205
+ }[mode];
1206
+ }
1207
+
1208
+ if (!mode) {
1209
+ throw new Error(`Invalid mode: ${mode}`);
1210
+ }
1211
+
1016
1212
  // Skip checks before init() complete
1017
1213
  if (this.init.initComplete) {
1018
1214
  if (mode === this.mode) {
@@ -1024,10 +1220,13 @@ BookReader.prototype.switchMode = function(
1024
1220
  }
1025
1221
 
1026
1222
  this.trigger(BookReader.eventNames.stop);
1027
- if (this.enableSearch) this.removeSearchHilites();
1028
1223
 
1029
1224
  this.prevReadMode = this.getPrevReadMode(this.mode);
1030
1225
 
1226
+ if (this.mode != mode) {
1227
+ this.activeMode.unprepare?.();
1228
+ }
1229
+
1031
1230
  this.mode = mode;
1032
1231
 
1033
1232
  // reinstate scale if moving from thumbnail view
@@ -1040,42 +1239,32 @@ BookReader.prototype.switchMode = function(
1040
1239
 
1041
1240
  // XXX maybe better to preserve zoom in each mode
1042
1241
  if (this.constMode1up == mode) {
1043
- this.onePageCalculateReductionFactors();
1044
- this.reduce = this.quantizeReduce(this.reduce, this.onePage.reductionFactors);
1045
- this.prepareOnePageView();
1242
+ this._modes.mode1Up.prepare();
1046
1243
  } else if (this.constModeThumb == mode) {
1047
1244
  this.reduce = this.quantizeReduce(this.reduce, this.reductionFactors);
1048
- this.prepareThumbnailView();
1245
+ this._modes.modeThumb.prepare();
1049
1246
  } 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
1247
+ this._modes.mode2Up.prepare();
1060
1248
  }
1061
1249
 
1062
1250
  if (!(this.suppressFragmentChange || suppressFragmentChange)) {
1063
1251
  this.trigger(BookReader.eventNames.fragmentChange);
1064
1252
  }
1065
- var eventName = mode + 'PageViewSelected';
1253
+ const eventName = mode + 'PageViewSelected';
1066
1254
  this.trigger(BookReader.eventNames[eventName]);
1067
1255
 
1068
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1256
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1257
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1069
1258
  };
1070
1259
 
1071
1260
  BookReader.prototype.updateBrClasses = function() {
1072
- var modeToClass = {};
1261
+ const modeToClass = {};
1073
1262
  modeToClass[this.constMode1up] = 'BRmode1up';
1074
- modeToClass[this.constMode2up] = 'BRmode2Up';
1263
+ modeToClass[this.constMode2up] = 'BRmode2up';
1075
1264
  modeToClass[this.constModeThumb] = 'BRmodeThumb';
1076
1265
 
1077
1266
  this.refs.$br
1078
- .removeClass('BRmode1up BRmode2Up BRmodeThumb')
1267
+ .removeClass('BRmode1up BRmode2up BRmodeThumb')
1079
1268
  .addClass(modeToClass[this.mode]);
1080
1269
 
1081
1270
  if (this.isFullscreen()) {
@@ -1095,31 +1284,30 @@ BookReader.prototype.isFullscreen = function() {
1095
1284
  * Toggles fullscreen
1096
1285
  * @param { boolean } bindKeyboardControls
1097
1286
  */
1098
- BookReader.prototype.toggleFullscreen = function(bindKeyboardControls = true) {
1287
+ BookReader.prototype.toggleFullscreen = async function(bindKeyboardControls = true) {
1099
1288
  if (this.isFullscreen()) {
1100
- this.exitFullScreen();
1289
+ await this.exitFullScreen();
1101
1290
  } else {
1102
- this.enterFullscreen(bindKeyboardControls);
1291
+ await this.enterFullscreen(bindKeyboardControls);
1103
1292
  }
1104
1293
  };
1105
1294
 
1106
1295
  /**
1107
1296
  * Enters fullscreen
1108
1297
  * including:
1109
- * - animation
1110
1298
  * - binds keyboard controls
1111
1299
  * - fires custom event
1112
1300
  * @param { boolean } bindKeyboardControls
1113
1301
  */
1114
- BookReader.prototype.enterFullscreen = function(bindKeyboardControls = true) {
1302
+ BookReader.prototype.enterFullscreen = async function(bindKeyboardControls = true) {
1303
+ this.refs.$br.addClass('BRfullscreenAnimation');
1115
1304
  const currentIndex = this.currentIndex();
1116
- this.refs.$brContainer.css('opacity', 0);
1117
1305
 
1118
1306
  if (bindKeyboardControls) {
1119
1307
  this._fullscreenCloseHandler = (e) => {
1120
1308
  if (e.keyCode === 27) this.toggleFullscreen();
1121
1309
  };
1122
- $(document).keyup(this._fullscreenCloseHandler);
1310
+ $(document).on("keyup", this._fullscreenCloseHandler);
1123
1311
  }
1124
1312
 
1125
1313
  const windowWidth = $(window).width();
@@ -1128,15 +1316,29 @@ BookReader.prototype.enterFullscreen = function(bindKeyboardControls = true) {
1128
1316
  }
1129
1317
 
1130
1318
  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
-
1138
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1319
+ // prioritize class updates so CSS can propagate
1320
+ this.updateBrClasses();
1321
+ if (this.activeMode instanceof Mode1Up) {
1322
+ this.activeMode.mode1UpLit.scale = this.activeMode.mode1UpLit.computeDefaultScale(this.book.getPage(currentIndex));
1323
+ // Need the new scale to be applied before calling jumpToIndex
1324
+ this.activeMode.mode1UpLit.requestUpdate();
1325
+ await this.activeMode.mode1UpLit.updateComplete;
1326
+ }
1327
+ this.jumpToIndex(currentIndex);
1328
+
1329
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1330
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1331
+ // Add "?view=theater"
1332
+ this.trigger(BookReader.eventNames.fragmentChange);
1333
+ // trigger event here, so that animations,
1334
+ // class updates happen before book-nav relays to web components
1139
1335
  this.trigger(BookReader.eventNames.fullscreenToggled);
1336
+
1337
+ // resize book after all events & css updates
1338
+ await new Promise(resolve => setTimeout(resolve, 0));
1339
+
1340
+ this.resize();
1341
+ this.refs.$br.removeClass('BRfullscreenAnimation');
1140
1342
  };
1141
1343
 
1142
1344
  /**
@@ -1146,27 +1348,36 @@ BookReader.prototype.enterFullscreen = function(bindKeyboardControls = true) {
1146
1348
  * - fires custom event
1147
1349
  * @param { boolean } bindKeyboardControls
1148
1350
  */
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();
1351
+ BookReader.prototype.exitFullScreen = async function () {
1352
+ this.refs.$br.addClass('BRfullscreenAnimation');
1353
+ $(document).off('keyup', this._fullscreenCloseHandler);
1155
1354
 
1355
+ const windowWidth = $(window).width();
1156
1356
  const canShow2up = this.options.controls.twoPage.visible;
1157
1357
  if (canShow2up && (windowWidth <= this.onePageMinBreakpoint)) {
1158
1358
  this.switchMode(this.constMode2up);
1159
1359
  }
1160
1360
 
1161
1361
  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);
1362
+ // Trigger fullscreen event immediately
1363
+ // so that book-nav can relay to web components
1169
1364
  this.trigger(BookReader.eventNames.fullscreenToggled);
1365
+
1366
+ this.updateBrClasses();
1367
+ await new Promise(resolve => setTimeout(resolve, 0));
1368
+ this.resize();
1369
+
1370
+ if (this.activeMode instanceof Mode1Up) {
1371
+ this.activeMode.mode1UpLit.scale = this.activeMode.mode1UpLit.computeDefaultScale(this.book.getPage(this.currentIndex()));
1372
+ this.activeMode.mode1UpLit.requestUpdate();
1373
+ await this.activeMode.mode1UpLit.updateComplete;
1374
+ }
1375
+
1376
+ this.plugins.textSelection?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1377
+ this.plugins.translate?.textSelectionManager.stopPageFlip(this.refs.$brContainer);
1378
+ // Remove "?view=theater"
1379
+ this.trigger(BookReader.eventNames.fragmentChange);
1380
+ this.refs.$br.removeClass('BRfullscreenAnimation');
1170
1381
  };
1171
1382
 
1172
1383
  /**
@@ -1180,7 +1391,7 @@ BookReader.prototype.currentIndex = function() {
1180
1391
  return this.firstIndex; // $$$ TODO page in center of view would be better
1181
1392
  } else if (this.mode == this.constMode2up) {
1182
1393
  // Only allow indices that are actually present in book
1183
- return utils.clamp(this.firstIndex, 0, this._models.book.getNumLeafs() - 1);
1394
+ return utils.clamp(this.firstIndex, 0, this.book.getNumLeafs() - 1);
1184
1395
  } else {
1185
1396
  throw 'currentIndex called for unimplemented mode ' + this.mode;
1186
1397
  }
@@ -1195,13 +1406,11 @@ BookReader.prototype.currentIndex = function() {
1195
1406
  */
1196
1407
  BookReader.prototype.updateFirstIndex = function(
1197
1408
  index,
1198
- { suppressFragmentChange = false } = {}
1409
+ { suppressFragmentChange = false } = {},
1199
1410
  ) {
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
- }
1411
+ // If there's no change, do nothing
1412
+ if (this.firstIndex === index) return;
1413
+
1205
1414
  this.firstIndex = index;
1206
1415
  if (!(this.suppressFragmentChange || suppressFragmentChange)) {
1207
1416
  this.trigger(BookReader.eventNames.fragmentChange);
@@ -1209,11 +1418,15 @@ BookReader.prototype.updateFirstIndex = function(
1209
1418
  // If there's an initial search we stop suppressing global URL changes
1210
1419
  // when local suppression ends
1211
1420
  // This seems to correctly handle multiple calls during mode/1up
1212
- if (this.options.initialSearchTerm && !suppressFragmentChange) {
1421
+ if (this.plugins.search?.options.initialSearchTerm && !suppressFragmentChange) {
1213
1422
  this.suppressFragmentChange = false;
1214
1423
  }
1215
- this.trigger('pageChanged');
1216
- this.updateNavIndexThrottled(index);
1424
+
1425
+ this.trigger(BookReader.eventNames.pageChanged);
1426
+
1427
+ // event to know if user is actively reading
1428
+ this.trigger(BookReader.eventNames.userAction);
1429
+ this._components.navbar.updateNavIndexThrottled(index);
1217
1430
  };
1218
1431
 
1219
1432
  /**
@@ -1260,24 +1473,42 @@ BookReader.prototype.leftmost = function() {
1260
1473
  }
1261
1474
  };
1262
1475
 
1263
- BookReader.prototype.next = function() {
1476
+ /**
1477
+ * @param {object} options
1478
+ * @param {boolean} [options.triggerStop = true]
1479
+ * @param {number | 'fast' | 'slow'} [options.flipSpeed]
1480
+ */
1481
+ BookReader.prototype.next = function({
1482
+ triggerStop = true,
1483
+ flipSpeed = null,
1484
+ } = {}) {
1264
1485
  if (this.constMode2up == this.mode) {
1265
- this.trigger(BookReader.eventNames.stop);
1266
- this.flipFwdToIndex(null);
1486
+ if (triggerStop) this.trigger(BookReader.eventNames.stop);
1487
+ flipSpeed = utils.parseAnimationSpeed(flipSpeed) || this.flipSpeed;
1488
+ this._modes.mode2Up.mode2UpLit.flipAnimation('next', {flipSpeed});
1267
1489
  } else {
1268
- if (this.firstIndex < this.lastDisplayableIndex()) {
1490
+ if (this.firstIndex < this.book.getNumLeafs() - 1) {
1269
1491
  this.jumpToIndex(this.firstIndex + 1);
1270
1492
  }
1271
1493
  }
1272
1494
  };
1273
1495
 
1274
- BookReader.prototype.prev = function() {
1496
+ /**
1497
+ * @param {object} options
1498
+ * @param {boolean} [options.triggerStop = true]
1499
+ * @param {number | 'fast' | 'slow'} [options.flipSpeed]
1500
+ */
1501
+ BookReader.prototype.prev = function({
1502
+ triggerStop = true,
1503
+ flipSpeed = null,
1504
+ } = {}) {
1275
1505
  const isOnFrontPage = this.firstIndex < 1;
1276
1506
  if (isOnFrontPage) return;
1277
1507
 
1278
1508
  if (this.constMode2up == this.mode) {
1279
- this.trigger(BookReader.eventNames.stop);
1280
- this.flipBackToIndex(null);
1509
+ if (triggerStop) this.trigger(BookReader.eventNames.stop);
1510
+ flipSpeed = utils.parseAnimationSpeed(flipSpeed) || this.flipSpeed;
1511
+ this._modes.mode2Up.mode2UpLit.flipAnimation('prev', {flipSpeed});
1281
1512
  } else {
1282
1513
  if (this.firstIndex >= 1) {
1283
1514
  this.jumpToIndex(this.firstIndex - 1);
@@ -1286,257 +1517,13 @@ BookReader.prototype.prev = function() {
1286
1517
  };
1287
1518
 
1288
1519
  BookReader.prototype.first = function() {
1289
- this.jumpToIndex(this.firstDisplayableIndex());
1520
+ this.jumpToIndex(0);
1290
1521
  };
1291
1522
 
1292
1523
  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
- /**
1317
- * Scrolls up one screen view
1318
- */
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
- }
1334
- };
1335
-
1336
- /**
1337
- * The amount to scroll vertically in integer pixels
1338
- */
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'));
1346
- };
1347
-
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.');
1524
+ this.jumpToIndex(this.book.getNumLeafs() - 1);
1353
1525
  };
1354
1526
 
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.');
1360
- };
1361
-
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
1527
 
1541
1528
  /**
1542
1529
  * @template TClass extends { br: BookReader }
@@ -1560,30 +1547,9 @@ function exposeOverrideableMethod(Class, classKey, method, brMethod = method) {
1560
1547
  /***********************/
1561
1548
  /** Navbar extensions **/
1562
1549
  /***********************/
1550
+ /** This cannot be removed yet because plugin.tts.js overrides it */
1563
1551
  BookReader.prototype.initNavbar = Navbar.prototype.init;
1564
1552
  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
1553
 
1588
1554
  /************************/
1589
1555
  /** Toolbar extensions **/
@@ -1598,515 +1564,15 @@ BookReader.prototype.buildInfoDiv = Toolbar.prototype.buildInfoDiv;
1598
1564
  exposeOverrideableMethod(Toolbar, '_components.toolbar', 'buildInfoDiv');
1599
1565
  BookReader.prototype.getToolBarHeight = Toolbar.prototype.getToolBarHeight;
1600
1566
  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
1567
 
2067
1568
  /**************************/
2068
1569
  /** BookModel extensions **/
2069
1570
  /**************************/
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');
1571
+ // Must modify petabox extension, which expects this on the prototype
1572
+ // before removing.
2090
1573
  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');
1574
+ exposeOverrideableMethod(BookModel, 'book', 'getPageURI');
1575
+
2110
1576
 
2111
1577
  // Parameter related functions
2112
1578
 
@@ -2125,7 +1591,7 @@ BookReader.prototype.updateFromParams = function(params) {
2125
1591
  if (mode) {
2126
1592
  this.switchMode(
2127
1593
  mode,
2128
- { init: init, suppressFragmentChange: !fragmentChange }
1594
+ { init: init, suppressFragmentChange: !fragmentChange },
2129
1595
  );
2130
1596
  }
2131
1597
 
@@ -2137,7 +1603,7 @@ BookReader.prototype.updateFromParams = function(params) {
2137
1603
  }
2138
1604
  } else if ('undefined' != typeof(params.page)) {
2139
1605
  // $$$ this assumes page numbers are unique
2140
- if (params.page != this._models.book.getPageNum(this.currentIndex())) {
1606
+ if (params.page != this.book.getPageNum(this.currentIndex())) {
2141
1607
  this.jumpToPage(params.page);
2142
1608
  }
2143
1609
  }
@@ -2146,8 +1612,8 @@ BookReader.prototype.updateFromParams = function(params) {
2146
1612
  // process /search
2147
1613
  // @deprecated for urlMode 'history'
2148
1614
  // Continues to work for urlMode 'hash'
2149
- if (this.enableSearch && 'undefined' != typeof(params.search)) {
2150
- if (this.searchTerm !== params.search) {
1615
+ if (this.plugins.search?.enabled && 'undefined' != typeof(params.search)) {
1616
+ if (this.plugins.search.searchTerm !== params.search) {
2151
1617
  this.$('.BRsearchInput').val(params.search);
2152
1618
  }
2153
1619
  }
@@ -2171,7 +1637,7 @@ BookReader.prototype.canSwitchToMode = function(mode) {
2171
1637
  // check there are enough pages to display
2172
1638
  // $$$ this is a workaround for the mis-feature that we can't display
2173
1639
  // short books in 2up mode
2174
- if (this._models.book.getNumLeafs() < 2) {
1640
+ if (this.book.getNumLeafs() < 2) {
2175
1641
  return false;
2176
1642
  }
2177
1643
  }
@@ -2179,31 +1645,6 @@ BookReader.prototype.canSwitchToMode = function(mode) {
2179
1645
  return true;
2180
1646
  };
2181
1647
 
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
1648
  /**
2208
1649
  * Returns the page URI or transparent image if out of range
2209
1650
  * Also makes the reduce argument optional
@@ -2213,7 +1654,7 @@ BookReader.prototype._getPageURISrcset = function(index, reduce, rotate) {
2213
1654
  * @return {string}
2214
1655
  */
2215
1656
  BookReader.prototype._getPageURI = function(index, reduce, rotate) {
2216
- const page = this._models.book.getPage(index, false);
1657
+ const page = this.book.getPage(index, false);
2217
1658
  // Synthesize page
2218
1659
  if (!page) return this.imagesBaseURL + "transparent.png";
2219
1660
 
@@ -2252,7 +1693,7 @@ BookReader.prototype.showProgressPopup = function(msg, onCloseCallback) {
2252
1693
 
2253
1694
  const bar = document.createElement("div");
2254
1695
  $(bar).css({
2255
- height: '20px'
1696
+ height: '20px',
2256
1697
  }).prop('className', 'BRprogressbar');
2257
1698
  $(this.popup).append(bar);
2258
1699
 
@@ -2279,7 +1720,7 @@ BookReader.prototype.initUIStrings = function() {
2279
1720
  // the toolbar and nav bar easier
2280
1721
 
2281
1722
  // Setup tooltips -- later we could load these from a file for i18n
2282
- var titles = {
1723
+ const titles = {
2283
1724
  '.logo': 'Go to Archive.org', // $$$ update after getting OL record
2284
1725
  '.zoom_in': 'Zoom in',
2285
1726
  '.zoom_out': 'Zoom out',
@@ -2295,12 +1736,8 @@ BookReader.prototype.initUIStrings = function() {
2295
1736
  '.full': 'Toggle fullscreen',
2296
1737
  '.book_left': 'Flip left',
2297
1738
  '.book_right': 'Flip right',
2298
- '.book_up': 'Page up',
2299
- '.book_down': 'Page down',
2300
1739
  '.play': 'Play',
2301
1740
  '.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
1741
  '.book_top': 'First page',
2305
1742
  '.book_bottom': 'Last page',
2306
1743
  '.book_leftmost': 'First page',
@@ -2311,7 +1748,7 @@ BookReader.prototype.initUIStrings = function() {
2311
1748
  titles['.book_rightmost'] = 'First page';
2312
1749
  }
2313
1750
 
2314
- for (var icon in titles) {
1751
+ for (const icon in titles) {
2315
1752
  this.$(icon).prop('title', titles[icon]);
2316
1753
  }
2317
1754
  };
@@ -2322,7 +1759,7 @@ BookReader.prototype.initUIStrings = function() {
2322
1759
  BookReader.prototype.reloadImages = function() {
2323
1760
  this.refs.$brContainer.find('img').each(function(index, elem) {
2324
1761
  if (!elem.complete || elem.naturalHeight === 0) {
2325
- var src = elem.src;
1762
+ const src = elem.src;
2326
1763
  elem.src = '';
2327
1764
  setTimeout(function() {
2328
1765
  elem.src = src;
@@ -2336,10 +1773,10 @@ BookReader.prototype.reloadImages = function() {
2336
1773
  * @return {number}
2337
1774
  */
2338
1775
  BookReader.prototype.getFooterHeight = function() {
2339
- var $heightEl = this.mode == this.constMode2up ? this.refs.$BRfooter : this.refs.$BRnav;
1776
+ const $heightEl = this.mode == this.constMode2up ? this.refs.$BRfooter : this.refs.$BRnav;
2340
1777
  if ($heightEl && this.refs.$BRfooter) {
2341
- var outerHeight = $heightEl.outerHeight();
2342
- var bottom = parseInt(this.refs.$BRfooter.css('bottom'));
1778
+ const outerHeight = $heightEl.outerHeight();
1779
+ const bottom = parseInt(this.refs.$BRfooter.css('bottom'));
2343
1780
  if (!isNaN(outerHeight) && !isNaN(bottom)) {
2344
1781
  return outerHeight + bottom;
2345
1782
  }
@@ -2355,10 +1792,11 @@ BookReader.prototype.getFooterHeight = function() {
2355
1792
  * @return {Object}
2356
1793
  */
2357
1794
  BookReader.prototype.paramsFromCurrent = function() {
2358
- var params = {};
1795
+ const params = {};
2359
1796
 
2360
- var index = this.currentIndex();
2361
- var pageNum = this._models.book.getPageNum(index);
1797
+ // Path params
1798
+ const index = this.currentIndex();
1799
+ const pageNum = this.book.getPageNum(index);
2362
1800
  if ((pageNum === 0) || pageNum) {
2363
1801
  params.page = pageNum;
2364
1802
  }
@@ -2366,12 +1804,19 @@ BookReader.prototype.paramsFromCurrent = function() {
2366
1804
  params.index = index;
2367
1805
  params.mode = this.mode;
2368
1806
 
1807
+ // Unused params
2369
1808
  // $$$ highlight
2370
1809
  // $$$ region
2371
1810
 
2372
- // search
2373
- if (this.enableSearch) {
2374
- params.search = this.searchTerm;
1811
+ // Querystring params
1812
+ // View
1813
+ const fullscreenView = 'theater';
1814
+ if (this.isFullscreenActive) {
1815
+ params.view = fullscreenView;
1816
+ }
1817
+ // Search
1818
+ if (this.plugins.search?.enabled) {
1819
+ params.search = this.plugins.search.searchTerm;
2375
1820
  }
2376
1821
 
2377
1822
  return params;
@@ -2390,7 +1835,7 @@ BookReader.prototype.paramsFromCurrent = function() {
2390
1835
  * @return {Object}
2391
1836
  */
2392
1837
  BookReader.prototype.paramsFromFragment = function(fragment) {
2393
- var params = {};
1838
+ const params = {};
2394
1839
 
2395
1840
  // For backwards compatibility we allow an initial # character
2396
1841
  // (as from window.location.hash) but don't require it
@@ -2399,7 +1844,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2399
1844
  }
2400
1845
 
2401
1846
  // Simple #nn syntax
2402
- var oldStyleLeafNum = parseInt( /^\d+$/.exec(fragment) );
1847
+ const oldStyleLeafNum = parseInt( /^\d+$/.exec(fragment) );
2403
1848
  if ( !isNaN(oldStyleLeafNum) ) {
2404
1849
  params.index = oldStyleLeafNum;
2405
1850
 
@@ -2408,9 +1853,9 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2408
1853
  }
2409
1854
 
2410
1855
  // Split into key-value pairs
2411
- var urlArray = fragment.split('/');
2412
- var urlHash = {};
2413
- for (var i = 0; i < urlArray.length; i += 2) {
1856
+ const urlArray = fragment.split('/');
1857
+ const urlHash = {};
1858
+ for (let i = 0; i < urlArray.length; i += 2) {
2414
1859
  urlHash[urlArray[i]] = urlArray[i + 1];
2415
1860
  }
2416
1861
 
@@ -2426,7 +1871,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2426
1871
  // Index and page
2427
1872
  if ('undefined' != typeof(urlHash['page'])) {
2428
1873
  // page was set -- may not be int
2429
- params.page = urlHash['page'];
1874
+ params.page = decodeURIComponent(urlHash['page']);
2430
1875
  }
2431
1876
 
2432
1877
  // $$$ process /region
@@ -2458,11 +1903,10 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2458
1903
  * @return {string}
2459
1904
  */
2460
1905
  BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2461
- const separator = '/';
2462
1906
  const fragments = [];
2463
1907
 
2464
1908
  if ('undefined' != typeof(params.page)) {
2465
- fragments.push('page', params.page);
1909
+ fragments.push('page', encodeURIComponent(params.page));
2466
1910
  } else {
2467
1911
  if ('undefined' != typeof(params.index)) {
2468
1912
  // Don't have page numbering but we do have the index
@@ -2488,15 +1932,18 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2488
1932
 
2489
1933
  // search
2490
1934
  if (params.search && urlMode === 'hash') {
2491
- fragments.push('search', params.search);
1935
+ fragments.push('search', utils.encodeURIComponentPlus(params.search));
2492
1936
  }
2493
1937
 
2494
- return utils.encodeURIComponentPlus(fragments.join(separator)).replace(/%2F/g, '/');
1938
+ return fragments.join('/');
2495
1939
  };
2496
1940
 
2497
1941
  /**
2498
1942
  * Create, update querystring from the params object
2499
1943
  *
1944
+ * Handles:
1945
+ * view=
1946
+ * q=
2500
1947
  * @param {Object} params
2501
1948
  * @param {string} currQueryString
2502
1949
  * @param {string} [urlMode]
@@ -2505,9 +1952,18 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2505
1952
  BookReader.prototype.queryStringFromParams = function(
2506
1953
  params,
2507
1954
  currQueryString,
2508
- urlMode = 'hash'
1955
+ urlMode = 'hash',
2509
1956
  ) {
2510
1957
  const newParams = new URLSearchParams(currQueryString);
1958
+
1959
+ if (params.view) {
1960
+ // Set ?view=theater when fullscreen
1961
+ newParams.set('view', params.view);
1962
+ } else {
1963
+ // Remove
1964
+ newParams.delete('view');
1965
+ }
1966
+
2511
1967
  if (params.search && urlMode === 'history') {
2512
1968
  newParams.set('q', params.search);
2513
1969
  }
@@ -2524,11 +1980,4 @@ BookReader.prototype.$ = function(selector) {
2524
1980
  return this.refs.$br.find(selector);
2525
1981
  };
2526
1982
 
2527
- /**
2528
- * Polyfill for deprecated method
2529
- */
2530
- jQuery.curCSS = function(element, prop, val) {
2531
- return jQuery(element).css(prop, val);
2532
- };
2533
-
2534
1983
  window.BookReader = BookReader;