@internetarchive/bookreader 5.0.0-9 → 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 (324) hide show
  1. package/.eslintrc.js +21 -19
  2. package/.github/workflows/node.js.yml +76 -11
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +404 -1125
  6. package/BookReader/BookReader.js +1 -1
  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 -1
  13. package/BookReader/icons/2up.svg +1 -1
  14. package/BookReader/icons/advance.svg +1 -1
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -1
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -1
  19. package/BookReader/icons/fullscreen_exit.svg +1 -1
  20. package/BookReader/icons/hamburger.svg +1 -1
  21. package/BookReader/icons/left-arrow.svg +1 -1
  22. package/BookReader/icons/magnify-minus.svg +1 -1
  23. package/BookReader/icons/magnify-plus.svg +1 -1
  24. package/BookReader/icons/magnify.svg +1 -1
  25. package/BookReader/icons/pause.svg +1 -1
  26. package/BookReader/icons/play.svg +1 -1
  27. package/BookReader/icons/playback-speed.svg +1 -1
  28. package/BookReader/icons/read-aloud.svg +1 -1
  29. package/BookReader/icons/review.svg +1 -1
  30. package/BookReader/icons/thumbnails.svg +1 -1
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -1
  33. package/BookReader/images/BRicons.svg +3 -3
  34. package/BookReader/images/books_graphic.svg +1 -1
  35. package/BookReader/images/icon_book.svg +1 -1
  36. package/BookReader/images/icon_bookmark.svg +1 -1
  37. package/BookReader/images/icon_gear.svg +1 -1
  38. package/BookReader/images/icon_hamburger.svg +1 -1
  39. package/BookReader/images/icon_home.svg +1 -1
  40. package/BookReader/images/icon_info.svg +1 -1
  41. package/BookReader/images/icon_one_page.svg +1 -1
  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 -1
  45. package/BookReader/images/icon_search_button.svg +1 -1
  46. package/BookReader/images/icon_share.svg +1 -1
  47. package/BookReader/images/icon_skip-ahead.svg +1 -1
  48. package/BookReader/images/icon_skip-back.svg +1 -1
  49. package/BookReader/images/icon_speaker.svg +1 -1
  50. package/BookReader/images/icon_speaker_open.svg +1 -1
  51. package/BookReader/images/icon_thumbnails.svg +1 -1
  52. package/BookReader/images/icon_toc.svg +1 -1
  53. package/BookReader/images/icon_two_pages.svg +1 -1
  54. package/BookReader/images/marker_chap-off.svg +1 -1
  55. package/BookReader/images/marker_chap-on.svg +1 -1
  56. package/BookReader/images/marker_srch-on.svg +1 -1
  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 -1
  61. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js +1 -1
  63. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  64. package/BookReader/plugins/plugin.chapters.js +25 -1
  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 -1
  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 -1
  72. package/BookReader/plugins/plugin.resume.js.map +1 -1
  73. package/BookReader/plugins/plugin.search.js +2 -1
  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 -1
  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 +1 -1
  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 -1
  83. package/BookReader/plugins/plugin.url.js.map +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  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 +584 -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 +5 -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 +50 -28
  135. package/src/BookNavigator/search/search-results.js +24 -10
  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 +544 -1078
  162. package/src/BookReaderPlugin.js +44 -0
  163. package/src/assets/icons/magnify-minus.svg +3 -7
  164. package/src/assets/icons/magnify-plus.svg +3 -7
  165. package/src/assets/icons/voice.svg +1 -0
  166. package/src/assets/images/unviewable_page.png +0 -0
  167. package/src/css/BookReader.scss +1 -5
  168. package/src/css/_BRBookmarks.scss +1 -1
  169. package/src/css/_BRComponent.scss +1 -1
  170. package/src/css/_BRicon.scss +8 -2
  171. package/src/css/_BRmain.scss +16 -3
  172. package/src/css/_BRnav.scss +12 -42
  173. package/src/css/_BRpages.scss +170 -42
  174. package/src/css/_BRsearch.scss +68 -25
  175. package/src/css/_BRtoolbar.scss +5 -5
  176. package/src/css/_TextSelection.scss +87 -27
  177. package/src/css/_colorbox.scss +2 -2
  178. package/src/css/_controls.scss +24 -7
  179. package/src/css/_icons.scss +1 -1
  180. package/src/ia-bookreader/ia-bookreader.js +224 -0
  181. package/src/plugins/plugin.archive_analytics.js +84 -78
  182. package/src/plugins/plugin.autoplay.js +99 -104
  183. package/src/plugins/plugin.chapters.js +237 -191
  184. package/src/plugins/plugin.iframe.js +1 -1
  185. package/src/plugins/plugin.iiif.js +141 -0
  186. package/src/plugins/plugin.resume.js +53 -50
  187. package/src/plugins/plugin.text_selection.js +503 -175
  188. package/src/plugins/plugin.vendor-fullscreen.js +7 -7
  189. package/src/plugins/search/plugin.search.js +151 -127
  190. package/src/plugins/search/utils.js +43 -0
  191. package/src/plugins/search/view.js +37 -59
  192. package/src/plugins/tts/AbstractTTSEngine.js +75 -45
  193. package/src/plugins/tts/FestivalTTSEngine.js +21 -31
  194. package/src/plugins/tts/PageChunk.js +16 -23
  195. package/src/plugins/tts/PageChunkIterator.js +11 -17
  196. package/src/plugins/tts/WebTTSEngine.js +88 -72
  197. package/src/plugins/tts/plugin.tts.js +310 -350
  198. package/src/plugins/tts/utils.js +16 -26
  199. package/src/plugins/url/UrlPlugin.js +191 -0
  200. package/src/plugins/{plugin.url.js → url/plugin.url.js} +47 -18
  201. package/src/util/browserSniffing.js +22 -0
  202. package/src/util/docCookies.js +21 -2
  203. package/src/util/strings.js +1 -0
  204. package/tests/e2e/README.md +37 -0
  205. package/tests/e2e/autoplay.test.js +9 -6
  206. package/tests/e2e/base.test.js +8 -16
  207. package/tests/e2e/helpers/base.js +55 -50
  208. package/tests/e2e/helpers/debug.js +1 -1
  209. package/tests/e2e/helpers/mockSearch.js +19 -22
  210. package/tests/e2e/helpers/params.js +17 -0
  211. package/tests/e2e/helpers/rightToLeft.js +8 -14
  212. package/tests/e2e/helpers/search.js +73 -0
  213. package/tests/e2e/models/Navigation.js +20 -37
  214. package/tests/e2e/rightToLeft.test.js +4 -5
  215. package/tests/e2e/viewmode.test.js +40 -33
  216. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  217. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  218. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  219. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  220. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  221. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  222. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  223. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  224. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  225. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  226. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  227. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  228. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  229. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  230. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  231. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  232. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  233. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  234. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  235. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  236. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  237. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +42 -29
  238. package/tests/jest/BookReader/PageContainer.test.js +238 -0
  239. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  240. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +3 -3
  241. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  242. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  243. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  244. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  245. package/tests/jest/BookReader/utils.test.js +250 -0
  246. package/tests/jest/BookReader.keyboard.test.js +190 -0
  247. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +10 -2
  248. package/tests/{BookReader.test.js → jest/BookReader.test.js} +43 -53
  249. package/tests/jest/plugins/plugin.archive_analytics.test.js +20 -0
  250. package/tests/jest/plugins/plugin.autoplay.test.js +35 -0
  251. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  252. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +4 -4
  253. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +22 -35
  254. package/tests/jest/plugins/plugin.text_selection.test.js +316 -0
  255. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  256. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +19 -47
  257. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +42 -9
  258. package/tests/jest/plugins/search/utils.js +25 -0
  259. package/tests/jest/plugins/search/utils.test.js +29 -0
  260. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +30 -10
  261. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  262. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  263. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  264. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  265. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  266. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  267. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +57 -18
  268. package/tests/jest/setup.js +3 -0
  269. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  270. package/tests/jest/util/docCookies.test.js +24 -0
  271. package/tests/{util → jest/util}/strings.test.js +1 -1
  272. package/tests/{utils.js → jest/utils.js} +38 -0
  273. package/webpack.config.js +16 -10
  274. package/.babelrc +0 -12
  275. package/.dependabot/config.yml +0 -6
  276. package/.testcaferc.json +0 -5
  277. package/BookReader/bookreader-component-bundle.js +0 -1450
  278. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  279. package/BookReader/bookreader-component-bundle.js.map +0 -1
  280. package/BookReader/jquery-1.10.1.js +0 -2
  281. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  282. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  283. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  284. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  285. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  286. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  287. package/BookReaderDemo/IIIFBookReader.js +0 -207
  288. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  289. package/BookReaderDemo/demo-autoplay.html +0 -38
  290. package/BookReaderDemo/demo-iiif.js +0 -26
  291. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  292. package/karma.conf.js +0 -23
  293. package/src/BookNavigator/BookModel.js +0 -14
  294. package/src/BookNavigator/BookNavigator.js +0 -446
  295. package/src/BookNavigator/assets/book-loader.js +0 -27
  296. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  297. package/src/BookNavigator/search/a-search-result.js +0 -55
  298. package/src/BookReader/DebugConsole.js +0 -54
  299. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  300. package/src/ItemNavigator/ItemNavigator.js +0 -376
  301. package/src/ItemNavigator/providers/sharing.js +0 -29
  302. package/src/css/_MobileNav.scss +0 -194
  303. package/src/dragscrollable-br.js +0 -261
  304. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  305. package/src/plugins/plugin.mobile_nav.js +0 -287
  306. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  307. package/tests/BookReader/DebugConsole.test.js +0 -25
  308. package/tests/BookReader/Mode1Up.test.js +0 -164
  309. package/tests/BookReader/Mode2Up.test.js +0 -247
  310. package/tests/BookReader/PageContainer.test.js +0 -115
  311. package/tests/BookReader/utils.test.js +0 -109
  312. package/tests/e2e/helpers/desktopSearch.js +0 -72
  313. package/tests/e2e/helpers/mobileSearch.js +0 -85
  314. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  315. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  316. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  317. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  318. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  319. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  320. package/tests/plugins/plugin.autoplay.test.js +0 -52
  321. package/tests/plugins/plugin.chapters.test.js +0 -130
  322. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  323. package/tests/plugins/plugin.text_selection.test.js +0 -203
  324. 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.options.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;
