@internetarchive/bookreader 5.0.0-8-multiple-files → 5.0.0-80

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 (321) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +78 -6
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +442 -1393
  6. package/BookReader/BookReader.js +2 -21564
  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 -12
  13. package/BookReader/icons/2up.svg +1 -15
  14. package/BookReader/icons/advance.svg +3 -26
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -0
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -17
  19. package/BookReader/icons/fullscreen_exit.svg +1 -17
  20. package/BookReader/icons/hamburger.svg +1 -15
  21. package/BookReader/icons/left-arrow.svg +1 -12
  22. package/BookReader/icons/magnify-minus.svg +1 -16
  23. package/BookReader/icons/magnify-plus.svg +1 -17
  24. package/BookReader/icons/magnify.svg +1 -15
  25. package/BookReader/icons/pause.svg +1 -23
  26. package/BookReader/icons/play.svg +1 -22
  27. package/BookReader/icons/playback-speed.svg +1 -34
  28. package/BookReader/icons/read-aloud.svg +1 -22
  29. package/BookReader/icons/review.svg +3 -22
  30. package/BookReader/icons/thumbnails.svg +1 -17
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -22
  33. package/BookReader/images/BRicons.svg +5 -94
  34. package/BookReader/images/books_graphic.svg +1 -177
  35. package/BookReader/images/icon_book.svg +1 -12
  36. package/BookReader/images/icon_bookmark.svg +1 -12
  37. package/BookReader/images/icon_gear.svg +1 -14
  38. package/BookReader/images/icon_hamburger.svg +1 -20
  39. package/BookReader/images/icon_home.svg +1 -21
  40. package/BookReader/images/icon_info.svg +1 -11
  41. package/BookReader/images/icon_one_page.svg +1 -8
  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 -15
  45. package/BookReader/images/icon_search_button.svg +1 -8
  46. package/BookReader/images/icon_share.svg +1 -9
  47. package/BookReader/images/icon_skip-ahead.svg +1 -6
  48. package/BookReader/images/icon_skip-back.svg +2 -13
  49. package/BookReader/images/icon_speaker.svg +1 -18
  50. package/BookReader/images/icon_speaker_open.svg +1 -10
  51. package/BookReader/images/icon_thumbnails.svg +1 -12
  52. package/BookReader/images/icon_toc.svg +1 -5
  53. package/BookReader/images/icon_two_pages.svg +1 -9
  54. package/BookReader/images/marker_chap-off.svg +1 -11
  55. package/BookReader/images/marker_chap-on.svg +1 -11
  56. package/BookReader/images/marker_srch-on.svg +1 -11
  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 -172
  60. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  61. package/BookReader/plugins/plugin.autoplay.js +1 -165
  62. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  63. package/BookReader/plugins/plugin.chapters.js +22 -301
  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 -74
  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 -368
  71. package/BookReader/plugins/plugin.resume.js.map +1 -1
  72. package/BookReader/plugins/plugin.search.js +2 -1420
  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 -1080
  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 +2 -9193
  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 -269
  82. package/BookReader/plugins/plugin.url.js.map +1 -1
  83. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -379
  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 +545 -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 +9 -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 +80 -28
  136. package/src/BookNavigator/search/search-results.js +28 -25
  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 +446 -1062
  161. package/src/assets/icons/close-circle-dark.svg +1 -0
  162. package/src/assets/icons/magnify-minus.svg +3 -7
  163. package/src/assets/icons/magnify-plus.svg +3 -7
  164. package/src/assets/icons/voice.svg +1 -0
  165. package/src/css/BookReader.scss +1 -17
  166. package/src/css/_BRBookmarks.scss +1 -1
  167. package/src/css/_BRComponent.scss +1 -1
  168. package/src/css/_BRmain.scss +33 -27
  169. package/src/css/_BRnav.scss +12 -39
  170. package/src/css/_BRpages.scss +149 -40
  171. package/src/css/_BRsearch.scss +68 -230
  172. package/src/css/_BRtoolbar.scss +5 -5
  173. package/src/css/_TextSelection.scss +87 -27
  174. package/src/css/_colorbox.scss +2 -2
  175. package/src/css/_controls.scss +20 -7
  176. package/src/css/_icons.scss +7 -1
  177. package/src/ia-bookreader/ia-bookreader.js +224 -0
  178. package/src/plugins/plugin.archive_analytics.js +3 -3
  179. package/src/plugins/plugin.autoplay.js +5 -11
  180. package/src/plugins/plugin.chapters.js +237 -191
  181. package/src/plugins/plugin.iiif.js +151 -0
  182. package/src/plugins/plugin.resume.js +3 -3
  183. package/src/plugins/plugin.text_selection.js +464 -134
  184. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  185. package/src/plugins/search/plugin.search.js +175 -120
  186. package/src/plugins/search/utils.js +43 -0
  187. package/src/plugins/search/view.js +64 -202
  188. package/src/plugins/tts/AbstractTTSEngine.js +71 -40
  189. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  190. package/src/plugins/tts/PageChunk.js +15 -21
  191. package/src/plugins/tts/PageChunkIterator.js +8 -12
  192. package/src/plugins/tts/WebTTSEngine.js +87 -71
  193. package/src/plugins/tts/plugin.tts.js +96 -127
  194. package/src/plugins/tts/utils.js +15 -25
  195. package/src/plugins/url/UrlPlugin.js +191 -0
  196. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  197. package/src/util/browserSniffing.js +22 -0
  198. package/src/util/docCookies.js +21 -2
  199. package/tests/e2e/README.md +37 -0
  200. package/tests/e2e/autoplay.test.js +2 -2
  201. package/tests/e2e/base.test.js +8 -16
  202. package/tests/e2e/helpers/base.js +53 -48
  203. package/tests/e2e/helpers/debug.js +1 -1
  204. package/tests/e2e/helpers/params.js +17 -0
  205. package/tests/e2e/helpers/rightToLeft.js +8 -14
  206. package/tests/e2e/helpers/search.js +73 -0
  207. package/tests/e2e/models/Navigation.js +20 -37
  208. package/tests/e2e/rightToLeft.test.js +4 -5
  209. package/tests/e2e/viewmode.test.js +40 -33
  210. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  211. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  212. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  213. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  214. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  215. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  216. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  217. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  218. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +109 -60
  219. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  220. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  221. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  222. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  223. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  224. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  225. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  226. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  227. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  228. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  229. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  230. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  231. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  232. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  233. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  234. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  235. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  236. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  237. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  238. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  239. package/tests/jest/BookReader/utils.test.js +229 -0
  240. package/tests/jest/BookReader.keyboard.test.js +190 -0
  241. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  242. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  243. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  244. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  245. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  246. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  247. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  248. package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
  249. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  250. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +26 -47
  251. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
  252. package/tests/jest/plugins/search/utils.js +25 -0
  253. package/tests/jest/plugins/search/utils.test.js +29 -0
  254. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +29 -9
  255. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  256. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  257. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  258. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  259. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  260. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  261. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  262. package/tests/jest/setup.js +3 -0
  263. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  264. package/tests/jest/util/docCookies.test.js +24 -0
  265. package/tests/{util → jest/util}/strings.test.js +1 -1
  266. package/tests/{utils.js → jest/utils.js} +38 -0
  267. package/webpack.config.js +12 -6
  268. package/.babelrc +0 -12
  269. package/.dependabot/config.yml +0 -6
  270. package/.testcaferc.json +0 -5
  271. package/BookReader/bookreader-component-bundle.js +0 -14312
  272. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  273. package/BookReader/bookreader-component-bundle.js.map +0 -1
  274. package/BookReader/icons/sort-ascending.svg +0 -1
  275. package/BookReader/icons/sort-descending.svg +0 -1
  276. package/BookReader/jquery-1.10.1.js +0 -108
  277. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  278. package/BookReader/plugins/plugin.menu_toggle.js +0 -369
  279. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  280. package/BookReader/plugins/plugin.mobile_nav.js +0 -335
  281. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  282. package/BookReaderDemo/IIIFBookReader.js +0 -207
  283. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  284. package/BookReaderDemo/demo-iiif.js +0 -26
  285. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  286. package/karma.conf.js +0 -23
  287. package/src/BookNavigator/BookModel.js +0 -14
  288. package/src/BookNavigator/BookNavigator.js +0 -452
  289. package/src/BookNavigator/assets/book-loader.js +0 -27
  290. package/src/BookNavigator/assets/icon_sort_ascending.js +0 -5
  291. package/src/BookNavigator/assets/icon_sort_descending.js +0 -5
  292. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  293. package/src/BookNavigator/search/a-search-result.js +0 -55
  294. package/src/BookNavigator/volumes/volumes-provider.js +0 -76
  295. package/src/BookNavigator/volumes/volumes.js +0 -161
  296. package/src/BookReader/DebugConsole.js +0 -54
  297. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  298. package/src/ItemNavigator/ItemNavigator.js +0 -372
  299. package/src/ItemNavigator/providers/sharing.js +0 -29
  300. package/src/assets/icons/sort-ascending.svg +0 -1
  301. package/src/assets/icons/sort-descending.svg +0 -1
  302. package/src/css/_MobileNav.scss +0 -194
  303. package/src/dragscrollable-br.js +0 -261
  304. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  305. package/src/plugins/plugin.mobile_nav.js +0 -287
  306. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  307. package/tests/BookReader/DebugConsole.test.js +0 -25
  308. package/tests/BookReader/Mode1Up.test.js +0 -164
  309. package/tests/BookReader/Mode2Up.test.js +0 -247
  310. package/tests/BookReader/utils.test.js +0 -109
  311. package/tests/e2e/helpers/desktopSearch.js +0 -72
  312. package/tests/e2e/helpers/mobileSearch.js +0 -85
  313. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  314. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  315. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  316. package/tests/karma/BookNavigator/volumes.test.js +0 -101
  317. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  318. package/tests/plugins/plugin.chapters.test.js +0 -130
  319. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  320. package/tests/plugins/plugin.text_selection.test.js +0 -203
  321. package/tests/util/docCookies.test.js +0 -15
