@internetarchive/bookreader 5.0.0-18

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