517
+ }
518
+
519
+ if (!this.canSwitchToMode(initialMode)) {
520
+ initialMode = this.constMode1up;
454
521
  }
455
522
 
456
- if (!this.canSwitchToMode(nextMode)) {
457
- nextMode = this.constMode1up;
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,15 +1198,28 @@ BookReader.prototype.enterFullscreen = function(bindKeyboardControls = true) {
1128
1198
  }
1129
1199
 
1130
1200
  this.isFullscreenActive = true;
1131
- this.animating = true;
1132
- this.refs.$brContainer.animate({opacity: 1}, 'fast', 'linear',() => {
1133
- this.resize();
1134
- this.jumpToIndex(currentIndex);
1135
- this.animating = false;
1136
- });
1137
-
1138
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
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
1139
1216
  this.trigger(BookReader.eventNames.fullscreenToggled);
1217
+
1218
+ // resize book after all events & css updates
1219
+ await new Promise(resolve => setTimeout(resolve, 0));
1220
+
1221
+ this.resize();
1222
+ this.refs.$br.removeClass('BRfullscreenAnimation');
1140
1223
  };
1141
1224
 
1142
1225
  /**
@@ -1146,27 +1229,35 @@ BookReader.prototype.enterFullscreen = function(bindKeyboardControls = true) {
1146
1229
  * - fires custom event
1147
1230
  * @param { boolean } bindKeyboardControls
1148
1231
  */
1149
- BookReader.prototype.exitFullScreen = function() {
1150
- this.refs.$brContainer.css('opacity', 0);
1151
-
1152
- $(document).unbind('keyup', this._fullscreenCloseHandler);
1153
-
1154
- var windowWidth = $(window).width();
1232
+ BookReader.prototype.exitFullScreen = async function () {
1233
+ this.refs.$br.addClass('BRfullscreenAnimation');
1234
+ $(document).off('keyup', this._fullscreenCloseHandler);
1155
1235
 
1236
+ const windowWidth = $(window).width();
1156
1237
  const canShow2up = this.options.controls.twoPage.visible;
1157
1238
  if (canShow2up && (windowWidth <= this.onePageMinBreakpoint)) {
1158
1239
  this.switchMode(this.constMode2up);
1159
1240
  }
1160
1241
 
1161
1242
  this.isFullscreenActive = false;
1162
- this.updateBrClasses();
1163
- this.animating = true;
1164
- this.refs.$brContainer.animate({opacity: 1}, 'fast', 'linear', () => {
1165
- this.resize();
1166
- this.animating = false;
1167
- });
1168
- this.textSelectionPlugin?.stopPageFlip(this.refs.$brContainer);
1243
+ // Trigger fullscreen event immediately
1244
+ // so that book-nav can relay to web components
1169
1245
  this.trigger(BookReader.eventNames.fullscreenToggled);
1246
+
1247
+ this.updateBrClasses();
1248
+ await new Promise(resolve => setTimeout(resolve, 0));
1249
+ this.resize();
1250
+
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');
1170
1261
  };
1171
1262
 
1172
1263
  /**
@@ -1180,7 +1271,7 @@ BookReader.prototype.currentIndex = function() {
1180
1271
  return this.firstIndex; // $$$ TODO page in center of view would be better
1181
1272
  } else if (this.mode == this.constMode2up) {
1182
1273
  // Only allow indices that are actually present in book
1183
- return utils.clamp(this.firstIndex, 0, this._models.book.getNumLeafs() - 1);
1274
+ return utils.clamp(this.firstIndex, 0, this.book.getNumLeafs() - 1);
1184
1275
  } else {
1185
1276
  throw 'currentIndex called for unimplemented mode ' + this.mode;
1186
1277
  }
@@ -1195,13 +1286,11 @@ BookReader.prototype.currentIndex = function() {
1195
1286
  */
1196
1287
  BookReader.prototype.updateFirstIndex = function(
1197
1288
  index,
1198
- { suppressFragmentChange = false } = {}
1289
+ { suppressFragmentChange = false } = {},
1199
1290
  ) {
1200
- // Called multiple times when defaults contains "mode/1up",
1201
- // including after init(). Skip fragment change if no index change
1202
- if (this.firstIndex === index) {
1203
- suppressFragmentChange = true;
1204
- }
1291
+ // If there's no change, do nothing
1292
+ if (this.firstIndex === index) return;
1293
+
1205
1294
  this.firstIndex = index;
1206
1295
  if (!(this.suppressFragmentChange || suppressFragmentChange)) {
1207
1296
  this.trigger(BookReader.eventNames.fragmentChange);
@@ -1212,8 +1301,12 @@ BookReader.prototype.updateFirstIndex = function(
1212
1301
  if (this.options.initialSearchTerm && !suppressFragmentChange) {
1213
1302
  this.suppressFragmentChange = false;
1214
1303
  }
1215
- this.trigger('pageChanged');
1216
- 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);
1217
1310
  };
1218
1311
 
1219
1312
  /**
@@ -1260,24 +1353,42 @@ BookReader.prototype.leftmost = function() {
1260
1353
  }
1261
1354
  };
1262
1355
 
1263
- 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
+ } = {}) {
1264
1365
  if (this.constMode2up == this.mode) {
1265
- this.trigger(BookReader.eventNames.stop);
1266
- 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});
1267
1369
  } else {
1268
- if (this.firstIndex < this.lastDisplayableIndex()) {
1370
+ if (this.firstIndex < this.book.getNumLeafs() - 1) {
1269
1371
  this.jumpToIndex(this.firstIndex + 1);
1270
1372
  }
1271
1373
  }
1272
1374
  };
1273
1375
 
1274
- 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
+ } = {}) {
1275
1385
  const isOnFrontPage = this.firstIndex < 1;
1276
1386
  if (isOnFrontPage) return;
1277
1387
 
1278
1388
  if (this.constMode2up == this.mode) {
1279
- this.trigger(BookReader.eventNames.stop);
1280
- 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});
1281
1392
  } else {
1282
1393
  if (this.firstIndex >= 1) {
1283
1394
  this.jumpToIndex(this.firstIndex - 1);
@@ -1286,257 +1397,13 @@ BookReader.prototype.prev = function() {
1286
1397
  };
1287
1398
 
1288
1399
  BookReader.prototype.first = function() {
1289
- this.jumpToIndex(this.firstDisplayableIndex());
1400
+ this.jumpToIndex(0);
1290
1401
  };
1291
1402
 
1292
1403
  BookReader.prototype.last = function() {
1293
- this.jumpToIndex(this.lastDisplayableIndex());
1294
- };
1295
-
1296
- /**
1297
- * Scrolls down one screen view
1298
- */
1299
- BookReader.prototype.scrollDown = function() {
1300
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1301
- if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) {
1302
- // Whole pages are visible, scroll whole page only
1303
- return this.next();
1304
- }
1305
-
1306
- this.refs.$brContainer.stop(true).animate(
1307
- { scrollTop: '+=' + this._scrollAmount() + 'px'},
1308
- 400, 'easeInOutExpo'
1309
- );
1310
- return true;
1311
- } else {
1312
- return false;
1313
- }
1314
- };
1315
-
1316
- /**
1317
- * Scrolls up one screen view
1318
- */
1319
- BookReader.prototype.scrollUp = function() {
1320
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1321
- if ( this.mode == this.constMode1up && (this.reduce >= this.onePageGetAutofitHeight()) ) {
1322
- // Whole pages are visible, scroll whole page only
1323
- return this.prev();
1324
- }
1325
-
1326
- this.refs.$brContainer.stop(true).animate(
1327
- { scrollTop: '-=' + this._scrollAmount() + 'px'},
1328
- 400, 'easeInOutExpo'
1329
- );
1330
- return true;
1331
- } else {
1332
- return false;
1333
- }
1404
+ this.jumpToIndex(this.book.getNumLeafs() - 1);
1334
1405
  };
