@internetarchive/bookreader 5.0.0-8 → 5.0.0-80

Sign up to get free protection for your applications and to get access to all the features.
Files changed (313) 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 +398 -1133
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1782 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +7 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/1up.svg +1 -1
  13. package/BookReader/icons/2up.svg +1 -1
  14. package/BookReader/icons/advance.svg +1 -1
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -1
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -1
  19. package/BookReader/icons/fullscreen_exit.svg +1 -1
  20. package/BookReader/icons/hamburger.svg +1 -1
  21. package/BookReader/icons/left-arrow.svg +1 -1
  22. package/BookReader/icons/magnify-minus.svg +1 -1
  23. package/BookReader/icons/magnify-plus.svg +1 -1
  24. package/BookReader/icons/magnify.svg +1 -1
  25. package/BookReader/icons/pause.svg +1 -1
  26. package/BookReader/icons/play.svg +1 -1
  27. package/BookReader/icons/playback-speed.svg +1 -1
  28. package/BookReader/icons/read-aloud.svg +1 -1
  29. package/BookReader/icons/review.svg +1 -1
  30. package/BookReader/icons/thumbnails.svg +1 -1
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -1
  33. package/BookReader/images/BRicons.svg +3 -3
  34. package/BookReader/images/books_graphic.svg +1 -1
  35. package/BookReader/images/icon_book.svg +1 -1
  36. package/BookReader/images/icon_bookmark.svg +1 -1
  37. package/BookReader/images/icon_gear.svg +1 -1
  38. package/BookReader/images/icon_hamburger.svg +1 -1
  39. package/BookReader/images/icon_home.svg +1 -1
  40. package/BookReader/images/icon_info.svg +1 -1
  41. package/BookReader/images/icon_one_page.svg +1 -1
  42. package/BookReader/images/icon_pause.svg +1 -1
  43. package/BookReader/images/icon_play.svg +1 -1
  44. package/BookReader/images/icon_playback-rate.svg +1 -1
  45. package/BookReader/images/icon_search_button.svg +1 -1
  46. package/BookReader/images/icon_share.svg +1 -1
  47. package/BookReader/images/icon_skip-ahead.svg +1 -1
  48. package/BookReader/images/icon_skip-back.svg +1 -1
  49. package/BookReader/images/icon_speaker.svg +1 -1
  50. package/BookReader/images/icon_speaker_open.svg +1 -1
  51. package/BookReader/images/icon_thumbnails.svg +1 -1
  52. package/BookReader/images/icon_toc.svg +1 -1
  53. package/BookReader/images/icon_two_pages.svg +1 -1
  54. package/BookReader/images/marker_chap-off.svg +1 -1
  55. package/BookReader/images/marker_chap-on.svg +1 -1
  56. package/BookReader/images/marker_srch-on.svg +1 -1
  57. package/BookReader/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.iiif.js +2 -0
  69. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  70. package/BookReader/plugins/plugin.resume.js +1 -1
  71. package/BookReader/plugins/plugin.resume.js.map +1 -1
  72. package/BookReader/plugins/plugin.search.js +2 -1
  73. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  74. package/BookReader/plugins/plugin.search.js.map +1 -1
  75. package/BookReader/plugins/plugin.text_selection.js +2 -1
  76. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  77. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  78. package/BookReader/plugins/plugin.tts.js +1 -1
  79. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  80. package/BookReader/plugins/plugin.tts.js.map +1 -1
  81. package/BookReader/plugins/plugin.url.js +1 -1
  82. package/BookReader/plugins/plugin.url.js.map +1 -1
  83. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  85. package/BookReader/webcomponents-bundle.js +3 -0
  86. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  87. package/BookReader/webcomponents-bundle.js.map +1 -0
  88. package/BookReaderDemo/BookReaderDemo.css +18 -19
  89. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  90. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  91. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  92. package/BookReaderDemo/IADemoBr.js +147 -0
  93. package/BookReaderDemo/demo-advanced.html +2 -2
  94. package/BookReaderDemo/demo-autoplay.html +2 -3
  95. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  96. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  97. package/BookReaderDemo/demo-fullscreen.html +2 -4
  98. package/BookReaderDemo/demo-iiif.html +99 -12
  99. package/BookReaderDemo/demo-internetarchive.html +214 -18
  100. package/BookReaderDemo/demo-multiple.html +2 -1
  101. package/BookReaderDemo/demo-preview-pages.html +2 -1
  102. package/BookReaderDemo/demo-simple.html +2 -1
  103. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  104. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  105. package/BookReaderDemo/immersion-1up.html +2 -2
  106. package/BookReaderDemo/immersion-mode.html +2 -4
  107. package/BookReaderDemo/toggle_controls.html +3 -2
  108. package/BookReaderDemo/view_mode.html +2 -1
  109. package/BookReaderDemo/viewmode-cycle.html +2 -3
  110. package/CHANGELOG.md +536 -33
  111. package/README.md +14 -1
  112. package/babel.config.js +20 -0
  113. package/codecov.yml +6 -0
  114. package/index.html +4 -1
  115. package/jsconfig.json +19 -0
  116. package/netlify.toml +9 -0
  117. package/package.json +70 -60
  118. package/renovate.json +52 -0
  119. package/scripts/preversion.js +0 -1
  120. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  121. package/src/BookNavigator/assets/button-base.js +4 -2
  122. package/src/BookNavigator/assets/ia-logo.js +17 -0
  123. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  124. package/src/BookNavigator/assets/icon_close.js +1 -1
  125. package/src/BookNavigator/book-navigator.js +590 -0
  126. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  127. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  128. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  129. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  130. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  131. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  132. package/src/BookNavigator/delete-modal-actions.js +1 -1
  133. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  134. package/src/BookNavigator/downloads/downloads.js +41 -25
  135. package/src/BookNavigator/search/search-provider.js +49 -27
  136. package/src/BookNavigator/search/search-results.js +23 -9
  137. package/src/BookNavigator/sharing.js +27 -0
  138. package/src/BookNavigator/viewable-files.js +95 -0
  139. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  140. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  141. package/src/BookReader/BookModel.js +64 -34
  142. package/src/BookReader/DragScrollable.js +233 -0
  143. package/src/BookReader/Mode1Up.js +56 -351
  144. package/src/BookReader/Mode1UpLit.js +388 -0
  145. package/src/BookReader/Mode2Up.js +73 -1318
  146. package/src/BookReader/Mode2UpLit.js +776 -0
  147. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  148. package/src/BookReader/ModeSmoothZoom.js +312 -0
  149. package/src/BookReader/ModeThumb.js +18 -12
  150. package/src/BookReader/Navbar/Navbar.js +14 -40
  151. package/src/BookReader/PageContainer.js +81 -6
  152. package/src/BookReader/ReduceSet.js +1 -1
  153. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  154. package/src/BookReader/events.js +2 -3
  155. package/src/BookReader/options.js +27 -2
  156. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  157. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  158. package/src/BookReader/utils/SelectionObserver.js +45 -0
  159. package/src/BookReader/utils.js +118 -13
  160. package/src/BookReader.js +427 -1061
  161. package/src/assets/icons/magnify-minus.svg +3 -7
  162. package/src/assets/icons/magnify-plus.svg +3 -7
  163. package/src/assets/icons/voice.svg +1 -0
  164. package/src/css/BookReader.scss +1 -5
  165. package/src/css/_BRBookmarks.scss +1 -1
  166. package/src/css/_BRComponent.scss +1 -1
  167. package/src/css/_BRmain.scss +16 -3
  168. package/src/css/_BRnav.scss +12 -39
  169. package/src/css/_BRpages.scss +149 -40
  170. package/src/css/_BRsearch.scss +68 -25
  171. package/src/css/_BRtoolbar.scss +5 -5
  172. package/src/css/_TextSelection.scss +87 -27
  173. package/src/css/_colorbox.scss +2 -2
  174. package/src/css/_controls.scss +20 -7
  175. package/src/css/_icons.scss +1 -1
  176. package/src/ia-bookreader/ia-bookreader.js +224 -0
  177. package/src/plugins/plugin.archive_analytics.js +3 -3
  178. package/src/plugins/plugin.autoplay.js +5 -11
  179. package/src/plugins/plugin.chapters.js +237 -191
  180. package/src/plugins/plugin.iiif.js +151 -0
  181. package/src/plugins/plugin.resume.js +3 -3
  182. package/src/plugins/plugin.text_selection.js +464 -134
  183. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  184. package/src/plugins/search/plugin.search.js +142 -125
  185. package/src/plugins/search/utils.js +43 -0
  186. package/src/plugins/search/view.js +33 -58
  187. package/src/plugins/tts/AbstractTTSEngine.js +71 -40
  188. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  189. package/src/plugins/tts/PageChunk.js +15 -21
  190. package/src/plugins/tts/PageChunkIterator.js +8 -12
  191. package/src/plugins/tts/WebTTSEngine.js +87 -71
  192. package/src/plugins/tts/plugin.tts.js +96 -127
  193. package/src/plugins/tts/utils.js +15 -25
  194. package/src/plugins/url/UrlPlugin.js +191 -0
  195. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  196. package/src/util/browserSniffing.js +22 -0
  197. package/src/util/docCookies.js +21 -2
  198. package/tests/e2e/README.md +37 -0
  199. package/tests/e2e/autoplay.test.js +2 -2
  200. package/tests/e2e/base.test.js +8 -16
  201. package/tests/e2e/helpers/base.js +53 -48
  202. package/tests/e2e/helpers/debug.js +1 -1
  203. package/tests/e2e/helpers/params.js +17 -0
  204. package/tests/e2e/helpers/rightToLeft.js +8 -14
  205. package/tests/e2e/helpers/search.js +73 -0
  206. package/tests/e2e/models/Navigation.js +20 -37
  207. package/tests/e2e/rightToLeft.test.js +4 -5
  208. package/tests/e2e/viewmode.test.js +40 -33
  209. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  210. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  211. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  212. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  213. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  214. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  215. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  216. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  217. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  218. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  219. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  220. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  221. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  222. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  223. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  224. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  225. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  226. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  227. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  228. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  229. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  230. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  231. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  232. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  233. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  234. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  235. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  236. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  237. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  238. package/tests/jest/BookReader/utils.test.js +229 -0
  239. package/tests/jest/BookReader.keyboard.test.js +190 -0
  240. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  241. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  242. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  243. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  244. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  245. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  246. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  247. package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
  248. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  249. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +25 -47
  250. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
  251. package/tests/jest/plugins/search/utils.js +25 -0
  252. package/tests/jest/plugins/search/utils.test.js +29 -0
  253. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +29 -9
  254. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  255. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  256. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  257. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  258. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  259. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  260. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  261. package/tests/jest/setup.js +3 -0
  262. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  263. package/tests/jest/util/docCookies.test.js +24 -0
  264. package/tests/{util → jest/util}/strings.test.js +1 -1
  265. package/tests/{utils.js → jest/utils.js} +38 -0
  266. package/webpack.config.js +12 -6
  267. package/.babelrc +0 -12
  268. package/.dependabot/config.yml +0 -6
  269. package/.testcaferc.json +0 -5
  270. package/BookReader/bookreader-component-bundle.js +0 -1450
  271. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  272. package/BookReader/bookreader-component-bundle.js.map +0 -1
  273. package/BookReader/jquery-1.10.1.js +0 -2
  274. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  275. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  276. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  277. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  278. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  279. package/BookReaderDemo/IIIFBookReader.js +0 -207
  280. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  281. package/BookReaderDemo/demo-iiif.js +0 -26
  282. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  283. package/karma.conf.js +0 -23
  284. package/src/BookNavigator/BookModel.js +0 -14
  285. package/src/BookNavigator/BookNavigator.js +0 -446
  286. package/src/BookNavigator/assets/book-loader.js +0 -27
  287. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  288. package/src/BookNavigator/search/a-search-result.js +0 -55
  289. package/src/BookReader/DebugConsole.js +0 -54
  290. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  291. package/src/ItemNavigator/ItemNavigator.js +0 -376
  292. package/src/ItemNavigator/providers/sharing.js +0 -29
  293. package/src/css/_MobileNav.scss +0 -194
  294. package/src/dragscrollable-br.js +0 -261
  295. package/src/lit-wrapper.js +0 -2
  296. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  297. package/src/plugins/plugin.mobile_nav.js +0 -287
  298. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  299. package/tests/BookReader/DebugConsole.test.js +0 -25
  300. package/tests/BookReader/Mode1Up.test.js +0 -164
  301. package/tests/BookReader/Mode2Up.test.js +0 -247
  302. package/tests/BookReader/utils.test.js +0 -109
  303. package/tests/e2e/helpers/desktopSearch.js +0 -72
  304. package/tests/e2e/helpers/mobileSearch.js +0 -85
  305. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  306. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  307. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  308. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  309. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  310. package/tests/plugins/plugin.chapters.test.js +0 -130
  311. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  312. package/tests/plugins/plugin.text_selection.test.js +0 -203
  313. 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 () {
@@ -214,7 +251,7 @@ BookReader.prototype.ttsPlayPause = function() {
214
251
  this.ttsToggle();
215
252
  } else {
216
253
  this.ttsEngine.togglePlayPause();
217
- this.ttsUpdateState(this.ttsEngine.paused);
254
+ this.ttsUpdateState();
218
255
  }
219
256
  };
