@internetarchive/bookreader 5.0.0-7 → 5.0.0-70-a1

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 (307) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +73 -10
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +396 -1129
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1788 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +19 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/1up.svg +1 -1
  13. package/BookReader/icons/2up.svg +1 -1
  14. package/BookReader/icons/advance.svg +1 -1
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -1
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -1
  19. package/BookReader/icons/fullscreen_exit.svg +1 -1
  20. package/BookReader/icons/hamburger.svg +1 -1
  21. package/BookReader/icons/left-arrow.svg +1 -1
  22. package/BookReader/icons/magnify-minus.svg +1 -1
  23. package/BookReader/icons/magnify-plus.svg +1 -1
  24. package/BookReader/icons/magnify.svg +1 -1
  25. package/BookReader/icons/pause.svg +1 -1
  26. package/BookReader/icons/play.svg +1 -1
  27. package/BookReader/icons/playback-speed.svg +1 -1
  28. package/BookReader/icons/read-aloud.svg +1 -1
  29. package/BookReader/icons/review.svg +1 -1
  30. package/BookReader/icons/thumbnails.svg +1 -1
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -1
  33. package/BookReader/images/BRicons.svg +3 -3
  34. package/BookReader/images/books_graphic.svg +1 -1
  35. package/BookReader/images/icon_book.svg +1 -1
  36. package/BookReader/images/icon_bookmark.svg +1 -1
  37. package/BookReader/images/icon_gear.svg +1 -1
  38. package/BookReader/images/icon_hamburger.svg +1 -1
  39. package/BookReader/images/icon_home.svg +1 -1
  40. package/BookReader/images/icon_info.svg +1 -1
  41. package/BookReader/images/icon_one_page.svg +1 -1
  42. package/BookReader/images/icon_pause.svg +1 -1
  43. package/BookReader/images/icon_play.svg +1 -1
  44. package/BookReader/images/icon_playback-rate.svg +1 -1
  45. package/BookReader/images/icon_search_button.svg +1 -1
  46. package/BookReader/images/icon_share.svg +1 -1
  47. package/BookReader/images/icon_skip-ahead.svg +1 -1
  48. package/BookReader/images/icon_skip-back.svg +1 -1
  49. package/BookReader/images/icon_speaker.svg +1 -1
  50. package/BookReader/images/icon_speaker_open.svg +1 -1
  51. package/BookReader/images/icon_thumbnails.svg +1 -1
  52. package/BookReader/images/icon_toc.svg +1 -1
  53. package/BookReader/images/icon_two_pages.svg +1 -1
  54. package/BookReader/images/marker_chap-off.svg +1 -1
  55. package/BookReader/images/marker_chap-on.svg +1 -1
  56. package/BookReader/images/marker_srch-on.svg +1 -1
  57. package/BookReader/jquery-3.js +2 -0
  58. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  59. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  60. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  61. package/BookReader/plugins/plugin.autoplay.js +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  63. package/BookReader/plugins/plugin.chapters.js +25 -1
  64. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  65. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  66. package/BookReader/plugins/plugin.iframe.js +1 -1
  67. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  68. package/BookReader/plugins/plugin.resume.js +1 -1
  69. package/BookReader/plugins/plugin.resume.js.map +1 -1
  70. package/BookReader/plugins/plugin.search.js +2 -1
  71. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  72. package/BookReader/plugins/plugin.search.js.map +1 -1
  73. package/BookReader/plugins/plugin.text_selection.js +2 -1
  74. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  75. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  76. package/BookReader/plugins/plugin.tts.js +1 -1
  77. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  78. package/BookReader/plugins/plugin.tts.js.map +1 -1
  79. package/BookReader/plugins/plugin.url.js +1 -1
  80. package/BookReader/plugins/plugin.url.js.map +1 -1
  81. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  82. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  83. package/BookReader/webcomponents-bundle.js +3 -0
  84. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  85. package/BookReader/webcomponents-bundle.js.map +1 -0
  86. package/BookReaderDemo/BookReaderDemo.css +16 -19
  87. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  88. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  89. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  90. package/BookReaderDemo/IADemoBr.js +147 -0
  91. package/BookReaderDemo/demo-advanced.html +2 -2
  92. package/BookReaderDemo/demo-autoplay.html +2 -3
  93. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  94. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  95. package/BookReaderDemo/demo-fullscreen.html +2 -4
  96. package/BookReaderDemo/demo-iiif.html +2 -1
  97. package/BookReaderDemo/demo-iiif.js +0 -1
  98. package/BookReaderDemo/demo-internetarchive.html +213 -17
  99. package/BookReaderDemo/demo-multiple.html +2 -1
  100. package/BookReaderDemo/demo-preview-pages.html +2 -1
  101. package/BookReaderDemo/demo-simple.html +2 -1
  102. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  103. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  104. package/BookReaderDemo/immersion-1up.html +2 -2
  105. package/BookReaderDemo/immersion-mode.html +2 -4
  106. package/BookReaderDemo/toggle_controls.html +3 -2
  107. package/BookReaderDemo/view_mode.html +2 -1
  108. package/BookReaderDemo/viewmode-cycle.html +2 -3
  109. package/CHANGELOG.md +279 -0
  110. package/README.md +14 -1
  111. package/babel.config.js +20 -0
  112. package/codecov.yml +6 -0
  113. package/index.html +4 -1
  114. package/jsconfig.json +19 -0
  115. package/netlify.toml +9 -0
  116. package/package.json +69 -60
  117. package/renovate.json +52 -0
  118. package/scripts/preversion.js +4 -1
  119. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  120. package/src/BookNavigator/assets/button-base.js +4 -2
  121. package/src/BookNavigator/assets/ia-logo.js +17 -0
  122. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  123. package/src/BookNavigator/assets/icon_close.js +1 -1
  124. package/src/BookNavigator/book-navigator.js +586 -0
  125. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  126. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  127. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  128. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  129. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  130. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  131. package/src/BookNavigator/delete-modal-actions.js +1 -1
  132. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  133. package/src/BookNavigator/downloads/downloads.js +41 -25
  134. package/src/BookNavigator/search/search-provider.js +49 -27
  135. package/src/BookNavigator/search/search-results.js +23 -9
  136. package/src/BookNavigator/sharing.js +27 -0
  137. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  138. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  139. package/src/BookNavigator/volumes/volumes-provider.js +95 -0
  140. package/src/BookReader/BookModel.js +64 -34
  141. package/src/BookReader/DragScrollable.js +233 -0
  142. package/src/BookReader/Mode1Up.js +56 -351
  143. package/src/BookReader/Mode1UpLit.js +388 -0
  144. package/src/BookReader/Mode2Up.js +73 -1318
  145. package/src/BookReader/Mode2UpLit.js +776 -0
  146. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  147. package/src/BookReader/ModeSmoothZoom.js +312 -0
  148. package/src/BookReader/ModeThumb.js +18 -12
  149. package/src/BookReader/Navbar/Navbar.js +12 -38
  150. package/src/BookReader/PageContainer.js +81 -6
  151. package/src/BookReader/ReduceSet.js +1 -1
  152. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  153. package/src/BookReader/events.js +2 -3
  154. package/src/BookReader/options.js +24 -2
  155. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  156. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  157. package/src/BookReader/utils/SelectionObserver.js +43 -0
  158. package/src/BookReader/utils.js +118 -13
  159. package/src/BookReader.js +423 -1056
  160. package/src/assets/icons/magnify-minus.svg +3 -7
  161. package/src/assets/icons/magnify-plus.svg +3 -7
  162. package/src/assets/icons/voice.svg +1 -0
  163. package/src/css/BookReader.scss +1 -5
  164. package/src/css/_BRBookmarks.scss +1 -1
  165. package/src/css/_BRComponent.scss +1 -1
  166. package/src/css/_BRmain.scss +16 -0
  167. package/src/css/_BRnav.scss +11 -38
  168. package/src/css/_BRpages.scss +149 -40
  169. package/src/css/_BRsearch.scss +67 -21
  170. package/src/css/_TextSelection.scss +87 -27
  171. package/src/css/_colorbox.scss +2 -2
  172. package/src/css/_controls.scss +20 -7
  173. package/src/css/_icons.scss +1 -1
  174. package/src/ia-bookreader/ia-bookreader.js +224 -0
  175. package/src/plugins/plugin.archive_analytics.js +3 -3
  176. package/src/plugins/plugin.autoplay.js +5 -11
  177. package/src/plugins/plugin.chapters.js +211 -186
  178. package/src/plugins/plugin.resume.js +3 -3
  179. package/src/plugins/plugin.text_selection.js +464 -134
  180. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  181. package/src/plugins/search/plugin.search.js +142 -125
  182. package/src/plugins/search/utils.js +43 -0
  183. package/src/plugins/search/view.js +33 -58
  184. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  185. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  186. package/src/plugins/tts/PageChunk.js +15 -21
  187. package/src/plugins/tts/PageChunkIterator.js +8 -12
  188. package/src/plugins/tts/WebTTSEngine.js +87 -71
  189. package/src/plugins/tts/plugin.tts.js +95 -126
  190. package/src/plugins/tts/utils.js +0 -25
  191. package/src/plugins/url/UrlPlugin.js +193 -0
  192. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  193. package/src/util/browserSniffing.js +22 -0
  194. package/src/util/docCookies.js +21 -2
  195. package/tests/e2e/README.md +37 -0
  196. package/tests/e2e/autoplay.test.js +2 -2
  197. package/tests/e2e/base.test.js +8 -16
  198. package/tests/e2e/helpers/base.js +53 -48
  199. package/tests/e2e/helpers/debug.js +1 -1
  200. package/tests/e2e/helpers/params.js +17 -0
  201. package/tests/e2e/helpers/rightToLeft.js +8 -14
  202. package/tests/e2e/helpers/search.js +73 -0
  203. package/tests/e2e/models/Navigation.js +20 -37
  204. package/tests/e2e/rightToLeft.test.js +4 -5
  205. package/tests/e2e/viewmode.test.js +40 -33
  206. package/tests/jest/BookNavigator/book-navigator.test.js +658 -0
  207. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  208. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  209. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  210. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  211. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  212. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  213. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  214. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  215. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  216. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  217. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +80 -0
  218. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  219. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  220. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  221. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  222. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  223. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  224. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  225. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  226. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  227. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  228. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  229. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  230. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  231. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  232. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  233. package/tests/jest/BookReader/utils/SelectionObserver.test.js +43 -0
  234. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  235. package/tests/jest/BookReader/utils.test.js +229 -0
  236. package/tests/jest/BookReader.keyboard.test.js +190 -0
  237. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  238. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  239. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  240. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  241. package/tests/jest/plugins/plugin.chapters.test.js +145 -0
  242. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  243. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  244. package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
  245. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  246. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +25 -47
  247. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
  248. package/tests/jest/plugins/search/utils.js +25 -0
  249. package/tests/jest/plugins/search/utils.test.js +29 -0
  250. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
  251. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  252. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  253. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  254. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  255. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  256. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  257. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  258. package/tests/jest/setup.js +3 -0
  259. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  260. package/tests/jest/util/docCookies.test.js +24 -0
  261. package/tests/{util → jest/util}/strings.test.js +1 -1
  262. package/tests/{utils.js → jest/utils.js} +38 -0
  263. package/webpack.config.js +11 -6
  264. package/.babelrc +0 -12
  265. package/.dependabot/config.yml +0 -6
  266. package/.testcaferc.json +0 -5
  267. package/BookReader/bookreader-component-bundle.js +0 -1450
  268. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  269. package/BookReader/bookreader-component-bundle.js.map +0 -1
  270. package/BookReader/jquery-1.10.1.js +0 -2
  271. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  272. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  273. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  274. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  275. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  276. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  277. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  278. package/karma.conf.js +0 -23
  279. package/src/BookNavigator/BookModel.js +0 -14
  280. package/src/BookNavigator/BookNavigator.js +0 -446
  281. package/src/BookNavigator/assets/book-loader.js +0 -27
  282. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  283. package/src/BookNavigator/search/a-search-result.js +0 -55
  284. package/src/BookReader/DebugConsole.js +0 -54
  285. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  286. package/src/ItemNavigator/ItemNavigator.js +0 -376
  287. package/src/ItemNavigator/providers/sharing.js +0 -29
  288. package/src/css/_MobileNav.scss +0 -194
  289. package/src/dragscrollable-br.js +0 -261
  290. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  291. package/src/plugins/plugin.mobile_nav.js +0 -287
  292. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  293. package/tests/BookReader/DebugConsole.test.js +0 -25
  294. package/tests/BookReader/Mode1Up.test.js +0 -164
  295. package/tests/BookReader/Mode2Up.test.js +0 -247
  296. package/tests/BookReader/utils.test.js +0 -109
  297. package/tests/e2e/helpers/desktopSearch.js +0 -72
  298. package/tests/e2e/helpers/mobileSearch.js +0 -85
  299. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  300. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  301. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  302. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  303. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  304. package/tests/plugins/plugin.chapters.test.js +0 -130
  305. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  306. package/tests/plugins/plugin.text_selection.test.js +0 -203
  307. package/tests/util/docCookies.test.js +0 -15