1335
1406
 
1336
- /**
1337
- * The amount to scroll vertically in integer pixels
1338
- */
1339
- BookReader.prototype._scrollAmount = function() {
1340
- if (this.constMode1up == this.mode) {
1341
- // Overlap by % of page size
1342
- return parseInt(this.refs.$brContainer.prop('clientHeight') - this._models.book.getPageHeight(this.currentIndex()) / this.reduce * 0.03);
1343
- }
1344
-
1345
- return parseInt(0.9 * this.refs.$brContainer.prop('clientHeight'));
1346
- };
1347
-
1348
- /**
1349
- * @deprecated No longer used; will be remove in v5
1350
- */
1351
- BookReader.prototype.prefetchImg = async function(index, fetchNow = false) {
1352
- console.warn('Call to deprecated function: BookReader.prefetchImg. No-op.');
1353
- };
1354
-
1355
- /**
1356
- * @deprecated No longer used; will be remove in v5
1357
- */
1358
- BookReader.prototype.pruneUnusedImgs = function() {
1359
- console.warn('Call to deprecated function: BookReader.pruneUnused. No-op.');
1360
- };
1361
-
1362
- /************************/
1363
- /** Mode1Up extensions **/
1364
- /************************/
1365
- /** @deprecated not used outside BookReader */
1366
- BookReader.prototype.prepareOnePageView = Mode1Up.prototype.prepare;
1367
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'prepare', 'prepareOnePageView');
1368
- /** @deprecated not used outside BookReader */
1369
- BookReader.prototype.drawLeafsOnePage = Mode1Up.prototype.drawLeafs;
1370
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'drawLeafs', 'drawLeafsOnePage');
1371
- /** @deprecated not used outside BookReader */
1372
- BookReader.prototype.zoom1up = Mode1Up.prototype.zoom;
1373
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'zoom', 'zoom1up');
1374
- /** @deprecated not used outside Mode1Up */
1375
- BookReader.prototype.onePageGetAutofitWidth = Mode1Up.prototype.getAutofitWidth;
1376
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'getAutofitWidth', 'onePageGetAutofitWidth');
1377
- /** @deprecated not used outside Mode1Up, BookReader */
1378
- BookReader.prototype.onePageGetAutofitHeight = Mode1Up.prototype.getAutofitHeight;
1379
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'getAutofitHeight', 'onePageGetAutofitHeight');
1380
- /** @deprecated not used outside Mode1Up, BookReader */
1381
- BookReader.prototype.onePageGetPageTop = Mode1Up.prototype.getPageTop;
1382
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'getPageTop', 'onePageGetPageTop');
1383
- /** @deprecated not used outside Mode1Up, BookReader */
1384
- BookReader.prototype.onePageCalculateReductionFactors = Mode1Up.prototype.calculateReductionFactors;
1385
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'calculateReductionFactors', 'onePageCalculateReductionFactors');
1386
- /** @deprecated not used outside Mode1Up, BookReader */
1387
- BookReader.prototype.resizePageView1up = Mode1Up.prototype.resizePageView;
1388
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'resizePageView', 'resizePageView1up');
1389
- /** @deprecated not used outside Mode1Up */
1390
- BookReader.prototype.onePageCalculateViewDimensions = Mode1Up.prototype.calculateViewDimensions;
1391
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'calculateViewDimensions', 'onePageCalculateViewDimensions');
1392
- /** @deprecated not used outside Mode1Up */
1393
- BookReader.prototype.centerX1up = Mode1Up.prototype.centerX;
1394
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'centerX', 'centerX1up');
1395
- /** @deprecated not used outside Mode1Up */
1396
- BookReader.prototype.centerY1up = Mode1Up.prototype.centerY;
1397
- exposeOverrideableMethod(Mode1Up, '_modes.mode1Up', 'centerY', 'centerY1up');
1398
-
1399
- /************************/
1400
- /** Mode2Up extensions **/
1401
- /************************/
1402
- /** @deprecated not used outside Mode2Up */
1403
- BookReader.prototype.zoom2up = Mode2Up.prototype.zoom;
1404
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'zoom', 'zoom2up');
1405
- BookReader.prototype.twoPageGetAutofitReduce = Mode2Up.prototype.getAutofitReduce;
1406
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getAutofitReduce', 'twoPageGetAutofitReduce');
1407
- BookReader.prototype.flipBackToIndex = Mode2Up.prototype.flipBackToIndex;
1408
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipBackToIndex', 'flipBackToIndex');
1409
- BookReader.prototype.flipFwdToIndex = Mode2Up.prototype.flipFwdToIndex;
1410
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipFwdToIndex', 'flipFwdToIndex');
1411
- BookReader.prototype.setHilightCss2UP = Mode2Up.prototype.setHilightCss;
1412
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setHilightCss', 'setHilightCss2UP');
1413
- /** @deprecated not used outside Mode2Up */
1414
- BookReader.prototype.setClickHandler2UP = Mode2Up.prototype.setClickHandler;
1415
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setClickHandler', 'setClickHandler2UP');
1416
- /** @deprecated not used outside Mode2Up */
1417
- BookReader.prototype.drawLeafsTwoPage = Mode2Up.prototype.drawLeafs;
1418
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'drawLeafs', 'drawLeafsTwoPage');
1419
- /** @deprecated not used outside BookReader */
1420
- BookReader.prototype.prepareTwoPageView = Mode2Up.prototype.prepareTwoPageView;
1421
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'prepareTwoPageView', 'prepareTwoPageView');
1422
- /** @deprecated not used outside Mode2Up */
1423
- BookReader.prototype.prepareTwoPagePopUp = Mode2Up.prototype.preparePopUp;
1424
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'preparePopUp', 'prepareTwoPagePopUp');
1425
- /** @deprecated not used outside BookReader, Mode2Up */
1426
- BookReader.prototype.calculateSpreadSize = Mode2Up.prototype.calculateSpreadSize;
1427
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'calculateSpreadSize', 'calculateSpreadSize');
1428
- /** @deprecated not used outside BookReader, Mode2Up */
1429
- BookReader.prototype.getIdealSpreadSize = Mode2Up.prototype.getIdealSpreadSize;
1430
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getIdealSpreadSize', 'getIdealSpreadSize');
1431
- /** @deprecated not used outside BookReader, Mode2Up */
1432
- BookReader.prototype.getSpreadSizeFromReduce = Mode2Up.prototype.getSpreadSizeFromReduce;
1433
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getSpreadSizeFromReduce', 'getSpreadSizeFromReduce');
1434
- /** @deprecated unused */
1435
- BookReader.prototype.twoPageIsZoomedIn = Mode2Up.prototype.isZoomedIn;
1436
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'isZoomedIn', 'twoPageIsZoomedIn');
1437
- /** @deprecated not used outside BookReader */
1438
- BookReader.prototype.twoPageCalculateReductionFactors = Mode2Up.prototype.calculateReductionFactors;
1439
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'calculateReductionFactors', 'twoPageCalculateReductionFactors');
1440
- /** @deprecated unused */
1441
- BookReader.prototype.twoPageSetCursor = Mode2Up.prototype.setCursor;
1442
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setCursor', 'twoPageSetCursor');
1443
- /** @deprecated unused outside BookReader, Mode2Up */
1444
- BookReader.prototype.flipLeftToRight = Mode2Up.prototype.flipLeftToRight;
1445
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipLeftToRight', 'flipLeftToRight');
1446
- /** @deprecated unused outside BookReader, Mode2Up */
1447
- BookReader.prototype.flipRightToLeft = Mode2Up.prototype.flipRightToLeft;
1448
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipRightToLeft', 'flipRightToLeft');
1449
- /** @deprecated unused outside Mode2Up */
1450
- BookReader.prototype.setMouseHandlers2UP = Mode2Up.prototype.setMouseHandlers;
1451
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setMouseHandlers', 'setMouseHandlers2UP');
1452
- /** @deprecated unused outside BookReader, Mode2Up */
1453
- BookReader.prototype.prepareFlipLeftToRight = Mode2Up.prototype.prepareFlipLeftToRight;
1454
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'prepareFlipLeftToRight', 'prepareFlipLeftToRight');
1455
- /** @deprecated unused outside BookReader, Mode2Up */
1456
- BookReader.prototype.prepareFlipRightToLeft = Mode2Up.prototype.prepareFlipRightToLeft;
1457
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'prepareFlipRightToLeft', 'prepareFlipRightToLeft');
1458
- /** @deprecated unused outside Mode2Up */
1459
- BookReader.prototype.getPageWidth2UP = Mode2Up.prototype.getPageWidth;
1460
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getPageWidth', 'getPageWidth2UP');
1461
- /** @deprecated unused outside Mode2Up */
1462
- BookReader.prototype.twoPageGutter = Mode2Up.prototype.gutter;
1463
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'gutter', 'twoPageGutter');
1464
- /** @deprecated unused outside Mode2Up */
1465
- BookReader.prototype.twoPageTop = Mode2Up.prototype.top;
1466
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'top', 'twoPageTop');
1467
- /** @deprecated unused outside Mode2Up */
1468
- BookReader.prototype.twoPageCoverWidth = Mode2Up.prototype.coverWidth;
1469
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'coverWidth', 'twoPageCoverWidth');
1470
- /** @deprecated unused outside Mode2Up */
1471
- BookReader.prototype.twoPageGetViewCenter = Mode2Up.prototype.getViewCenter;
1472
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'getViewCenter', 'twoPageGetViewCenter');
1473
- /** @deprecated unused outside BookReader, Mode2Up */
1474
- BookReader.prototype.twoPageCenterView = Mode2Up.prototype.centerView;
1475
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'centerView', 'twoPageCenterView');
1476
- /** @deprecated unused outside Mode2Up */
1477
- BookReader.prototype.twoPageFlipAreaHeight = Mode2Up.prototype.flipAreaHeight;
1478
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipAreaHeight', 'twoPageFlipAreaHeight');
1479
- /** @deprecated unused outside Mode2Up */
1480
- BookReader.prototype.twoPageFlipAreaWidth = Mode2Up.prototype.flipAreaWidth;
1481
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipAreaWidth', 'twoPageFlipAreaWidth');
1482
- /** @deprecated unused outside BookReader, Mode2Up */
1483
- BookReader.prototype.twoPageFlipAreaTop = Mode2Up.prototype.flipAreaTop;
1484
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'flipAreaTop', 'twoPageFlipAreaTop');
1485
- /** @deprecated unused outside Mode2Up */
1486
- BookReader.prototype.twoPageLeftFlipAreaLeft = Mode2Up.prototype.leftFlipAreaLeft;
1487
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'leftFlipAreaLeft', 'twoPageLeftFlipAreaLeft');
1488
- /** @deprecated unused outside Mode2Up */
1489
- BookReader.prototype.twoPageRightFlipAreaLeft = Mode2Up.prototype.rightFlipAreaLeft;
1490
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'rightFlipAreaLeft', 'twoPageRightFlipAreaLeft');
1491
- /** @deprecated unused outside BookReader, Mode2Up */
1492
- BookReader.prototype.gutterOffsetForIndex = Mode2Up.prototype.gutterOffsetForIndex;
1493
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'gutterOffsetForIndex', 'gutterOffsetForIndex');
1494
- /** @deprecated unused outside BookReader, Mode2Up */
1495
- BookReader.prototype.leafEdgeWidth = Mode2Up.prototype.leafEdgeWidth;
1496
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'leafEdgeWidth', 'leafEdgeWidth');
1497
- /** @deprecated unused outside BookReader, Mode2Up */
1498
- BookReader.prototype.jumpIndexForLeftEdgePageX = Mode2Up.prototype.jumpIndexForLeftEdgePageX;
1499
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'jumpIndexForLeftEdgePageX', 'jumpIndexForLeftEdgePageX');
1500
- /** @deprecated unused outside BookReader, Mode2Up */
1501
- BookReader.prototype.jumpIndexForRightEdgePageX = Mode2Up.prototype.jumpIndexForRightEdgePageX;
1502
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'jumpIndexForRightEdgePageX', 'jumpIndexForRightEdgePageX');
1503
- /** @deprecated unused outside Mode2Up */
1504
- BookReader.prototype.prefetch = Mode2Up.prototype.prefetch;
1505
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'prefetch', 'prefetch');
1506
- /** @deprecated unused outside Mode2Up */
1507
- BookReader.prototype.setSpreadIndices = Mode2Up.prototype.setSpreadIndices;
1508
- exposeOverrideableMethod(Mode2Up, '_modes.mode2Up', 'setSpreadIndices', 'setSpreadIndices');
1509
- /**
1510
- * Immediately stop flip animations. Callbacks are triggered.
1511
- */
1512
- BookReader.prototype.stopFlipAnimations = function() {
1513
- this.trigger(BookReader.eventNames.stop);
1514
-
1515
- // Stop animation, clear queue, trigger callbacks
1516
- if (this.leafEdgeTmp) {
1517
- $(this.leafEdgeTmp).stop(false, true);
1518
- }
1519
- jQuery.each(this._modes.mode2Up.pageContainers, function() {
1520
- $(this.$container).stop(false, true);
1521
- });
1522
-
1523
- // And again since animations also queued in callbacks
1524
- if (this.leafEdgeTmp) {
1525
- $(this.leafEdgeTmp).stop(false, true);
1526
- }
1527
- jQuery.each(this._modes.mode2Up.pageContainers, function() {
1528
- $(this.$container).stop(false, true);
1529
- });
1530
- };
1531
-
1532
- /**
1533
- * Returns true if keyboard navigation should be disabled for the event
1534
- * @param {Event}
1535
- * @return {boolean}
1536
- */
1537
- BookReader.prototype.keyboardNavigationIsDisabled = function(event) {
1538
- return event.target.tagName == "INPUT";
1539
- };
1540
1407
 
