@internetarchive/bookreader 5.0.0-3 → 5.0.0-30-d

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