@@ -6,8 +6,9 @@ import FestivalTTSEngine from './FestivalTTSEngine.js';
6
6
  import WebTTSEngine from './WebTTSEngine.js';
7
7
  import { toISO6391, approximateWordCount } from './utils.js';
8
8
  import { en as tooltips } from './tooltip_dict.js';
9
- /** @typedef {import('./PageChunk.js')} PageChunk */
10
- /** @typedef {import("./AbstractTTSEngine.js")} AbstractTTSEngine */
9
+ import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
10
+ /** @typedef {import('./PageChunk.js').default} PageChunk */
11
+ /** @typedef {import("./AbstractTTSEngine.js").default} AbstractTTSEngine */
11
12
 
12
13
  // Default options for TTS
13
14
  jQuery.extend(BookReader.defaultOptions, {
@@ -22,7 +23,9 @@ BookReader.prototype.setup = (function (super_) {
22
23
  super_.call(this, options);
23
24
 
24
25
  if (this.options.enableTtsPlugin) {
25
- this.ttsHilites = [];
26
+ /** @type { {[pageIndex: number]: Array<{ l: number, r: number, t: number, b: number }>} } */
27
+ this._ttsBoxesByIndex = {};
28
+
26
29
  let TTSEngine = WebTTSEngine.isSupported() ? WebTTSEngine :
27
30
  FestivalTTSEngine.isSupported() ? FestivalTTSEngine :
28
31
  null;
@@ -54,17 +57,6 @@ BookReader.prototype.init = (function(super_) {
54
57
  if (this.options.enableTtsPlugin) {
55
58
  // Bind to events
56
59
 
57
- // TODO move this to BookReader.js or something
58
- this.bind(BookReader.eventNames.fragmentChange, () => {
59
- if (this.mode == this.constMode2up) {
60
- // clear highlights if they're no longer valid for this page
61
- const visibleIndices = [this.twoPage.currentIndexL, this.twoPage.currentIndexR];
62
- const visibleSelector = visibleIndices.map(i => `.BRReadAloudHilite.Leaf-${i}`).join(', ');
63
- $(this.ttsHilites).filter(visibleSelector).show();
64
- $(this.ttsHilites).not(visibleSelector).hide();
65
- }
66
- });
67
-
68
60
  this.bind(BookReader.eventNames.PostInit, () => {
69
61
  this.$('.BRicon.read').click(() => {
70
62
  this.ttsToggle();
@@ -88,6 +80,17 @@ BookReader.prototype.init = (function(super_) {
88
80
  };
89
81
  })(BookReader.prototype.init);
90
82
 
83
+ /** @override */
84
+ BookReader.prototype._createPageContainer = (function (super_) {
85
+ return function (index) {
86
+ const pageContainer = super_.call(this, index);
87
+ if (this.options.enableTtsPlugin && pageContainer.page && index in this._ttsBoxesByIndex) {
88
+ const pageIndex = pageContainer.page.index;
89
+ renderBoxesInPageContainerLayer('ttsHiliteLayer', this._ttsBoxesByIndex[pageIndex], pageContainer.page, pageContainer.$container[0]);
90
+ }
91
+ return pageContainer;
92
+ };
93
+ })(BookReader.prototype._createPageContainer);
91
94
 
92
95
  // Extend buildMobileDrawerElement
93
96
  BookReader.prototype.buildMobileDrawerElement = (function (super_) {
@@ -146,19 +149,53 @@ BookReader.prototype.initNavbar = (function (super_) {
146
149
  <div class="icon icon-advance"></div>
147
150
  </button>
148
151
  </li>
152
+ <li>
153
+ <select class="playback-voices" name="playback-voice" style="display: none" title="Change read aloud voices">
154
+ </select>
155
+ </li>
149
156
  </ul>
150
157
  `);
158
+
151
159
  $el.find('.BRcontrols').prepend(this.refs.$BRReadAloudToolbar);
160
+
161
+ const renderVoiceOption = (voices) => {
162
+ return voices.map(voice =>
163
+ `<option value="${voice.voiceURI}">${voice.lang} - ${voice.name}</option>`).join('');
164
+ };
165
+
166
+ const voiceSortOrder = (a,b) => `${a.lang} - ${a.name}`.localeCompare(`${b.lang} - ${b.name}`);
167
+
168
+ const renderVoicesMenu = (voicesMenu) => {
169
+ voicesMenu.empty();
170
+ const bookLanguage = this.ttsEngine.opts.bookLanguage;
171
+ const bookLanguages = this.ttsEngine.getVoices().filter(v => v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
172
+ const otherLanguages = this.ttsEngine.getVoices().filter(v => !v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
173
+
174
+ if (this.ttsEngine.getVoices().length > 1) {
175
+ voicesMenu.append($(`<optgroup label="Book Language (${bookLanguage})"> ${renderVoiceOption(bookLanguages)} </optgroup>`));
176
+ voicesMenu.append($(`<optgroup label="Other Languages"> ${renderVoiceOption(otherLanguages)} </optgroup>`));
177
+
178
+ voicesMenu.val(this.ttsEngine.voice.voiceURI);
179
+ voicesMenu.show();
180
+ } else {
181
+ voicesMenu.hide();
182
+ }
183
+ };
184
+
185
+ const voicesMenu = this.refs.$BRReadAloudToolbar.find('[name=playback-voice]');
186
+ renderVoicesMenu(voicesMenu);
187
+ voicesMenu.on("change", ev => this.ttsEngine.setVoice(voicesMenu.val()));
152
188
  this.ttsEngine.events.on('pause resume start', () => this.ttsUpdateState());
153
- this.refs.$BRReadAloudToolbar.find('[name=play]').click(this.ttsPlayPause.bind(this));
154
- this.refs.$BRReadAloudToolbar.find('[name=advance]').click(this.ttsJumpForward.bind(this));
155
- this.refs.$BRReadAloudToolbar.find('[name=review]').click(this.ttsJumpBackward.bind(this));
189
+ this.ttsEngine.events.on('voiceschanged', () => renderVoicesMenu(voicesMenu));
190
+ this.refs.$BRReadAloudToolbar.find('[name=play]').on("click", this.ttsPlayPause.bind(this));
191
+ this.refs.$BRReadAloudToolbar.find('[name=advance]').on("click", this.ttsJumpForward.bind(this));
192
+ this.refs.$BRReadAloudToolbar.find('[name=review]').on("click", this.ttsJumpBackward.bind(this));
156
193
  const $rateSelector = this.refs.$BRReadAloudToolbar.find('select[name="playback-speed"]');
157
- $rateSelector.change(ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
194
+ $rateSelector.on("change", ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
158
195
  $(`<li>
159
196
  <button class="BRicon read js-tooltip" title="${tooltips.readAloud}">
160
197
  <div class="icon icon-read-aloud"></div>
161
- <span class="tooltip">${tooltips.readAloud}</span>
198
+ <span class="BRtooltip">${tooltips.readAloud}</span>
162
199
  </button>
163
200
  </li>`).insertBefore($el.find('.BRcontrols .BRicon.zoom_out').closest('li'));
164
201
  }
@@ -187,7 +224,7 @@ BookReader.prototype.ttsStart = function (startTTSEngine = true) {
187
224
  this.$('.BRicon.read').addClass('unread active');
188
225
  this.ttsSendAnalyticsEvent('Start');
189
226
  if (startTTSEngine)
190
- this.ttsEngine.start(this.currentIndex(), this.getNumLeafs());
227
+ this.ttsEngine.start(this.currentIndex(), this.book.getNumLeafs());
191
228
  };
192
229
 
193
230
  BookReader.prototype.ttsJumpForward = function () {
@@ -233,12 +270,10 @@ BookReader.prototype.ttsStop = function () {
233
270
  * @param {PageChunk} chunk
234
271
  * @return {PromiseLike<void>} returns once the flip is done
235
272
  */
236
- BookReader.prototype.ttsBeforeChunkPlay = function(chunk) {
237
- return this.ttsMaybeFlipToIndex(chunk.leafIndex)
238
- .then(() => {
239
- this.ttsHighlightChunk(chunk);
240
- this.ttsScrollToChunk(chunk);
241
- });
273
+ BookReader.prototype.ttsBeforeChunkPlay = async function(chunk) {
274
+ await this.ttsMaybeFlipToIndex(chunk.leafIndex);
275
+ this.ttsHighlightChunk(chunk);
276
+ this.ttsScrollToChunk(chunk);
242
277
  };
243
278
 
244
279
  /**
@@ -251,129 +286,63 @@ BookReader.prototype.ttsSendChunkFinishedAnalyticsEvent = function(chunk) {
251
286
  /**
252
287
  * Flip the page if the provided leaf index is not visible
253
288
  * @param {Number} leafIndex
254
- * @return {PromiseLike<void>} resolves once the flip animation has completed
255
289
  */
256
- BookReader.prototype.ttsMaybeFlipToIndex = function (leafIndex) {
257
- const in2PageMode = this.constMode2up == this.mode;
258
- let resolve = null;
259
- const promise = new Promise(res => resolve = res);
260
-
261
- if (!in2PageMode) {
290
+ BookReader.prototype.ttsMaybeFlipToIndex = async function (leafIndex) {
291
+ if (this.constMode2up != this.mode) {
262
292
  this.jumpToIndex(leafIndex);
263
- resolve();
264
293
  } else {
265
- const leafVisible = leafIndex == this.twoPage.currentIndexR || leafIndex == this.twoPage.currentIndexL;
266
- if (leafVisible) {
267
- resolve();
268
- } else {
269
- this.animationFinishedCallback = resolve;
270
- const mustGoNext = leafIndex > Math.max(this.twoPage.currentIndexR, this.twoPage.currentIndexL);
271
- if (mustGoNext) this.next();
272
- else this.prev();
273
- promise.then(this.ttsMaybeFlipToIndex.bind(this, leafIndex));
274
- }
275
- }
276
-
277
- return promise;
278
- }
279
-
280
- /**
281
- * @param {PageChunk} chunk
282
- */
283
- BookReader.prototype.ttsHighlightChunk = function(chunk) {
284
- this.ttsRemoveHilites();
285
-
286
- if (this.constMode2up == this.mode) {
287
- this.ttsHilite2UP(chunk);
288
- } else {
289
- this.ttsHilite1UP(chunk);
294
+ await this._modes.mode2Up.mode2UpLit.jumpToIndex(leafIndex);
290
295
  }
291
296
  };
292
297
 
293
298
  /**
294
299
  * @param {PageChunk} chunk
295
300
  */
296
- BookReader.prototype.ttsScrollToChunk = function(chunk) {
297
- if (this.constMode1up != this.mode) return;
298
-
299
- let leafTop = 0;
300
- let h;
301
- let i;
302
- for (i = 0; i < chunk.leafIndex; i++) {
303
- h = parseInt(this._getPageHeight(i) / this.reduce);
304
- leafTop += h + this.padding;
305
- }
306
-
307
- const chunkTop = chunk.lineRects[0][3]; //coords are in l,b,r,t order
308
- const chunkBot = chunk.lineRects[chunk.lineRects.length - 1][1];
309
-
310
- const topOfFirstChunk = leafTop + chunkTop / this.reduce;
311
- const botOfLastChunk = leafTop + chunkBot / this.reduce;
312
-
313
- if (window?.soundManager?.debugMode) console.log('leafTop = ' + leafTop + ' topOfFirstChunk = ' + topOfFirstChunk + ' botOfLastChunk = ' + botOfLastChunk);
301
+ BookReader.prototype.ttsHighlightChunk = function(chunk) {
302
+ // The poorly-named variable leafIndex
303
+ const pageIndex = chunk.leafIndex;
314
304
 
315
- const containerTop = this.refs.$brContainer.prop('scrollTop');
316
- const containerBot = containerTop + this.refs.$brContainer.height();
317
- if (window?.soundManager?.debugMode) console.log('containerTop = ' + containerTop + ' containerBot = ' + containerBot);
305
+ this.ttsRemoveHilites();
318
306
 
319
- if ((topOfFirstChunk < containerTop) || (botOfLastChunk > containerBot)) {
320
- this.refs.$brContainer.stop(true).animate({scrollTop: topOfFirstChunk},'fast');
321
- }
322
- };
307
+ // group by index; currently only possible to have chunks on one page :/
308
+ this._ttsBoxesByIndex = {
309
+ [pageIndex]: chunk.lineRects.map(([l, b, r, t]) => ({l, r, b, t}))
310
+ };
323
311
 
324
- /**
325
- * @param {PageChunk} chunk
326
- */
327
- BookReader.prototype.ttsHilite1UP = function(chunk) {
328
- for (let i = 0; i < chunk.lineRects.length; i++) {
329
- //each rect is an array of l,b,r,t coords (djvu.xml ordering...)
330
- const l = chunk.lineRects[i][0];
331
- const b = chunk.lineRects[i][1];
332
- const r = chunk.lineRects[i][2];
333
- const t = chunk.lineRects[i][3];
334
-
335
- const div = document.createElement('div');
336
- this.ttsHilites.push(div);
337
- $(div).prop('className', 'BookReaderSearchHilite').appendTo(
338
- this.$('.pagediv' + chunk.leafIndex)
339
- );
340
-
341
- $(div).css({
342
- width: (r - l) / this.reduce + 'px',
343
- height: (b - t) / this.reduce + 'px',
344
- left: l / this.reduce + 'px',
345
- top: t / this.reduce + 'px'
346
- });
312
+ // update any already created pages
313
+ for (const [pageIndexString, boxes] of Object.entries(this._ttsBoxesByIndex)) {
314
+ const pageIndex = parseFloat(pageIndexString);
315
+ const page = this.book.getPage(pageIndex);
316
+ const pageContainers = this.getActivePageContainerElementsForIndex(pageIndex);
317
+ pageContainers.forEach(container => renderBoxesInPageContainerLayer('ttsHiliteLayer', boxes, page, container));
347
318
  }
348
-
349
319
  };
350
320
 
351
321
  /**
352
322
  * @param {PageChunk} chunk
353
323
  */
354
- BookReader.prototype.ttsHilite2UP = function (chunk) {
355
- for (let i = 0; i < chunk.lineRects.length; i++) {
356
- //each rect is an array of l,b,r,t coords (djvu.xml ordering...)
357
- const l = chunk.lineRects[i][0];
358
- const b = chunk.lineRects[i][1];
359
- const r = chunk.lineRects[i][2];
360
- const t = chunk.lineRects[i][3];
361
-
362
- const div = document.createElement('div');
363
- this.ttsHilites.push(div);
364
- $(div)
365
- .prop('className', 'BookReaderSearchHilite BRReadAloudHilite Leaf-' + chunk.leafIndex)
366
- .css('zIndex', 3)
367
- .appendTo(this.refs.$brTwoPageView);
368
- this.setHilightCss2UP(div, chunk.leafIndex, l, r, t, b);
369
- }
324
+ BookReader.prototype.ttsScrollToChunk = function(chunk) {
325
+ // It behaves weird if used in thumb mode
326
+ if (this.constModeThumb == this.mode) return;
327
+
328
+ $(`.pagediv${chunk.leafIndex} .ttsHiliteLayer rect`).last()?.[0]?.scrollIntoView({
329
+ // Only vertically center the highlight if we're in 1up or in full screen. In
330
+ // 2up, if we're not fullscreen, the whole body gets scrolled around to try to
331
+ // center the highlight 🙄 See:
332
+ // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
333
+ // Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
334
+ // full-width, and covers up the last line of the highlight.
335
+ block: this.constMode1up == this.mode || this.isFullscreenActive ? 'center' : 'nearest',
336
+ inline: 'center',
337
+ behavior: 'smooth',
338
+ });
370
339
  };
371
340
 
372
341
  // ttsRemoveHilites()
373
342
  //______________________________________________________________________________
374
343
  BookReader.prototype.ttsRemoveHilites = function () {
375
- $(this.ttsHilites).remove();
376
- this.ttsHilites = [];
344
+ $(this.getActivePageContainerElements()).find('.ttsHiliteLayer').remove();
345
+ this._ttsBoxesByIndex = {};
377
346
  };
378
347
 
379
348
  /**
@@ -1,21 +1,5 @@
1
1
  import langs from 'iso-language-codes/js/data.js';
2
2
 
3
- /**
4
- * Convert a EventTarget style event into a promise
5
- * @param {EventTarget} target
6
- * @param {string} eventType
7
- * @return {Promise<Event>}
8
- */
9
- export function promisifyEvent(target, eventType) {
10
- return new Promise(res => {
11
- const resolver = ev => {
12
- target.removeEventListener(eventType, resolver);
13
- res(ev);
14
- };
15
- target.addEventListener(eventType, resolver);
16
- });
17
- }
18
-
19
3
  /**
20
4
  * Use regex to approximate word count in a string
21
5
  * @param {string} text
@@ -26,15 +10,6 @@ export function approximateWordCount(text) {
26
10
  return m ? m.length : 0;
27
11
  }
28
12
 
29
- /**
30
- * Waits the provided number of ms and then resolves with a promise
31
- * @param {number} ms
32
- * @return {Promise}
33
- */
34
- export function sleep(ms) {
35
- return new Promise(res => setTimeout(res, ms));
36
- }
37
-
38
13
  /**
39
14
  * Checks whether the current browser is on android
40
15
  * @param {string} [userAgent]
@@ -0,0 +1,193 @@
1
+ export class UrlPlugin {
2
+ constructor(options = {}) {
3
+ this.bookReaderOptions = options;
4
+
5
+ // the canonical order of elements is important in the path and query string
6
+ this.urlSchema = [
7
+ { name: 'page', position: 'path', default: 'n0' },
8
+ { name: 'mode', position: 'path', default: '2up' },
9
+ { name: 'search', position: 'path', deprecated_for: 'q' },
10
+ { name: 'q', position: 'query_param' },
11
+ { name: 'sort', position: 'query_param' },
12
+ { name: 'view', position: 'query_param' },
13
+ { name: 'admin', position: 'query_param' },
14
+ ];
15
+
16
+ this.urlState = {};
17
+ this.urlMode = this.bookReaderOptions.urlMode || 'hash';
18
+ this.urlHistoryBasePath = this.bookReaderOptions.urlHistoryBasePath || '/';
19
+ this.urlLocationPollId = null;
20
+ this.oldLocationHash = null;
21
+ this.oldUserHash = null;
22
+ }
23
+
24
+ /**
25
+ * Parse JSON object URL state to string format
26
+ * Arrange path names in an order that it is positioned on the urlSchema
27
+ * @param {Object} urlState
28
+ * @returns {string}
29
+ */
30
+ urlStateToUrlString(urlState) {
31
+ const searchParams = new URLSearchParams();
32
+ const pathParams = {};
33
+
34
+ Object.keys(urlState).forEach(key => {
35
+ let schema = this.urlSchema.find(schema => schema.name === key);
36
+ if (schema?.deprecated_for) {
37
+ schema = this.urlSchema.find(schemaKey => schemaKey.name === schema.deprecated_for);
38
+ }
39
+ if (schema?.position == 'path') {
40
+ pathParams[schema?.name] = urlState[key];
41
+ } else {
42
+ searchParams.append(schema?.name || key, urlState[key]);
43
+ }
44
+ });
45
+
46
+ const strPathParams = this.urlSchema
47
+ .filter(s => s.position == 'path')
48
+ .map(schema => pathParams[schema.name] ? `${schema.name}/${pathParams[schema.name]}` : '')
49
+ .join('/');
50
+
51
+ // replace consecutive slashes with a single slash + remove trailing slashes
52
+ const strStrippedTrailingSlash = `${strPathParams.replace(/\/+/g, '/').replace(/\/+$/, '')}`;
53
+ const concatenatedPath = `${strStrippedTrailingSlash}?${searchParams.toString()}`;
54
+ return searchParams.toString() ? concatenatedPath : `${strStrippedTrailingSlash}`;
55
+ }
56
+
57
+ /**
58
+ * Parse string URL and add it in the current urlState
59
+ * Example:
60
+ * /page/n7/mode/2up => {page: 'n7', mode: '2up'}
61
+ * /page/n7/mode/2up/search/hello => {page: 'n7', mode: '2up', q: 'hello'}
62
+ * @param {string} urlString
63
+ * @returns {object}
64
+ */
65
+ urlStringToUrlState(urlString) {
66
+ const urlState = {};
67
+
68
+ // Fetch searchParams from given {str}
69
+ // Note: whole URL path is needed for URL parsing
70
+ const urlPath = new URL(urlString, 'http://example.com');
71
+ const urlSearchParamsObj = Object.fromEntries(urlPath.searchParams.entries());
72
+ const splitUrlMatches = urlPath.pathname.match(/[^\\/]+\/[^\\/]+/g);
73
+ const urlStrSplitSlashObj = splitUrlMatches ? Object.fromEntries(splitUrlMatches.map(x => x.split('/'))) : {};
74
+
75
+ const doesKeyExists = (_object, _key) => {
76
+ return Object.keys(_object).some(value => value == _key);
77
+ };
78
+
79
+ // Add path objects to urlState
80
+ this.urlSchema
81
+ .filter(schema => schema.position == 'path')
82
+ .forEach(schema => {
83
+ const hasPropertyKey = doesKeyExists(urlStrSplitSlashObj, schema.name);
84
+ const hasDeprecatedKey = doesKeyExists(schema, 'deprecated_for') && hasPropertyKey;
85
+
86
+ if (hasDeprecatedKey) {
87
+ urlState[schema.deprecated_for] = urlStrSplitSlashObj[schema.name];
88
+ return;
89
+ }
90
+
91
+ if (hasPropertyKey) {
92
+ urlState[schema.name] = urlStrSplitSlashObj[schema.name];
93
+ return;
94
+ }
95
+ });
96
+
97
+ // Add searchParams to urlState
98
+ Object.entries(urlSearchParamsObj).forEach(([key, value]) => {
99
+ urlState[key] = value;
100
+ });
101
+
102
+ return urlState;
103
+ }
104
+
105
+ /**
106
+ * Add or update key-value to the urlState
107
+ * @param {string} key
108
+ * @param {string} val
109
+ */
110
+ setUrlParam(key, value) {
111
+ this.urlState[key] = value;
112
+
113
+ this.pushToAddressBar();
114
+ }
115
+
116
+ /**
117
+ * Delete key-value to the urlState
118
+ * @param {string} key
119
+ */
120
+ removeUrlParam(key) {
121
+ delete this.urlState[key];
122
+
123
+ this.pushToAddressBar();
124
+ }
125
+
126
+ /**
127
+ * Get key-value from the urlState
128
+ * @param {string} key
129
+ * @return {string}
130
+ */
131
+ getUrlParam(key) {
132
+ return this.urlState[key];
133
+ }
134
+
135
+ /**
136
+ * Push URL params to addressbar
137
+ */
138
+ pushToAddressBar() {
139
+ const urlStrPath = this.urlStateToUrlString(this.urlState);
140
+ const concatenatedPath = urlStrPath !== '/' ? urlStrPath : '';
141
+ if (this.urlMode == 'history') {
142
+ if (!window.history || !window.history.replaceState) {
143
+ this.options.urlMode = 'hash';
144
+ } else {
145
+ const newUrlPath = `${this.urlHistoryBasePath}${concatenatedPath}`.trim().replace(/(\/+)/g, '/');
146
+ try {
147
+ window.history.replaceState({}, null, newUrlPath);
148
+ } catch (e) {
149
+ // DOMException on Chrome when in sandboxed iframe
150
+ this.urlMode = 'hash';
151
+ }
152
+ }
153
+ }
154
+
155
+ if (this.urlMode == 'hash') {
156
+ window.location.replace('#' + concatenatedPath);
157
+ }
158
+ this.oldLocationHash = urlStrPath;
159
+ }
160
+
161
+ /**
162
+ * Get the url and check if it has changed
163
+ * If it was changeed, update the urlState
164
+ */
165
+ listenForHashChanges() {
166
+ this.oldLocationHash = window.location.hash.substr(1);
167
+ if (this.urlLocationPollId) {
168
+ clearInterval(this.urlLocationPollId);
169
+ this.urlLocationPollId = null;
170
+ }
171
+
172
+ // check if the URL changes
173
+ const updateHash = () => {
174
+ const newFragment = window.location.hash.substr(1);
175
+ const hasFragmentChange = newFragment != this.oldLocationHash;
176
+
177
+ if (!hasFragmentChange) { return; }
178
+
179
+ this.urlState = this.urlStringToUrlState(newFragment);
180
+ };
181
+ this.urlLocationPollId = setInterval(updateHash, 500);
182
+ }
183
+
184
+ /**
185
+ * Will read either the hash or URL and return the bookreader fragment
186
+ */
187
+ pullFromAddressBar (location = window.location) {
188
+ const path = this.urlMode === 'history'
189
+ ? (location.pathname.substr(this.urlHistoryBasePath.length) + location.search)
190
+ : location.hash.substr(1);
191
+ this.urlState = this.urlStringToUrlState(path);
192
+ }
193
+ }
@@ -1,4 +1,7 @@
1
1
  /* global BookReader */
2
+
3
+ import { UrlPlugin } from "./UrlPlugin";
4
+
2
5
  /**
3
6
  * Plugin for URL management in BookReader
4
7
  * Note read more about the url "fragment" here:
@@ -22,7 +25,7 @@ jQuery.extend(BookReader.defaultOptions, {
22
25
  urlHistoryBasePath: '/',
23
26
 
24
27
  /** Only these params will be reflected onto the URL */
25
- urlTrackedParams: ['page', 'search', 'mode', 'region', 'highlight'],
28
+ urlTrackedParams: ['page', 'search', 'mode', 'region', 'highlight', 'view'],
26
29
 
27
30
  /** If true, don't update the URL when `page == n0 (eg "/page/n0")` */
28
31
  urlTrackIndex0: false,
@@ -50,7 +53,7 @@ BookReader.prototype.init = (function(super_) {
50
53
  this.bind(BookReader.eventNames.PostInit, () => {
51
54
  const { updateWindowTitle, urlMode } = this.options;
52
55
  if (updateWindowTitle) {
53
- document.title = this.shortTitle(50);
56
+ document.title = this.shortTitle(this.bookTitle, 50);
54
57
  }
55
58
  if (urlMode === 'hash') {
56
59
  this.urlStartLocationPolling();
@@ -86,7 +89,7 @@ BookReader.prototype.urlStartLocationPolling = function() {
86
89
  this.oldLocationHash = this.urlReadFragment();
87
90
 
88
91
  if (this.locationPollId) {
89
- clearInterval(this.locationPollID);
92
+ clearInterval(this.locationPollId);
90
93
  this.locationPollId = null;
91
94
  }
92
95
 
@@ -110,7 +113,7 @@ BookReader.prototype.urlStartLocationPolling = function() {
110
113
  updateParams();
111
114
  }
112
115
  this.oldUserHash = newFragment;
113
- }
116
+ };
114
117
 
115
118
  this.locationPollId = setInterval(updateHash, 500);
116
119
  };
@@ -121,7 +124,7 @@ BookReader.prototype.urlStartLocationPolling = function() {
121
124
  */
122
125
  BookReader.prototype.urlUpdateFragment = function() {
123
126
  const allParams = this.paramsFromCurrent();
124
- const { urlMode, urlTrackIndex0, urlTrackedParams } = this.options;
127
+ const { urlTrackIndex0, urlTrackedParams } = this.options;
125
128
 
126
129
  if (!urlTrackIndex0
127
130
  && (typeof(allParams.index) !== 'undefined')
@@ -134,32 +137,39 @@ BookReader.prototype.urlUpdateFragment = function() {
134
137
  if (paramName in allParams) {
135
138
  validParams[paramName] = allParams[paramName];
136
139
  }
137
- return validParams
140
+ return validParams;
138
141
  }, {});
139
142
 
140
- const newFragment = this.fragmentFromParams(params, urlMode);
143
+ const newFragment = this.fragmentFromParams(params, this.options.urlMode);
141
144
  const currFragment = this.urlReadFragment();
142
145
  const currQueryString = this.getLocationSearch();
143
- const newQueryString = this.queryStringFromParams(params, currQueryString, urlMode);
146
+ const newQueryString = this.queryStringFromParams(params, currQueryString, this.options.urlMode);
144
147
  if (currFragment === newFragment && currQueryString === newQueryString) {
145
148
  return;
146
149
  }
147
150
 
148
- if (urlMode === 'history') {
149
- if (window.history && window.history.replaceState) {
151
+ if (this.options.urlMode === 'history') {
152
+ if (!window.history || !window.history.replaceState) {
153
+ this.options.urlMode = 'hash';
154
+ } else {
150
155
  const baseWithoutSlash = this.options.urlHistoryBasePath.replace(/\/+$/, '');
151
156
  const newFragmentWithSlash = newFragment === '' ? '' : `/${newFragment}`;
152
157
 
153
158
  const newUrlPath = `${baseWithoutSlash}${newFragmentWithSlash}${newQueryString}`;
154
- window.history.replaceState({}, null, newUrlPath);
155
- this.oldLocationHash = newFragment + newQueryString;
156
-
159
+ try {
160
+ window.history.replaceState({}, null, newUrlPath);
161
+ this.oldLocationHash = newFragment + newQueryString;
162
+ } catch (e) {
163
+ // DOMException on Chrome when in sandboxed iframe
164
+ this.options.urlMode = 'hash';
165
+ }
157
166
  }
158
- } else {
167
+ }
168
+
169
+ if (this.options.urlMode === 'hash') {
159
170
  const newQueryStringSearch = this.urlParamsFiltersOnlySearch(this.readQueryString());
160
171
  window.location.replace('#' + newFragment + newQueryStringSearch);
161
172
  this.oldLocationHash = newFragment + newQueryStringSearch;
162
-
163
173
  }
164
174
  };
165
175
 
@@ -173,7 +183,7 @@ BookReader.prototype.urlUpdateFragment = function() {
173
183
  BookReader.prototype.urlParamsFiltersOnlySearch = function(url) {
174
184
  const params = new URLSearchParams(url);
175
185
  return params.has('q') ? `?${new URLSearchParams({ q: params.get('q') })}` : '';
176
- }
186
+ };
177
187
 
178
188
 
179
189
  /**
@@ -196,3 +206,22 @@ BookReader.prototype.urlReadFragment = function() {
196
206
  BookReader.prototype.urlReadHashFragment = function() {
197
207
  return window.location.hash.substr(1);
198
208
  };
209
+ export class BookreaderUrlPlugin extends BookReader {
210
+ init() {
211
+ if (this.options.enableUrlPlugin) {
212
+ this.urlPlugin = new UrlPlugin(this.options);
213
+ this.bind(BookReader.eventNames.PostInit, () => {
214
+ const { urlMode } = this.options;
215
+
216
+ if (urlMode === 'hash') {
217
+ this.urlPlugin.listenForHashChanges();
218
+ }
219
+ });
220
+ }
221
+
222
+ super.init();
223
+ }
224
+ }
225
+
226
+ window.BookReader = BookreaderUrlPlugin;
227
+ export default BookreaderUrlPlugin;