1541
1408
  /**
1542
1409
  * @template TClass extends { br: BookReader }
@@ -1560,30 +1427,9 @@ function exposeOverrideableMethod(Class, classKey, method, brMethod = method) {
1560
1427
  /***********************/
1561
1428
  /** Navbar extensions **/
1562
1429
  /***********************/
1430
+ /** This cannot be removed yet because plugin.tts.js overrides it */
1563
1431
  BookReader.prototype.initNavbar = Navbar.prototype.init;
1564
1432
  exposeOverrideableMethod(Navbar, '_components.navbar', 'init', 'initNavbar');
1565
- BookReader.prototype.switchNavbarControls = Navbar.prototype.switchNavbarControls;
1566
- exposeOverrideableMethod(Navbar, '_components.navbar', 'switchNavbarControls');
1567
- BookReader.prototype.updateViewModeButton = Navbar.prototype.updateViewModeButton;
1568
- exposeOverrideableMethod(Navbar, '_components.navbar', 'updateViewModeButton');
1569
- BookReader.prototype.getNavPageNumString = Navbar.prototype.getNavPageNumString;
1570
- exposeOverrideableMethod(Navbar, '_components.navbar', 'getNavPageNumString');
1571
- /** @deprecated */
1572
- BookReader.prototype.initEmbedNavbar = Navbar.prototype.initEmbed;
1573
- exposeOverrideableMethod(Navbar, '_components.navbar', 'initEmbed', 'initEmbedNavbar');
1574
- /** @deprecated unused */
1575
- BookReader.prototype.getNavPageNumHtml = getNavPageNumHtml;
1576
- /** @deprecated unused outside this file */
1577
- BookReader.prototype.updateNavPageNum = Navbar.prototype.updateNavPageNum;
1578
- exposeOverrideableMethod(Navbar, '_components.navbar', 'updateNavPageNum');
1579
- /** @deprecated unused outside this file */
1580
- BookReader.prototype.updateNavIndex = Navbar.prototype.updateNavIndex;
1581
- exposeOverrideableMethod(Navbar, '_components.navbar', 'updateNavIndex');
1582
- /** @deprecated unused outside this file */
1583
- BookReader.prototype.updateNavIndexThrottled = utils.throttle(BookReader.prototype.updateNavIndex, 250, false);
1584
- /** @deprecated unused */
1585
- BookReader.prototype.updateNavIndexDebounced = utils.debounce(BookReader.prototype.updateNavIndex, 500, false);
1586
-
1587
1433
 
1588
1434
  /************************/