@@ -21,27 +21,18 @@ export default class PageChunk {
21
21
  * @param {number} leafIndex
22
22
  * @return {Promise<PageChunk[]>}
23
23
  */
24
- static fetch(server, bookPath, leafIndex) {
25
- // jquery's ajax "PromiseLike" implementation is inconsistent with
26
- // modern Promises, so convert it to a full promise (it doesn't forward
27
- // a returned promise to the next handler in the chain, which kind of
28
- // defeats the entire point of using promises to avoid "callback hell")
29
- return new Promise((res, rej) => {
30
- $.ajax({
31
- type: 'GET',
32
- url: `https://${server}/BookReader/BookReaderGetTextWrapper.php`,
33
- dataType:'jsonp',
34
- cache: true,
35
- data: {
36
- path: `${bookPath}_djvu.xml`,
37
- page: leafIndex
38
- },
39
- error: rej,
40
- })
41
- .then(chunks => {
42
- res(PageChunk._fromTextWrapperResponse(leafIndex, chunks));
43
- });
24
+ static async fetch(server, bookPath, leafIndex) {
25
+ const chunks = await $.ajax({
26
+ type: 'GET',
27
+ url: `https://${server}/BookReader/BookReaderGetTextWrapper.php`,
28
+ dataType:'jsonp',
29
+ cache: true,
30
+ data: {
31
+ path: `${bookPath}_djvu.xml`,
32
+ page: leafIndex
33
+ }
44
34
  });
35
+ return PageChunk._fromTextWrapperResponse(leafIndex, chunks);
45
36
  }
46
37
 
47
38
  /**
@@ -97,7 +88,10 @@ export default class PageChunk {
97
88
  * @return {string}
98
89
  */
99
90
  static _removeDanglingHyphens(text) {
100
- return text.replace(/-\s+/g, '');
91
+ // Some books mis-OCR a dangling hyphen as a ¬ (mathematical not sign) . Since in math
92
+ // the not sign should not appear followed by a space, we think we can safely assume
93
+ // this should be replaced.
94
+ return text.replace(/[-¬]\s+/g, '');
101
95
  }
102
96
  }
103
97
 
@@ -53,22 +53,18 @@ export default class PageChunkIterator {
53
53
  * in the correct order.
54
54
  * @return {PromiseLike<"__PageChunkIterator.AT_END__" | PageChunk>}
55
55
  */
56
- _nextUncontrolled() {
56
+ async _nextUncontrolled() {
57
57
  if (this._cursor.page == this.pageCount) {
58
58
  return Promise.resolve(PageChunkIterator.AT_END);
59
59
  }
60
-
61
60
  this._recenterBuffer(this._cursor.page);
62
-
63
- return this._fetchPageChunks(this._cursor.page)
64
- .then(chunks => {
65
- if (this._cursor.chunk == chunks.length) {
66
- this._cursor.page++;
67
- this._cursor.chunk = 0;
68
- return this._nextUncontrolled();
69
- }
70
- return chunks[this._cursor.chunk++];
71
- });
61
+ const chunks = await this._fetchPageChunks(this._cursor.page);
62
+ if (this._cursor.chunk == chunks.length) {
63
+ this._cursor.page++;
64
+ this._cursor.chunk = 0;
65
+ return this._nextUncontrolled();
66
+ }
67
+ return chunks[this._cursor.chunk++];
72
68
  }
73
69
 
74
70
  /**
@@ -1,6 +1,7 @@
1
1
  /* global br */
2
2
  import { isChrome, isFirefox } from '../../util/browserSniffing.js';
3
- import { sleep, promisifyEvent, isAndroid } from './utils.js';
3
+ import { isAndroid } from './utils.js';
4
+ import { promisifyEvent, sleep } from '../../BookReader/utils.js';
4
5
  import AbstractTTSEngine from './AbstractTTSEngine.js';
5
6
  /** @typedef {import("./AbstractTTSEngine.js").PageChunk} PageChunk */
6
7
  /** @typedef {import("./AbstractTTSEngine.js").AbstractTTSSound} AbstractTTSSound */
@@ -70,7 +71,22 @@ export default class WebTTSEngine extends AbstractTTSEngine {
70
71
  }
71
72
 
72
73
  /** @override */
73
- getVoices() { return speechSynthesis.getVoices(); }
74
+ getVoices() {
75
+ const voices = speechSynthesis.getVoices();
76
+ if (voices.filter(v => v.default).length != 1) {
77
+ // iOS bug where the default system voice is sometimes
78
+ // missing from the list
79
+ voices.unshift({
80
+ voiceURI: 'bookreader.SystemDefault',
81
+ name: 'System Default',
82
+ // Not necessarily true, but very likely
83
+ lang: navigator.language,
84
+ default: true,
85
+ localService: true,
86
+ });
87
+ }
88
+ return voices;
89
+ }
74
90
 
75
91
  /** @override */
76
92
  createSound(chunk) {
@@ -121,7 +137,11 @@ export class WebTTSSound {
121
137
  this.started = false;
122
138
 
123
139
  this.utterance = new SpeechSynthesisUtterance(this.text.slice(this._charIndex));
124
- this.utterance.voice = this.voice;
140
+ // iOS bug where the default system voice is sometimes
141
+ // missing from the list
142
+ if (this.voice?.voiceURI !== 'bookreader.SystemDefault') {
143
+ this.utterance.voice = this.voice;
144
+ }
125
145
  // Need to also set lang (for some reason); won't set voice on Chrome@Android otherwise
126
146
  if (this.voice) this.utterance.lang = this.voice.lang;
127
147
  this.utterance.rate = this.rate;
@@ -167,7 +187,7 @@ export class WebTTSSound {
167
187
  * left off.
168
188
  * @return {Promise<void>}
169
189
  */
170
- reload() {
190
+ async reload() {
171
191
  // We'll restore the pause state, so copy it here
172
192
  const wasPaused = this.paused;
173
193
  // Use recent event to determine where we'll restart from
@@ -179,14 +199,12 @@ export class WebTTSSound {
179
199
  }
180
200
 
181
201
  // We can't modify the utterance object, so we have to make a new one
182
- return this.stop()
183
- .then(() => {
184
- this.load();
185
- // Instead of playing and immediately pausing, we don't start playing. Note
186
- // this is a requirement because pause doesn't work consistently across
187
- // browsers.
188
- if (!wasPaused) this.play();
189
- });
202
+ await this.stop();
203
+ this.load();
204
+ // Instead of playing and immediately pausing, we don't start playing. Note
205
+ // this is a requirement because pause doesn't work consistently across
206
+ // browsers.
207
+ if (!wasPaused) this.play();
190
208
  }
191
209
 
192
210
  play() {
@@ -222,15 +240,16 @@ export class WebTTSSound {
222
240
  return endPromise;
223
241
  }
224
242
 
225
- finish() {
226
- this.stop().then(() => this.utterance.dispatchEvent(new Event('finish')));
243
+ async finish() {
244
+ await this.stop();
245
+ this.utterance.dispatchEvent(new Event('finish'));
227
246
  }
228
247
 
229
248
  /**
230
249
  * @override
231
250
  * Will fire a pause event unless already paused
232
251
  **/
233
- pause() {
252
+ async pause() {
234
253
  if (this.paused) return;
235
254
 
236
255
  const pausePromise = promisifyEvent(this.utterance, 'pause');
@@ -246,19 +265,18 @@ export class WebTTSSound {
246
265
  if (pauseMightNotFire) {
247
266
  // wait for it just in case
248
267
  const timeoutPromise = sleep(100).then(() => 'timeout');
249
- Promise.race([pausePromise, timeoutPromise])
250
- .then(result => {
251
- // We got our pause event; nothing to do!
252
- if (result != 'timeout') return;
253
-
254
- this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
255
- // if pause might not work, then we'll stop entirely and restart later
256
- if (pauseMightNotWork) this.stop();
257
- });
268
+ const result = await Promise.race([pausePromise, timeoutPromise]);
269
+ // We got our pause event; nothing to do!
270
+ if (result != 'timeout') return;
271
+
272
+ this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
273
+
274
+ // if pause might not work, then we'll stop entirely and restart later
275
+ if (pauseMightNotWork) this.stop();
258
276
  }
259
277
  }
260
278
 
261
- resume() {
279
+ async resume() {
262
280
  if (!this.started) {
263
281
  this.play();
264
282
  return;
@@ -278,16 +296,15 @@ export class WebTTSSound {
278
296
  speechSynthesis.resume();
279
297
 
280
298
  if (resumeMightNotFire) {
281
- Promise.race([resumePromise, sleep(100).then(() => 'timeout')])
282
- .then(result => {
283
- if (result != 'timeout') return;
284
-
285
- this.utterance.dispatchEvent(new CustomEvent('resume', {}));
286
- if (resumeMightNotWork) {
287
- const reloadPromise = this.reload();
288
- reloadPromise.then(() => this.play());
289
- }
290
- });
299
+ const result = await Promise.race([resumePromise, sleep(100).then(() => 'timeout')]);
300
+
301
+ if (result != 'timeout') return;
302
+
303
+ this.utterance.dispatchEvent(new CustomEvent('resume', {}));
304
+ if (resumeMightNotWork) {
305
+ await this.reload();
306
+ this.play();
307
+ }
291
308
  }
292
309
  }
293
310
 
@@ -296,6 +313,11 @@ export class WebTTSSound {
296
313
  this.reload();
297
314
  }
298
315
 
316
+ /** @param {SpeechSynthesisVoice} voice */
317
+ setVoice(voice) {
318
+ this.voice = voice;
319
+ this.reload();
320
+ }
299
321
  /**
300
322
  * @private
301
323
  * Chrome has a bug where it only plays 15 seconds of TTS and then
@@ -303,45 +325,39 @@ export class WebTTSSound {
303
325
  * We avoid this (as described here: https://bugs.chromium.org/p/chromium/issues/detail?id=679437#c15 )
304
326
  * by pausing after 14 seconds and ~instantly resuming.
305
327
  */
306
- _chromePausingBugFix() {
328
+ async _chromePausingBugFix() {
307
329
  const timeoutPromise = sleep(14000).then(() => 'timeout');
308
330
  const pausePromise = promisifyEvent(this.utterance, 'pause').then(() => 'paused');
309
331
  const endPromise = promisifyEvent(this.utterance, 'end').then(() => 'ended');
310
- return Promise.race([timeoutPromise, pausePromise, endPromise])
311
- .then(result => {
312
- if (location.toString().indexOf('_debugReadAloud=true') != -1) {
313
- console.log(`CHROME-PAUSE-HACK: ${result}`);
314
- }
315
- switch (result) {
316
- case 'ended':
317
- // audio was stopped/finished; nothing to do
318
- break;
319
- case 'paused':
320
- // audio was paused; wait for resume
321
- // Chrome won't let you resume the audio if 14s have passed 🤷‍
322
- // We could do the same as before (but resume+pause instead of pause+resume),
323
- // but that means we'd _constantly_ be running in the background. So in that
324
- // case, let's just restart the chunk
325
- Promise.race([
326
- promisifyEvent(this.utterance, 'resume'),
327
- sleep(14000).then(() => 'timeout'),
328
- ])
329
- .then(result => {
330
- result == 'timeout' ? this.reload() : this._chromePausingBugFix();
331
- });
332
- break;
333
- case 'timeout':
334
- // We hit Chrome's secret cut off time. Pause/resume
335
- // to be able to keep TTS-ing
336
- speechSynthesis.pause();
337
- sleep(25)
338
- .then(() => {
339
- speechSynthesis.resume();
340
- this._chromePausingBugFix();
341
- });
342
- break;
343
- }
344
- });
332
+ const result = await Promise.race([timeoutPromise, pausePromise, endPromise]);
333
+ if (location.toString().indexOf('_debugReadAloud=true') != -1) {
334
+ console.log(`CHROME-PAUSE-HACK: ${result}`);
335
+ }
336
+ switch (result) {
337
+ case 'ended':
338
+ // audio was stopped/finished; nothing to do
339
+ break;
340
+ case 'paused':
341
+ // audio was paused; wait for resume
342
+ // Chrome won't let you resume the audio if 14s have passed 🤷‍
343
+ // We could do the same as before (but resume+pause instead of pause+resume),
344
+ // but that means we'd _constantly_ be running in the background. So in that
345
+ // case, let's just restart the chunk
346
+ await Promise.race([
347
+ promisifyEvent(this.utterance, 'resume'),
348
+ sleep(14000).then(() => 'timeout'),
349
+ ]);
350
+ result == 'timeout' ? this.reload() : this._chromePausingBugFix();
351
+ break;
352
+ case 'timeout':
353
+ // We hit Chrome's secret cut off time. Pause/resume
354
+ // to be able to keep TTS-ing
355
+ speechSynthesis.pause();
356
+ await sleep(25);
357
+ speechSynthesis.resume();
358
+ this._chromePausingBugFix();
359
+ break;
360
+ }
345
361
  }
346
362
  }
347
363
 
@@ -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
  /**