220
257
 
@@ -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]
@@ -89,3 +64,18 @@ function searchForISO6391(language, columnsToSearch) {
89
64
  }
90
65
  return null;
91
66
  }
67
+
68
+ /**
69
+ * Checks whether the current browser supports localStorage or
70
+ * if the current context has access to it.
71
+ * @return {boolean}
72
+ */
73
+ export function hasLocalStorage() {
74
+ try {
75
+ return !!window.localStorage;
76
+ } catch (e) {
77
+ // Will throw in sandboxed iframe
78
+ // DOMException: Window.localStorage getter: Forbidden in a sandboxed document without the 'allow-same-origin' flag.
79
+ return false;
80
+ }
81
+ }
@@ -0,0 +1,191 @@
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}/${encodeURIComponent(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
+ // Not in the URL
87
+ if (!hasPropertyKey && !hasDeprecatedKey) {
88
+ return;
89
+ }
90
+
91
+ const urlStateParam = hasDeprecatedKey ? schema.deprecated_for : schema.name;
92
+ urlState[urlStateParam] = decodeURIComponent(urlStrSplitSlashObj[schema.name]);
93
+ });
94
+
95
+ // Add searchParams to urlState
96
+ Object.entries(urlSearchParamsObj).forEach(([key, value]) => {
97
+ urlState[key] = value;
98
+ });
99
+
100
+ return urlState;
101
+ }
102
+
103
+ /**
104
+ * Add or update key-value to the urlState
105
+ * @param {string} key
106
+ * @param {string} val
107
+ */
108
+ setUrlParam(key, value) {
109
+ this.urlState[key] = value;
110
+
111
+ this.pushToAddressBar();
112
+ }
113
+
114
+ /**
115
+ * Delete key-value to the urlState
116
+ * @param {string} key
117
+ */
118
+ removeUrlParam(key) {
119
+ delete this.urlState[key];
120
+
121
+ this.pushToAddressBar();
122
+ }
123
+
124
+ /**
125
+ * Get key-value from the urlState
126
+ * @param {string} key
127
+ * @return {string}
128
+ */
129
+ getUrlParam(key) {
130
+ return this.urlState[key];
131
+ }
132
+
133
+ /**
134
+ * Push URL params to addressbar
135
+ */
136
+ pushToAddressBar() {
137
+ const urlStrPath = this.urlStateToUrlString(this.urlState);
138
+ const concatenatedPath = urlStrPath !== '/' ? urlStrPath : '';
139
+ if (this.urlMode == 'history') {
140
+ if (!window.history || !window.history.replaceState) {
141
+ this.options.urlMode = 'hash';
142
+ } else {
143
+ const newUrlPath = `${this.urlHistoryBasePath}${concatenatedPath}`.trim().replace(/(\/+)/g, '/');
144
+ try {
145
+ window.history.replaceState({}, null, newUrlPath);
146
+ } catch (e) {
147
+ // DOMException on Chrome when in sandboxed iframe
148
+ this.urlMode = 'hash';
149
+ }
150
+ }
151
+ }
152
+
153
+ if (this.urlMode == 'hash') {
154
+ window.location.replace('#' + concatenatedPath);
155
+ }
156
+ this.oldLocationHash = urlStrPath;
157
+ }
158
+
159
+ /**
160
+ * Get the url and check if it has changed
161
+ * If it was changeed, update the urlState
162
+ */
163
+ listenForHashChanges() {
164
+ this.oldLocationHash = window.location.hash.substr(1);
165
+ if (this.urlLocationPollId) {
166
+ clearInterval(this.urlLocationPollId);
167
+ this.urlLocationPollId = null;
168
+ }
169
+
170
+ // check if the URL changes
171
+ const updateHash = () => {
172
+ const newFragment = window.location.hash.substr(1);
173
+ const hasFragmentChange = newFragment != this.oldLocationHash;
174
+
175
+ if (!hasFragmentChange) { return; }
176
+
177
+ this.urlState = this.urlStringToUrlState(newFragment);
178
+ };
179
+ this.urlLocationPollId = setInterval(updateHash, 500);
180
+ }
181
+
182
+ /**
183
+ * Will read either the hash or URL and return the bookreader fragment
184
+ */
185
+ pullFromAddressBar (location = window.location) {
186
+ const path = this.urlMode === 'history'
187
+ ? (location.pathname.substr(this.urlHistoryBasePath.length) + location.search)
188
+ : location.hash.substr(1);
189
+ this.urlState = this.urlStringToUrlState(path);
190
+ }
191
+ }