1589
1435
  /** Toolbar extensions **/
@@ -1598,24 +1444,14 @@ BookReader.prototype.buildInfoDiv = Toolbar.prototype.buildInfoDiv;
1598
1444
  exposeOverrideableMethod(Toolbar, '_components.toolbar', 'buildInfoDiv');
1599
1445
  BookReader.prototype.getToolBarHeight = Toolbar.prototype.getToolBarHeight;
1600
1446
  exposeOverrideableMethod(Toolbar, '_components.toolbar', 'getToolBarHeight');
1601
- /** @deprecated zoom no longer in toolbar */
1602
- BookReader.prototype.updateToolbarZoom = Toolbar.prototype.updateToolbarZoom;
1603
- exposeOverrideableMethod(Toolbar, '_components.toolbar', 'updateToolbarZoom');
1604
- /** @deprecated unused */
1605
- BookReader.prototype.blankInfoDiv = blankInfoDiv;
1606
- /** @deprecated unused */
1607
- BookReader.prototype.blankShareDiv = blankShareDiv;
1608
- /** @deprecated unused */
1609
- BookReader.prototype.createPopup = createPopup;
1610
1447
 
1611
1448
  /**
1612
1449
  * Bind navigation handlers
1613
1450
  */
1614
1451
  BookReader.prototype.bindNavigationHandlers = function() {
1615
1452
  const self = this;
1453
+ const jIcons = this.$('.BRicon');
1616
1454
 
1617
- // Note the mobile plugin attaches itself to body, so we need to select outside
1618
- const jIcons = this.$('.BRicon').add('.BRmobileMenu .BRicon');
1619
1455
  // Map of jIcon class -> click handler
1620
1456
  const navigationControls = {
1621
1457
  book_left: () => {
@@ -1626,20 +1462,6 @@ BookReader.prototype.bindNavigationHandlers = function() {
1626
1462
  this.trigger(BookReader.eventNames.stop);
1627
1463
  this.right();
1628
1464
  },
1629
- book_up: () => {
1630
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1631
- this.scrollUp();
1632
- } else {
1633
- this.prev();
1634
- }
1635
- },
1636
- book_down: () => {
1637
- if ($.inArray(this.mode, [this.constMode1up, this.constModeThumb]) >= 0) {
1638
- this.scrollDown();
1639
- } else {
1640
- this.next();
1641
- }
1642
- },
1643
1465
  book_top: this.first.bind(this),
1644
1466
  book_bottom: this.last.bind(this),
1645
1467
  book_leftmost: this.leftmost.bind(this),
@@ -1665,7 +1487,7 @@ BookReader.prototype.bindNavigationHandlers = function() {
1665
1487
  },
1666
1488
  full: () => {
1667
1489
  if (this.ui == 'embed') {
1668
- var url = this.$('.BRembedreturn a').attr('href');
1490
+ const url = this.$('.BRembedreturn a').attr('href');
1669
1491
  window.open(url);
1670
1492
  } else {
1671
1493
  this.toggleFullscreen();
@@ -1673,29 +1495,31 @@ BookReader.prototype.bindNavigationHandlers = function() {
1673
1495
  },
1674
1496
  };
1675
1497
 
1676
- jIcons.filter('.fit').bind('fit', function() {
1677
- // 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);
1678
1502
  });
1679
1503
 
1680
1504
  for (const control in navigationControls) {
1681
1505
  jIcons.filter(`.${control}`).on('click.bindNavigationHandlers', () => {
1682
- navigationControls[control]()
1506
+ navigationControls[control]();
1683
1507
  return false;
1684
1508
  });
1685
1509
  }
1686
1510
 
1687
- var $brNavCntlBtmEl = this.$('.BRnavCntlBtm');
1688
- var $brNavCntlTopEl = this.$('.BRnavCntlTop');
1511
+ const $brNavCntlBtmEl = this.$('.BRnavCntlBtm');
1512
+ const $brNavCntlTopEl = this.$('.BRnavCntlTop');
1689
1513
 
1690
1514
  this.$('.BRnavCntl').click(
1691
1515
  function() {
1692
- var promises = [];
1516
+ const promises = [];
1693
1517
  // TODO don't use magic constants
1694
1518
  // TODO move this to a function
1695
1519
  if ($brNavCntlBtmEl.hasClass('BRdn')) {
1696
1520
  if (self.refs.$BRtoolbar)
1697
1521
  promises.push(self.refs.$BRtoolbar.animate(
1698
- {top: self.getToolBarHeight() * -1}
1522
+ {top: self.getToolBarHeight() * -1},
1699
1523
  ).promise());
1700
1524
  promises.push(self.$('.BRfooter').animate({bottom: self.getFooterHeight() * -1}).promise());
1701
1525
  $brNavCntlBtmEl.addClass('BRup').removeClass('BRdn');
@@ -1709,7 +1533,7 @@ BookReader.prototype.bindNavigationHandlers = function() {
1709
1533
  $brNavCntlBtmEl.addClass('BRdn').removeClass('BRup');
1710
1534
  $brNavCntlTopEl.addClass('BRup').removeClass('BRdn');
1711
1535
  self.$('.BRnavCntlBtm.BRnavCntl').animate({height:'30px'});
1712
- self.$('.BRvavCntl').animate({opacity:1})
1536
+ self.$('.BRvavCntl').animate({opacity:1});
1713
1537
  }
1714
1538
  $.when.apply($, promises).done(function() {
1715
1539
  // Only do full resize in auto mode and need to recalc. size
@@ -1725,388 +1549,45 @@ BookReader.prototype.bindNavigationHandlers = function() {
1725
1549
  self.resizeBRcontainer();
1726
1550
  }
1727
1551
  });
1728
- }
1729
- );
1730
- $brNavCntlBtmEl.mouseover(function() {
1731
- if ($(this).hasClass('BRup')) {
1732
- self.$('.BRnavCntl').animate({opacity:1},250);
1733
- }
1734
- }).mouseleave(function() {
1735
- if ($(this).hasClass('BRup')) {
1736
- self.$('.BRnavCntl').animate({opacity:.75},250);
1737
- }
1738
- });
1739
- $brNavCntlTopEl.mouseover(function() {
1740
- if ($(this).hasClass('BRdn')) {
1741
- self.$('.BRnavCntl').animate({opacity:1},250);
1742
- }
1743
- }).mouseleave(function() {
1744
- if ($(this).hasClass('BRdn')) {
1745
- self.$('.BRnavCntl').animate({opacity:.75},250);
1746
- }
1747
- });
1748
-
1749
- this.initSwipeData();
1750
-
1751
- $(document).off('mousemove.navigation', this.el);
1752
- $(document).on(
1753
- 'mousemove.navigation',
1754
- this.el,
1755
- { 'br': this },
1756
- this.navigationMousemoveHandler
1757
- );
1758
-
1759
- $(document).off('mousedown.swipe', '.BRpageimage');
1760
- $(document).on(
1761
- 'mousedown.swipe',
1762
- '.BRpageimage',
1763
- { 'br': this },
1764
- this.swipeMousedownHandler
1765
- );
1766
-
1767
- this.bindMozTouchHandlers();
1768
- };
1769
-
1770
- /**
1771
- * Unbind navigation handlers
1772
- */
1773
- BookReader.prototype.unbindNavigationHandlers = function() {
1774
- $(document).off('mousemove.navigation', this.el);
1775
- };
1776
-
1777
- /**
1778
- * Handle mousemove related to navigation. Bind at #BookReader level to allow autohide.
1779
- */
1780
- BookReader.prototype.navigationMousemoveHandler = function(event) {
1781
- // $$$ possibly not great to be calling this for every mousemove
1782
- if (event.data['br'].uiAutoHide) {
1783
- // 77px is an approximate height of the Internet Archive Top Nav
1784
- // 75 & 76 (pixels) provide used in this context is checked against the IA top nav height
1785
- var navkey = $(document).height() - 75;
1786
- if ((event.pageY < 76) || (event.pageY > navkey)) {
1787
- // inside or near navigation elements
1788
- event.data['br'].hideNavigation();
1789
- } else {
1790
- event.data['br'].showNavigation();
1791
- }
1792
- }
1793
- };
1794
-
1795
- BookReader.prototype.initSwipeData = function(clientX, clientY) {
1796
- /*
1797
- * Based on the really quite awesome "Today's Guardian" at http://guardian.gyford.com/
1798
- */
1799
- this._swipe = {
1800
- mightBeSwiping: false,
1801
- didSwipe: false,
1802
- mightBeDraggin: false,
1803
- didDrag: false,
1804
- startTime: (new Date).getTime(),
1805
- startX: clientX,
1806
- startY: clientY,
1807
- lastX: clientX,
1808
- lastY: clientY,
1809
- deltaX: 0,
1810
- deltaY: 0,
1811
- deltaT: 0
1812
- }
1813
- };
1814
-
1815
- BookReader.prototype.swipeMousedownHandler = function(event) {
1816
- var self = event.data['br'];
1817
-
1818
- // We should be the last bubble point for the page images
1819
- // Disable image drag and select, but keep right-click
1820
- if (event.which == 3) {
1821
- return !self.protected;
1822
- }
1823
-
1824
- $(event.target).bind('mouseout.swipe',
1825
- { 'br': self},
1826
- self.swipeMouseupHandler
1827
- ).bind('mouseup.swipe',
1828
- { 'br': self},
1829
- self.swipeMouseupHandler
1830
- ).bind('mousemove.swipe',
1831
- { 'br': self },
1832
- self.swipeMousemoveHandler
1552
+ },
1833
1553
  );
1834
-
1835
- self.initSwipeData(event.clientX, event.clientY);
1836
- self._swipe.mightBeSwiping = true;
1837
- self._swipe.mightBeDragging = true;
1838
-
1839
- event.preventDefault();
1840
- event.returnValue = false;
1841
- event.cancelBubble = true;
1842
- return false;
1843
- };
1844
-
1845
- BookReader.prototype.swipeMousemoveHandler = function(event) {
1846
- var self = event.data['br'];
1847
- var _swipe = self._swipe;
1848
- if (! _swipe.mightBeSwiping) {
1849
- return;
1850
- }
1851
-
1852
- // Update swipe data
1853
- _swipe.deltaX = event.clientX - _swipe.startX;
1854
- _swipe.deltaY = event.clientY - _swipe.startY;
1855
- _swipe.deltaT = (new Date).getTime() - _swipe.startTime;
1856
-
1857
- var absX = Math.abs(_swipe.deltaX);
1858
- var absY = Math.abs(_swipe.deltaY);
1859
-
1860
- // Minimum distance in the amount of tim to trigger the swipe
1861
- var minSwipeLength = Math.min(self.refs.$br.width() / 5, 80);
1862
- var maxSwipeTime = 400;
1863
-
1864
- // Check for horizontal swipe
1865
- if (absX > absY && (absX > minSwipeLength) && _swipe.deltaT < maxSwipeTime) {
1866
- _swipe.mightBeSwiping = false; // only trigger once
1867
- _swipe.didSwipe = true;
1868
- if (self.mode == self.constMode2up) {
1869
- if (_swipe.deltaX < 0) {
1870
- self.right();
1871
- } else {
1872
- self.left();
1554
+ $brNavCntlBtmEl
1555
+ .on("mouseover", function() {
1556
+ if ($(this).hasClass('BRup')) {
1557
+ self.$('.BRnavCntl').animate({opacity:1},250);
1873
1558
  }
1874
- }
1875
- }
1876
-
1877
- if ( _swipe.deltaT > maxSwipeTime && !_swipe.didSwipe) {
1878
- if (_swipe.mightBeDragging) {
1879
- // Dragging
1880
- _swipe.didDrag = true;
1881
- self.refs.$brContainer
1882
- .scrollTop(self.refs.$brContainer.scrollTop() - event.clientY + _swipe.lastY)
1883
- .scrollLeft(self.refs.$brContainer.scrollLeft() - event.clientX + _swipe.lastX);
1884
- }
1885
- }
1886
- _swipe.lastX = event.clientX;
1887
- _swipe.lastY = event.clientY;
1888
-
1889
- event.preventDefault();
1890
- event.returnValue = false;
1891
- event.cancelBubble = true;
1892
- return false;
1893
- };
1894
-
1895
- BookReader.prototype.swipeMouseupHandler = function(event) {
1896
- var _swipe = event.data['br']._swipe;
1897
- _swipe.mightBeSwiping = false;
1898
- _swipe.mightBeDragging = false;
1899
-
1900
- $(event.target).unbind('mouseout.swipe').unbind('mouseup.swipe').unbind('mousemove.swipe');
1901
-
1902
- if (_swipe.didSwipe || _swipe.didDrag) {
1903
- // Swallow event if completed swipe gesture
1904
- event.preventDefault();
1905
- event.returnValue = false;
1906
- event.cancelBubble = true;
1907
- return false;
1908
- }
1909
- return true;
1910
- };
1911
-
1912
- BookReader.prototype.bindMozTouchHandlers = function() {
1913
- var self = this;
1914
-
1915
- // Currently only want touch handlers in 2up
1916
- this.refs.$br.bind('MozTouchDown', function(event) {
1917
- if (this.mode == self.constMode2up) {
1918
- event.preventDefault();
1919
- }
1920
- })
1921
- .bind('MozTouchMove', function(event) {
1922
- if (this.mode == self.constMode2up) {
1923
- event.preventDefault();
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);
1924
1569
  }
1925
1570
  })
1926
- .bind('MozTouchUp', function(event) {
1927
- if (this.mode == self.constMode2up) {
1928
- event.preventDefault();
1571
+ .on("mouseleave", function() {
1572
+ if ($(this).hasClass('BRdn')) {
1573
+ self.$('.BRnavCntl').animate({opacity:.75},250);
1929
1574
  }
1930
1575
  });
1931
- };
1932
-
1933
- /**
1934
- * Returns true if the navigation elements are currently visible
1935
- * @return {boolean}
1936
- */
1937
- BookReader.prototype.navigationIsVisible = function() {
1938
- // $$$ doesn't account for transitioning states, nav must be fully visible to return true
1939
- var toolpos = this.refs.$BRtoolbar.position();
1940
- var tooltop = toolpos.top;
1941
- return tooltop == 0;
1942
- };
1943
-
1944
- /**
1945
- * Main controller that sets navigation into view.
1946
- * Defaults to SHOW the navigation chrome
1947
- */
1948
- BookReader.prototype.setNavigationView = function brSetNavigationView(hide) {
1949
- var animationLength = this.constNavAnimationDuration;
1950
- var animationType = 'linear';
1951
- var resizePageContainer = function resizePageContainer () {
1952
- /* main page container fills whole container */
1953
- if (this.constMode2up !== this.mode) {
1954
- var animate = true;
1955
- this.resizeBRcontainer(animate);
1956
- }
1957
- this.trigger(BookReader.eventNames.navToggled);
1958
- }.bind(this);
1959
-
1960
- var toolbarHeight = 0;
1961
- var navbarHeight = 0;
1962
- if (hide) {
1963
- toolbarHeight = this.getToolBarHeight() * -1;
1964
- navbarHeight = this.getFooterHeight() * -1;
1965
-
1966
- this.refs.$BRtoolbar.addClass('js-menu-hide');
1967
- this.refs.$BRfooter.addClass('js-menu-hide');
1968
- } else {
1969
- this.refs.$BRtoolbar.removeClass('js-menu-hide');
1970
- this.refs.$BRfooter.removeClass('js-menu-hide');
1971
- }
1972
-
1973
- this.refs.$BRtoolbar.animate(
1974
- { top: toolbarHeight },
1975
- animationLength,
1976
- animationType,
1977
- resizePageContainer
1978
- );
1979
- this.refs.$BRfooter.animate(
1980
- { bottom: navbarHeight },
1981
- animationLength,
1982
- animationType,
1983
- resizePageContainer
1984
- );
1985
- };
1986
- /**
1987
- * Hide navigation elements, if visible
1988
- */
1989
- BookReader.prototype.hideNavigation = function() {
1990
- // Check if navigation is showing
1991
- if (this.navigationIsVisible()) {
1992
- var hide = true;
1993
- this.setNavigationView(hide);
1994
- }
1995
- };
1996
-
1997
- /**
1998
- * Show navigation elements
1999
- */
2000
- BookReader.prototype.showNavigation = function() {
2001
- // Check if navigation is hidden
2002
- if (!this.navigationIsVisible()) {
2003
- this.setNavigationView();
2004
- }
2005
- };
2006
-
2007
- /**
2008
- * Returns the index of the first visible page, dependent on the mode.
2009
- * $$$ Currently we cannot display the front/back cover in 2-up and will need to update
2010
- * this function when we can as part of https://bugs.launchpad.net/gnubook/+bug/296788
2011
- * @return {number}
2012
- */
2013
- BookReader.prototype.firstDisplayableIndex = function() {
2014
- if (this.mode != this.constMode2up) {
2015
- return 0;
2016
- }
2017
1576
 
2018
- if ('rl' != this.pageProgression) {
2019
- // LTR
2020
- if (this._models.book.getPageSide(0) == 'L') {
2021
- return 0;
2022
- } else {
2023
- return -1;
2024
- }
2025
- } else {
2026
- // RTL
2027
- if (this._models.book.getPageSide(0) == 'R') {
2028
- return 0;
2029
- } else {
2030
- return -1;
2031
- }
1577
+ // Call _bindNavigationHandlers on the plugins
1578
+ for (const plugin of Object.values(this._plugins)) {
1579
+ plugin._bindNavigationHandlers();
2032
1580
  }
2033
1581
  };
2034
1582
 
2035
- /**
2036
- * Returns the index of the last visible page, dependent on the mode.
2037
- * $$$ Currently we cannot display the front/back cover in 2-up and will need to update
2038
- * this function when we can as part of https://bugs.launchpad.net/gnubook/+bug/296788
2039
- * @return {number}
2040
- */
2041
- BookReader.prototype.lastDisplayableIndex = function() {
2042
-
2043
- var lastIndex = this._models.book.getNumLeafs() - 1;
2044
-
2045
- if (this.mode != this.constMode2up) {
2046
- return lastIndex;
2047
- }
2048
-
2049
- if ('rl' != this.pageProgression) {
2050
- // LTR
2051
- if (this._models.book.getPageSide(lastIndex) == 'R') {
2052
- return lastIndex;
2053
- } else {
2054
- return lastIndex + 1;
2055
- }
2056
- } else {
2057
- // RTL
2058
- if (this._models.book.getPageSide(lastIndex) == 'L') {
2059
- return lastIndex;
2060
- } else {
2061
- return lastIndex + 1;
2062
- }
2063
- }
2064
- };
2065
-
2066
-
2067
1583
  /**************************/
2068
1584
  /** BookModel extensions **/
2069
1585
  /**************************/
2070
- /** @deprecated not used outside */
2071
- BookReader.prototype.getMedianPageSize = BookModel.prototype.getMedianPageSize;
2072
- exposeOverrideableMethod(BookModel, '_models.book', 'getMedianPageSize');
2073
- BookReader.prototype._getPageWidth = BookModel.prototype._getPageWidth;
2074
- exposeOverrideableMethod(BookModel, '_models.book', '_getPageWidth');
2075
- BookReader.prototype._getPageHeight = BookModel.prototype._getPageHeight;
2076
- exposeOverrideableMethod(BookModel, '_models.book', '_getPageHeight');
2077
- BookReader.prototype.getPageIndex = BookModel.prototype.getPageIndex;
2078
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageIndex');
2079
- /** @deprecated not used outside */
2080
- BookReader.prototype.getPageIndices = BookModel.prototype.getPageIndices;
2081
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageIndices');
2082
- BookReader.prototype.getPageName = BookModel.prototype.getPageName;
2083
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageName');
2084
- BookReader.prototype.getNumLeafs = BookModel.prototype.getNumLeafs;
2085
- exposeOverrideableMethod(BookModel, '_models.book', 'getNumLeafs');
2086
- BookReader.prototype.getPageWidth = BookModel.prototype.getPageWidth;
2087
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageWidth');
2088
- BookReader.prototype.getPageHeight = BookModel.prototype.getPageHeight;
2089
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageHeight');
1586
+ // Must modify petabox extension, which expects this on the prototype
1587
+ // before removing.
2090
1588
  BookReader.prototype.getPageURI = BookModel.prototype.getPageURI;
2091
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageURI');
2092
- BookReader.prototype.getPageSide = BookModel.prototype.getPageSide;
2093
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageSide');
2094
- BookReader.prototype.getPageNum = BookModel.prototype.getPageNum;
2095
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageNum');
2096
- BookReader.prototype.getPageProp = BookModel.prototype.getPageProp;
2097
- exposeOverrideableMethod(BookModel, '_models.book', 'getPageProp');
2098
- BookReader.prototype.getSpreadIndices = BookModel.prototype.getSpreadIndices;
2099
- exposeOverrideableMethod(BookModel, '_models.book', 'getSpreadIndices');
2100
- BookReader.prototype.leafNumToIndex = BookModel.prototype.leafNumToIndex;
2101
- exposeOverrideableMethod(BookModel, '_models.book', 'leafNumToIndex');
2102
- BookReader.prototype.parsePageString = BookModel.prototype.parsePageString;
2103
- exposeOverrideableMethod(BookModel, '_models.book', 'parsePageString');
2104
- /** @deprecated unused */
2105
- BookReader.prototype._getDataFlattened = BookModel.prototype._getDataFlattened;
2106
- exposeOverrideableMethod(BookModel, '_models.book', '_getDataFlattened');
2107
- /** @deprecated unused */
2108
- BookReader.prototype._getDataProp = BookModel.prototype._getDataProp;
2109
- exposeOverrideableMethod(BookModel, '_models.book', '_getDataProp');
1589
+ exposeOverrideableMethod(BookModel, 'book', 'getPageURI');
1590
+
2110
1591
 
2111
1592
  // Parameter related functions
2112
1593
 
@@ -2125,7 +1606,7 @@ BookReader.prototype.updateFromParams = function(params) {
2125
1606
  if (mode) {
2126
1607
  this.switchMode(
2127
1608
  mode,
2128
- { init: init, suppressFragmentChange: !fragmentChange }
1609
+ { init: init, suppressFragmentChange: !fragmentChange },
2129
1610
  );
2130
1611
  }
2131
1612
 
@@ -2137,7 +1618,7 @@ BookReader.prototype.updateFromParams = function(params) {
2137
1618
  }
2138
1619
  } else if ('undefined' != typeof(params.page)) {
2139
1620
  // $$$ this assumes page numbers are unique
2140
- if (params.page != this._models.book.getPageNum(this.currentIndex())) {
1621
+ if (params.page != this.book.getPageNum(this.currentIndex())) {
2141
1622
  this.jumpToPage(params.page);
2142
1623
  }
2143
1624
  }
@@ -2171,7 +1652,7 @@ BookReader.prototype.canSwitchToMode = function(mode) {
2171
1652
  // check there are enough pages to display
2172
1653
  // $$$ this is a workaround for the mis-feature that we can't display
2173
1654
  // short books in 2up mode
2174
- if (this._models.book.getNumLeafs() < 2) {
1655
+ if (this.book.getNumLeafs() < 2) {
2175
1656
  return false;
2176
1657
  }
2177
1658
  }
@@ -2179,31 +1660,6 @@ BookReader.prototype.canSwitchToMode = function(mode) {
2179
1660
  return true;
2180
1661
  };
2181
1662
 
2182
-
2183
- /**
2184
- * @deprecated. Use PageModel.getURISrcSet. Slated for removal in v5.
2185
- * Returns the srcset with correct URIs or void string if out of range
2186
- * Also makes the reduce argument optional
2187
- * @param {number} index
2188
- * @param {number} [reduce]
2189
- * @param {number} [rotate]
2190
- * @return {string}
2191
- */
2192
- BookReader.prototype._getPageURISrcset = function(index, reduce, rotate) {
2193
- const page = this._models.book.getPage(index, false);
2194
- // Synthesize page
2195
- if (!page) return "";
2196
-
2197
- // reduce not passed in
2198
- // $$$ this probably won't work for thumbnail mode
2199
- if ('undefined' == typeof(reduce)) {
2200
- reduce = page.height / this.twoPage.height;
2201
- }
2202
-
2203
- return page.getURISrcSet(reduce, rotate);
2204
- }
2205
-
2206
-
2207
1663
  /**
2208
1664
  * Returns the page URI or transparent image if out of range
2209
1665
  * Also makes the reduce argument optional
@@ -2213,7 +1669,7 @@ BookReader.prototype._getPageURISrcset = function(index, reduce, rotate) {
2213
1669
  * @return {string}
2214
1670
  */
2215
1671
  BookReader.prototype._getPageURI = function(index, reduce, rotate) {
2216
- const page = this._models.book.getPage(index, false);
1672
+ const page = this.book.getPage(index, false);
2217
1673
  // Synthesize page
2218
1674
  if (!page) return this.imagesBaseURL + "transparent.png";
2219
1675
 
@@ -2252,7 +1708,7 @@ BookReader.prototype.showProgressPopup = function(msg, onCloseCallback) {
2252
1708
 
2253
1709
  const bar = document.createElement("div");
2254
1710
  $(bar).css({
2255
- height: '20px'
1711
+ height: '20px',
2256
1712
  }).prop('className', 'BRprogressbar');
2257
1713
  $(this.popup).append(bar);
2258
1714
 
@@ -2279,7 +1735,7 @@ BookReader.prototype.initUIStrings = function() {
2279
1735
  // the toolbar and nav bar easier
2280
1736
 
2281
1737
  // Setup tooltips -- later we could load these from a file for i18n
2282
- var titles = {
1738
+ const titles = {
2283
1739
  '.logo': 'Go to Archive.org', // $$$ update after getting OL record
2284
1740
  '.zoom_in': 'Zoom in',
2285
1741
  '.zoom_out': 'Zoom out',
@@ -2295,8 +1751,6 @@ BookReader.prototype.initUIStrings = function() {
2295
1751
  '.full': 'Toggle fullscreen',
2296
1752
  '.book_left': 'Flip left',
2297
1753
  '.book_right': 'Flip right',
2298
- '.book_up': 'Page up',
2299
- '.book_down': 'Page down',
2300
1754
  '.play': 'Play',
2301
1755
  '.pause': 'Pause',
2302
1756
  '.BRdn': 'Show/hide nav bar', // Would have to keep updating on state change to have just "Hide nav bar"
@@ -2311,10 +1765,10 @@ BookReader.prototype.initUIStrings = function() {
2311
1765
  titles['.book_rightmost'] = 'First page';
2312
1766
  }
2313
1767
 
2314
- for (var icon in titles) {
1768
+ for (const icon in titles) {
2315
1769
  this.$(icon).prop('title', titles[icon]);
2316
1770
  }
2317
- }
1771
+ };
2318
1772
 
2319
1773
  /**
2320
1774
  * Reloads images. Useful when some images might have failed.
@@ -2322,7 +1776,7 @@ BookReader.prototype.initUIStrings = function() {
2322
1776
  BookReader.prototype.reloadImages = function() {
2323
1777
  this.refs.$brContainer.find('img').each(function(index, elem) {
2324
1778
  if (!elem.complete || elem.naturalHeight === 0) {
2325
- var src = elem.src;
1779
+ const src = elem.src;
2326
1780
  elem.src = '';
2327
1781
  setTimeout(function() {
2328
1782
  elem.src = src;
@@ -2336,16 +1790,16 @@ BookReader.prototype.reloadImages = function() {
2336
1790
  * @return {number}
2337
1791
  */
2338
1792
  BookReader.prototype.getFooterHeight = function() {
2339
- 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;
2340
1794
  if ($heightEl && this.refs.$BRfooter) {
2341
- var outerHeight = $heightEl.outerHeight();
2342
- var bottom = parseInt(this.refs.$BRfooter.css('bottom'));
1795
+ const outerHeight = $heightEl.outerHeight();
1796
+ const bottom = parseInt(this.refs.$BRfooter.css('bottom'));
2343
1797
  if (!isNaN(outerHeight) && !isNaN(bottom)) {
2344
1798
  return outerHeight + bottom;
2345
1799
  }
2346
1800
  }
2347
1801
  return 0;
2348
- }
1802
+ };
2349
1803
 
2350
1804
  // Basic Usage built-in Methods (can be overridden through options)
2351
1805
  // This implementation uses options.data value for populating BookReader
@@ -2355,10 +1809,11 @@ BookReader.prototype.getFooterHeight = function() {
2355
1809
  * @return {Object}
2356
1810
  */
2357
1811
  BookReader.prototype.paramsFromCurrent = function() {
2358
- var params = {};
1812
+ const params = {};
2359
1813
 
2360
- var index = this.currentIndex();
2361
- var pageNum = this._models.book.getPageNum(index);
1814
+ // Path params
1815
+ const index = this.currentIndex();
1816
+ const pageNum = this.book.getPageNum(index);
2362
1817
  if ((pageNum === 0) || pageNum) {
2363
1818
  params.page = pageNum;
2364
1819
  }
@@ -2366,10 +1821,17 @@ BookReader.prototype.paramsFromCurrent = function() {
2366
1821
  params.index = index;
2367
1822
  params.mode = this.mode;
2368
1823
 
1824
+ // Unused params
2369
1825
  // $$$ highlight
2370
1826
  // $$$ region
2371
1827
 
2372
- // search
1828
+ // Querystring params
1829
+ // View
1830
+ const fullscreenView = 'theater';
1831
+ if (this.isFullscreenActive) {
1832
+ params.view = fullscreenView;
1833
+ }
1834
+ // Search
2373
1835
  if (this.enableSearch) {
2374
1836
  params.search = this.searchTerm;
2375
1837
  }
@@ -2390,7 +1852,7 @@ BookReader.prototype.paramsFromCurrent = function() {
2390
1852
  * @return {Object}
2391
1853
  */
2392
1854
  BookReader.prototype.paramsFromFragment = function(fragment) {
2393
- var params = {};
1855
+ const params = {};
2394
1856
 
2395
1857
  // For backwards compatibility we allow an initial # character
2396
1858
  // (as from window.location.hash) but don't require it
@@ -2399,7 +1861,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2399
1861
  }
2400
1862
 
2401
1863
  // Simple #nn syntax
2402
- var oldStyleLeafNum = parseInt( /^\d+$/.exec(fragment) );
1864
+ const oldStyleLeafNum = parseInt( /^\d+$/.exec(fragment) );
2403
1865
  if ( !isNaN(oldStyleLeafNum) ) {
2404
1866
  params.index = oldStyleLeafNum;
2405
1867
 
@@ -2408,9 +1870,9 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2408
1870
  }
2409
1871
 
2410
1872
  // Split into key-value pairs
2411
- var urlArray = fragment.split('/');
2412
- var urlHash = {};
2413
- 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) {
2414
1876
  urlHash[urlArray[i]] = urlArray[i + 1];
2415
1877
  }
2416
1878
 
@@ -2426,7 +1888,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2426
1888
  // Index and page
2427
1889
  if ('undefined' != typeof(urlHash['page'])) {
2428
1890
  // page was set -- may not be int
2429
- params.page = urlHash['page'];
1891
+ params.page = decodeURIComponent(urlHash['page']);
2430
1892
  }
2431
1893
 
2432
1894
  // $$$ process /region
@@ -2440,7 +1902,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2440
1902
 
2441
1903
  // $$$ process /theme
2442
1904
  if (urlHash['theme'] != undefined) {
2443
- params.theme = urlHash['theme']
1905
+ params.theme = urlHash['theme'];
2444
1906
  }
2445
1907
  return params;
2446
1908
  };
@@ -2458,11 +1920,10 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
2458
1920
  * @return {string}
2459
1921
  */
2460
1922
  BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2461
- const separator = '/';
2462
1923
  const fragments = [];
2463
1924
 
2464
1925
  if ('undefined' != typeof(params.page)) {
2465
- fragments.push('page', params.page);
1926
+ fragments.push('page', encodeURIComponent(params.page));
2466
1927
  } else {
2467
1928
  if ('undefined' != typeof(params.index)) {
2468
1929
  // Don't have page numbering but we do have the index
@@ -2488,15 +1949,18 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2488
1949
 
2489
1950
  // search
2490
1951
  if (params.search && urlMode === 'hash') {
2491
- fragments.push('search', params.search);
1952
+ fragments.push('search', utils.encodeURIComponentPlus(params.search));
2492
1953
  }
2493
1954
 
2494
- return utils.encodeURIComponentPlus(fragments.join(separator)).replace(/%2F/g, '/');
1955
+ return fragments.join('/');
2495
1956
  };
2496
1957
 
2497
1958
  /**
2498
1959
  * Create, update querystring from the params object
2499
1960
  *
1961
+ * Handles:
1962
+ * view=
1963
+ * q=
2500
1964
  * @param {Object} params
2501
1965
  * @param {string} currQueryString
2502
1966
  * @param {string} [urlMode]
@@ -2505,30 +1969,32 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
2505
1969
  BookReader.prototype.queryStringFromParams = function(
2506
1970
  params,
2507
1971
  currQueryString,
2508
- urlMode = 'hash'
1972
+ urlMode = 'hash',
2509
1973
  ) {
2510
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
+
2511
1984
  if (params.search && urlMode === 'history') {
2512
- newParams.set('q', params.search)
1985
+ newParams.set('q', params.search);
2513
1986
  }
2514
1987
  // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/toString
2515
1988
  // Note: This method returns the query string without the question mark.
2516
1989
  const result = newParams.toString();
2517
1990
  return result ? '?' + result : '';
2518
- }
1991
+ };
2519
1992
 
2520
1993
  /**
2521
1994
  * Helper to select within instance's elements
2522
1995
  */
2523
1996
  BookReader.prototype.$ = function(selector) {
2524
1997
  return this.refs.$br.find(selector);
2525
- }
2526
-
2527
- /**
2528
- * Polyfill for deprecated method
2529
- */
2530
- jQuery.curCSS = function(element, prop, val) {
2531
- return jQuery(element).css(prop, val);
2532
1998
  };
2533
1999
 
2534
2000
  window.BookReader = BookReader;