@internetarchive/bookreader 5.0.0-9-multiple-files → 5.0.0-90

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